SourcesScreen.kt 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. package eu.kanade.presentation.browse
  2. import androidx.compose.foundation.clickable
  3. import androidx.compose.foundation.layout.Column
  4. import androidx.compose.foundation.layout.PaddingValues
  5. import androidx.compose.foundation.layout.fillMaxWidth
  6. import androidx.compose.foundation.layout.padding
  7. import androidx.compose.foundation.lazy.items
  8. import androidx.compose.material.icons.Icons
  9. import androidx.compose.material.icons.filled.PushPin
  10. import androidx.compose.material.icons.outlined.PushPin
  11. import androidx.compose.material3.AlertDialog
  12. import androidx.compose.material3.Icon
  13. import androidx.compose.material3.IconButton
  14. import androidx.compose.material3.LocalTextStyle
  15. import androidx.compose.material3.MaterialTheme
  16. import androidx.compose.material3.Text
  17. import androidx.compose.material3.TextButton
  18. import androidx.compose.runtime.Composable
  19. import androidx.compose.ui.Modifier
  20. import androidx.compose.ui.platform.LocalContext
  21. import androidx.compose.ui.unit.dp
  22. import eu.kanade.presentation.browse.components.BaseSourceItem
  23. import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
  24. import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
  25. import eu.kanade.tachiyomi.util.system.LocaleHelper
  26. import tachiyomi.domain.source.model.Pin
  27. import tachiyomi.domain.source.model.Source
  28. import tachiyomi.i18n.MR
  29. import tachiyomi.presentation.core.components.ScrollbarLazyColumn
  30. import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
  31. import tachiyomi.presentation.core.components.material.padding
  32. import tachiyomi.presentation.core.components.material.topSmallPaddingValues
  33. import tachiyomi.presentation.core.i18n.stringResource
  34. import tachiyomi.presentation.core.screens.EmptyScreen
  35. import tachiyomi.presentation.core.screens.LoadingScreen
  36. import tachiyomi.presentation.core.theme.header
  37. import tachiyomi.presentation.core.util.plus
  38. import tachiyomi.source.local.isLocal
  39. @Composable
  40. fun SourcesScreen(
  41. state: SourcesScreenModel.State,
  42. contentPadding: PaddingValues,
  43. onClickItem: (Source, Listing) -> Unit,
  44. onClickPin: (Source) -> Unit,
  45. onLongClickItem: (Source) -> Unit,
  46. ) {
  47. when {
  48. state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
  49. state.isEmpty -> EmptyScreen(
  50. stringRes = MR.strings.source_empty_screen,
  51. modifier = Modifier.padding(contentPadding),
  52. )
  53. else -> {
  54. ScrollbarLazyColumn(
  55. contentPadding = contentPadding + topSmallPaddingValues,
  56. ) {
  57. items(
  58. items = state.items,
  59. contentType = {
  60. when (it) {
  61. is SourceUiModel.Header -> "header"
  62. is SourceUiModel.Item -> "item"
  63. }
  64. },
  65. key = {
  66. when (it) {
  67. is SourceUiModel.Header -> it.hashCode()
  68. is SourceUiModel.Item -> "source-${it.source.key()}"
  69. }
  70. },
  71. ) { model ->
  72. when (model) {
  73. is SourceUiModel.Header -> {
  74. SourceHeader(
  75. modifier = Modifier.animateItemPlacement(),
  76. language = model.language,
  77. )
  78. }
  79. is SourceUiModel.Item -> SourceItem(
  80. modifier = Modifier.animateItemPlacement(),
  81. source = model.source,
  82. onClickItem = onClickItem,
  83. onLongClickItem = onLongClickItem,
  84. onClickPin = onClickPin,
  85. )
  86. }
  87. }
  88. }
  89. }
  90. }
  91. }
  92. @Composable
  93. private fun SourceHeader(
  94. language: String,
  95. modifier: Modifier = Modifier,
  96. ) {
  97. val context = LocalContext.current
  98. Text(
  99. text = LocaleHelper.getSourceDisplayName(language, context),
  100. modifier = modifier
  101. .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
  102. style = MaterialTheme.typography.header,
  103. )
  104. }
  105. @Composable
  106. private fun SourceItem(
  107. source: Source,
  108. onClickItem: (Source, Listing) -> Unit,
  109. onLongClickItem: (Source) -> Unit,
  110. onClickPin: (Source) -> Unit,
  111. modifier: Modifier = Modifier,
  112. ) {
  113. BaseSourceItem(
  114. modifier = modifier,
  115. source = source,
  116. onClickItem = { onClickItem(source, Listing.Popular) },
  117. onLongClickItem = { onLongClickItem(source) },
  118. action = {
  119. if (source.supportsLatest) {
  120. TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
  121. Text(
  122. text = stringResource(MR.strings.latest),
  123. style = LocalTextStyle.current.copy(
  124. color = MaterialTheme.colorScheme.primary,
  125. ),
  126. )
  127. }
  128. }
  129. SourcePinButton(
  130. isPinned = Pin.Pinned in source.pin,
  131. onClick = { onClickPin(source) },
  132. )
  133. },
  134. )
  135. }
  136. @Composable
  137. private fun SourcePinButton(
  138. isPinned: Boolean,
  139. onClick: () -> Unit,
  140. ) {
  141. val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
  142. val tint = if (isPinned) {
  143. MaterialTheme.colorScheme.primary
  144. } else {
  145. MaterialTheme.colorScheme.onBackground.copy(
  146. alpha = SecondaryItemAlpha,
  147. )
  148. }
  149. val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
  150. IconButton(onClick = onClick) {
  151. Icon(
  152. imageVector = icon,
  153. tint = tint,
  154. contentDescription = stringResource(description),
  155. )
  156. }
  157. }
  158. @Composable
  159. fun SourceOptionsDialog(
  160. source: Source,
  161. onClickPin: () -> Unit,
  162. onClickDisable: () -> Unit,
  163. onDismiss: () -> Unit,
  164. ) {
  165. AlertDialog(
  166. title = {
  167. Text(text = source.visualName)
  168. },
  169. text = {
  170. Column {
  171. val textId = if (Pin.Pinned in source.pin) MR.strings.action_unpin else MR.strings.action_pin
  172. Text(
  173. text = stringResource(textId),
  174. modifier = Modifier
  175. .clickable(onClick = onClickPin)
  176. .fillMaxWidth()
  177. .padding(vertical = 16.dp),
  178. )
  179. if (!source.isLocal()) {
  180. Text(
  181. text = stringResource(MR.strings.action_disable),
  182. modifier = Modifier
  183. .clickable(onClick = onClickDisable)
  184. .fillMaxWidth()
  185. .padding(vertical = 16.dp),
  186. )
  187. }
  188. }
  189. },
  190. onDismissRequest = onDismiss,
  191. confirmButton = {},
  192. )
  193. }
  194. sealed interface SourceUiModel {
  195. data class Item(val source: Source) : SourceUiModel
  196. data class Header(val language: String) : SourceUiModel
  197. }