SourcesScreen.kt 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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.res.stringResource
  22. import androidx.compose.ui.unit.dp
  23. import eu.kanade.presentation.browse.components.BaseSourceItem
  24. import eu.kanade.tachiyomi.R
  25. import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
  26. import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
  27. import eu.kanade.tachiyomi.util.system.LocaleHelper
  28. import tachiyomi.domain.source.model.Pin
  29. import tachiyomi.domain.source.model.Source
  30. import tachiyomi.presentation.core.components.ScrollbarLazyColumn
  31. import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
  32. import tachiyomi.presentation.core.components.material.padding
  33. import tachiyomi.presentation.core.components.material.topSmallPaddingValues
  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. textResource = R.string.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. language = model.language,
  76. )
  77. }
  78. is SourceUiModel.Item -> SourceItem(
  79. source = model.source,
  80. onClickItem = onClickItem,
  81. onLongClickItem = onLongClickItem,
  82. onClickPin = onClickPin,
  83. )
  84. }
  85. }
  86. }
  87. }
  88. }
  89. }
  90. @Composable
  91. private fun SourceHeader(
  92. language: String,
  93. modifier: Modifier = Modifier,
  94. ) {
  95. val context = LocalContext.current
  96. Text(
  97. text = LocaleHelper.getSourceDisplayName(language, context),
  98. modifier = modifier
  99. .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
  100. style = MaterialTheme.typography.header,
  101. )
  102. }
  103. @Composable
  104. private fun SourceItem(
  105. source: Source,
  106. onClickItem: (Source, Listing) -> Unit,
  107. onLongClickItem: (Source) -> Unit,
  108. onClickPin: (Source) -> Unit,
  109. modifier: Modifier = Modifier,
  110. ) {
  111. BaseSourceItem(
  112. modifier = modifier,
  113. source = source,
  114. onClickItem = { onClickItem(source, Listing.Popular) },
  115. onLongClickItem = { onLongClickItem(source) },
  116. action = {
  117. if (source.supportsLatest) {
  118. TextButton(onClick = { onClickItem(source, Listing.Latest) }) {
  119. Text(
  120. text = stringResource(R.string.latest),
  121. style = LocalTextStyle.current.copy(
  122. color = MaterialTheme.colorScheme.primary,
  123. ),
  124. )
  125. }
  126. }
  127. SourcePinButton(
  128. isPinned = Pin.Pinned in source.pin,
  129. onClick = { onClickPin(source) },
  130. )
  131. },
  132. )
  133. }
  134. @Composable
  135. private fun SourcePinButton(
  136. isPinned: Boolean,
  137. onClick: () -> Unit,
  138. ) {
  139. val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
  140. val tint = if (isPinned) {
  141. MaterialTheme.colorScheme.primary
  142. } else {
  143. MaterialTheme.colorScheme.onBackground.copy(
  144. alpha = SecondaryItemAlpha,
  145. )
  146. }
  147. val description = if (isPinned) R.string.action_unpin else R.string.action_pin
  148. IconButton(onClick = onClick) {
  149. Icon(
  150. imageVector = icon,
  151. tint = tint,
  152. contentDescription = stringResource(description),
  153. )
  154. }
  155. }
  156. @Composable
  157. fun SourceOptionsDialog(
  158. source: Source,
  159. onClickPin: () -> Unit,
  160. onClickDisable: () -> Unit,
  161. onDismiss: () -> Unit,
  162. ) {
  163. AlertDialog(
  164. title = {
  165. Text(text = source.visualName)
  166. },
  167. text = {
  168. Column {
  169. val textId = if (Pin.Pinned in source.pin) R.string.action_unpin else R.string.action_pin
  170. Text(
  171. text = stringResource(textId),
  172. modifier = Modifier
  173. .clickable(onClick = onClickPin)
  174. .fillMaxWidth()
  175. .padding(vertical = 16.dp),
  176. )
  177. if (!source.isLocal()) {
  178. Text(
  179. text = stringResource(R.string.action_disable),
  180. modifier = Modifier
  181. .clickable(onClick = onClickDisable)
  182. .fillMaxWidth()
  183. .padding(vertical = 16.dp),
  184. )
  185. }
  186. }
  187. },
  188. onDismissRequest = onDismiss,
  189. confirmButton = {},
  190. )
  191. }
  192. sealed interface SourceUiModel {
  193. data class Item(val source: Source) : SourceUiModel
  194. data class Header(val language: String) : SourceUiModel
  195. }