Banners.kt 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package eu.kanade.presentation.components
  2. import androidx.compose.animation.AnimatedVisibility
  3. import androidx.compose.animation.expandVertically
  4. import androidx.compose.animation.shrinkVertically
  5. import androidx.compose.foundation.background
  6. import androidx.compose.foundation.layout.Arrangement
  7. import androidx.compose.foundation.layout.Row
  8. import androidx.compose.foundation.layout.Spacer
  9. import androidx.compose.foundation.layout.WindowInsets
  10. import androidx.compose.foundation.layout.fillMaxWidth
  11. import androidx.compose.foundation.layout.padding
  12. import androidx.compose.foundation.layout.requiredSize
  13. import androidx.compose.foundation.layout.statusBars
  14. import androidx.compose.foundation.layout.width
  15. import androidx.compose.foundation.layout.windowInsetsPadding
  16. import androidx.compose.material3.CircularProgressIndicator
  17. import androidx.compose.material3.MaterialTheme
  18. import androidx.compose.material3.Text
  19. import androidx.compose.runtime.Composable
  20. import androidx.compose.runtime.getValue
  21. import androidx.compose.runtime.mutableStateOf
  22. import androidx.compose.runtime.remember
  23. import androidx.compose.runtime.setValue
  24. import androidx.compose.ui.Modifier
  25. import androidx.compose.ui.layout.SubcomposeLayout
  26. import androidx.compose.ui.platform.LocalDensity
  27. import androidx.compose.ui.text.style.TextAlign
  28. import androidx.compose.ui.unit.dp
  29. import androidx.compose.ui.util.fastForEach
  30. import androidx.compose.ui.util.fastMap
  31. import androidx.compose.ui.util.fastMaxBy
  32. import dev.icerock.moko.resources.StringResource
  33. import tachiyomi.i18n.MR
  34. import tachiyomi.presentation.core.i18n.localize
  35. val DownloadedOnlyBannerBackgroundColor
  36. @Composable get() = MaterialTheme.colorScheme.tertiary
  37. val IncognitoModeBannerBackgroundColor
  38. @Composable get() = MaterialTheme.colorScheme.primary
  39. val IndexingBannerBackgroundColor
  40. @Composable get() = MaterialTheme.colorScheme.secondary
  41. @Composable
  42. fun WarningBanner(
  43. textRes: StringResource,
  44. modifier: Modifier = Modifier,
  45. ) {
  46. Text(
  47. text = localize(textRes),
  48. modifier = modifier
  49. .fillMaxWidth()
  50. .background(MaterialTheme.colorScheme.error)
  51. .padding(16.dp),
  52. color = MaterialTheme.colorScheme.onError,
  53. style = MaterialTheme.typography.bodyMedium,
  54. textAlign = TextAlign.Center,
  55. )
  56. }
  57. @Composable
  58. fun AppStateBanners(
  59. downloadedOnlyMode: Boolean,
  60. incognitoMode: Boolean,
  61. indexing: Boolean,
  62. modifier: Modifier = Modifier,
  63. ) {
  64. val density = LocalDensity.current
  65. val mainInsets = WindowInsets.statusBars
  66. val mainInsetsTop = mainInsets.getTop(density)
  67. SubcomposeLayout(modifier = modifier) { constraints ->
  68. val indexingPlaceable = subcompose(0) {
  69. AnimatedVisibility(
  70. visible = indexing,
  71. enter = expandVertically(),
  72. exit = shrinkVertically(),
  73. ) {
  74. IndexingDownloadBanner(
  75. modifier = Modifier.windowInsetsPadding(mainInsets),
  76. )
  77. }
  78. }.fastMap { it.measure(constraints) }
  79. val indexingHeight = indexingPlaceable.fastMaxBy { it.height }?.height ?: 0
  80. val downloadedOnlyPlaceable = subcompose(1) {
  81. AnimatedVisibility(
  82. visible = downloadedOnlyMode,
  83. enter = expandVertically(),
  84. exit = shrinkVertically(),
  85. ) {
  86. val top = (mainInsetsTop - indexingHeight).coerceAtLeast(0)
  87. DownloadedOnlyModeBanner(
  88. modifier = Modifier.windowInsetsPadding(WindowInsets(top = top)),
  89. )
  90. }
  91. }.fastMap { it.measure(constraints) }
  92. val downloadedOnlyHeight = downloadedOnlyPlaceable.fastMaxBy { it.height }?.height ?: 0
  93. val incognitoPlaceable = subcompose(2) {
  94. AnimatedVisibility(
  95. visible = incognitoMode,
  96. enter = expandVertically(),
  97. exit = shrinkVertically(),
  98. ) {
  99. val top = (mainInsetsTop - indexingHeight - downloadedOnlyHeight).coerceAtLeast(0)
  100. IncognitoModeBanner(
  101. modifier = Modifier.windowInsetsPadding(WindowInsets(top = top)),
  102. )
  103. }
  104. }.fastMap { it.measure(constraints) }
  105. val incognitoHeight = incognitoPlaceable.fastMaxBy { it.height }?.height ?: 0
  106. layout(constraints.maxWidth, indexingHeight + downloadedOnlyHeight + incognitoHeight) {
  107. indexingPlaceable.fastForEach {
  108. it.place(0, 0)
  109. }
  110. downloadedOnlyPlaceable.fastForEach {
  111. it.place(0, indexingHeight)
  112. }
  113. incognitoPlaceable.fastForEach {
  114. it.place(0, indexingHeight + downloadedOnlyHeight)
  115. }
  116. }
  117. }
  118. }
  119. @Composable
  120. private fun DownloadedOnlyModeBanner(modifier: Modifier = Modifier) {
  121. Text(
  122. text = localize(MR.strings.label_downloaded_only),
  123. modifier = Modifier
  124. .background(DownloadedOnlyBannerBackgroundColor)
  125. .fillMaxWidth()
  126. .padding(4.dp)
  127. .then(modifier),
  128. color = MaterialTheme.colorScheme.onTertiary,
  129. textAlign = TextAlign.Center,
  130. style = MaterialTheme.typography.labelMedium,
  131. )
  132. }
  133. @Composable
  134. private fun IncognitoModeBanner(modifier: Modifier = Modifier) {
  135. Text(
  136. text = localize(MR.strings.pref_incognito_mode),
  137. modifier = Modifier
  138. .background(IncognitoModeBannerBackgroundColor)
  139. .fillMaxWidth()
  140. .padding(4.dp)
  141. .then(modifier),
  142. color = MaterialTheme.colorScheme.onPrimary,
  143. textAlign = TextAlign.Center,
  144. style = MaterialTheme.typography.labelMedium,
  145. )
  146. }
  147. @Composable
  148. private fun IndexingDownloadBanner(modifier: Modifier = Modifier) {
  149. val density = LocalDensity.current
  150. Row(
  151. modifier = Modifier
  152. .background(color = IndexingBannerBackgroundColor)
  153. .fillMaxWidth()
  154. .padding(8.dp)
  155. .then(modifier),
  156. horizontalArrangement = Arrangement.Center,
  157. ) {
  158. var textHeight by remember { mutableStateOf(0.dp) }
  159. CircularProgressIndicator(
  160. modifier = Modifier.requiredSize(textHeight),
  161. color = MaterialTheme.colorScheme.onSecondary,
  162. strokeWidth = textHeight / 8,
  163. )
  164. Spacer(modifier = Modifier.width(8.dp))
  165. Text(
  166. text = localize(MR.strings.download_notifier_cache_renewal),
  167. color = MaterialTheme.colorScheme.onSecondary,
  168. textAlign = TextAlign.Center,
  169. style = MaterialTheme.typography.labelMedium,
  170. onTextLayout = {
  171. with(density) {
  172. textHeight = it.size.height.toDp()
  173. }
  174. },
  175. )
  176. }
  177. }