SourcesScreen.kt 6.9 KB

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