UpdatesScreen.kt 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package eu.kanade.presentation.updates
  2. import androidx.activity.compose.BackHandler
  3. import androidx.compose.foundation.layout.fillMaxWidth
  4. import androidx.compose.foundation.layout.padding
  5. import androidx.compose.material.icons.Icons
  6. import androidx.compose.material.icons.outlined.FlipToBack
  7. import androidx.compose.material.icons.outlined.Refresh
  8. import androidx.compose.material.icons.outlined.SelectAll
  9. import androidx.compose.material3.SnackbarHost
  10. import androidx.compose.material3.SnackbarHostState
  11. import androidx.compose.material3.TopAppBarScrollBehavior
  12. import androidx.compose.runtime.Composable
  13. import androidx.compose.runtime.getValue
  14. import androidx.compose.runtime.mutableStateOf
  15. import androidx.compose.runtime.remember
  16. import androidx.compose.runtime.rememberCoroutineScope
  17. import androidx.compose.runtime.setValue
  18. import androidx.compose.ui.Modifier
  19. import androidx.compose.ui.util.fastAll
  20. import androidx.compose.ui.util.fastAny
  21. import eu.kanade.presentation.components.AppBar
  22. import eu.kanade.presentation.components.AppBarActions
  23. import eu.kanade.presentation.manga.components.ChapterDownloadAction
  24. import eu.kanade.presentation.manga.components.MangaBottomActionMenu
  25. import eu.kanade.tachiyomi.data.download.model.Download
  26. import eu.kanade.tachiyomi.ui.updates.UpdatesItem
  27. import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel
  28. import kotlinx.collections.immutable.persistentListOf
  29. import kotlinx.coroutines.delay
  30. import kotlinx.coroutines.launch
  31. import tachiyomi.i18n.MR
  32. import tachiyomi.presentation.core.components.FastScrollLazyColumn
  33. import tachiyomi.presentation.core.components.material.PullRefresh
  34. import tachiyomi.presentation.core.components.material.Scaffold
  35. import tachiyomi.presentation.core.i18n.stringResource
  36. import tachiyomi.presentation.core.screens.EmptyScreen
  37. import tachiyomi.presentation.core.screens.LoadingScreen
  38. import java.util.Date
  39. import kotlin.time.Duration.Companion.seconds
  40. @Composable
  41. fun UpdateScreen(
  42. state: UpdatesScreenModel.State,
  43. snackbarHostState: SnackbarHostState,
  44. lastUpdated: Long,
  45. onClickCover: (UpdatesItem) -> Unit,
  46. onSelectAll: (Boolean) -> Unit,
  47. onInvertSelection: () -> Unit,
  48. onUpdateLibrary: () -> Boolean,
  49. onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
  50. onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
  51. onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
  52. onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
  53. onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
  54. onOpenChapter: (UpdatesItem) -> Unit,
  55. ) {
  56. BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
  57. Scaffold(
  58. topBar = { scrollBehavior ->
  59. UpdatesAppBar(
  60. onUpdateLibrary = { onUpdateLibrary() },
  61. actionModeCounter = state.selected.size,
  62. onSelectAll = { onSelectAll(true) },
  63. onInvertSelection = { onInvertSelection() },
  64. onCancelActionMode = { onSelectAll(false) },
  65. scrollBehavior = scrollBehavior,
  66. )
  67. },
  68. bottomBar = {
  69. UpdatesBottomBar(
  70. selected = state.selected,
  71. onDownloadChapter = onDownloadChapter,
  72. onMultiBookmarkClicked = onMultiBookmarkClicked,
  73. onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
  74. onMultiDeleteClicked = onMultiDeleteClicked,
  75. )
  76. },
  77. snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
  78. ) { contentPadding ->
  79. when {
  80. state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
  81. state.items.isEmpty() -> EmptyScreen(
  82. stringRes = MR.strings.information_no_recent,
  83. modifier = Modifier.padding(contentPadding),
  84. )
  85. else -> {
  86. val scope = rememberCoroutineScope()
  87. var isRefreshing by remember { mutableStateOf(false) }
  88. PullRefresh(
  89. refreshing = isRefreshing,
  90. onRefresh = {
  91. val started = onUpdateLibrary()
  92. if (!started) return@PullRefresh
  93. scope.launch {
  94. // Fake refresh status but hide it after a second as it's a long running task
  95. isRefreshing = true
  96. delay(1.seconds)
  97. isRefreshing = false
  98. }
  99. },
  100. enabled = { !state.selectionMode },
  101. indicatorPadding = contentPadding,
  102. ) {
  103. FastScrollLazyColumn(
  104. contentPadding = contentPadding,
  105. ) {
  106. updatesLastUpdatedItem(lastUpdated)
  107. updatesUiItems(
  108. uiModels = state.getUiModel(),
  109. selectionMode = state.selectionMode,
  110. onUpdateSelected = onUpdateSelected,
  111. onClickCover = onClickCover,
  112. onClickUpdate = onOpenChapter,
  113. onDownloadChapter = onDownloadChapter,
  114. )
  115. }
  116. }
  117. }
  118. }
  119. }
  120. }
  121. @Composable
  122. private fun UpdatesAppBar(
  123. onUpdateLibrary: () -> Unit,
  124. // For action mode
  125. actionModeCounter: Int,
  126. onSelectAll: () -> Unit,
  127. onInvertSelection: () -> Unit,
  128. onCancelActionMode: () -> Unit,
  129. scrollBehavior: TopAppBarScrollBehavior,
  130. modifier: Modifier = Modifier,
  131. ) {
  132. AppBar(
  133. modifier = modifier,
  134. title = stringResource(MR.strings.label_recent_updates),
  135. actions = {
  136. AppBarActions(
  137. persistentListOf(
  138. AppBar.Action(
  139. title = stringResource(MR.strings.action_update_library),
  140. icon = Icons.Outlined.Refresh,
  141. onClick = onUpdateLibrary,
  142. ),
  143. ),
  144. )
  145. },
  146. actionModeCounter = actionModeCounter,
  147. onCancelActionMode = onCancelActionMode,
  148. actionModeActions = {
  149. AppBarActions(
  150. persistentListOf(
  151. AppBar.Action(
  152. title = stringResource(MR.strings.action_select_all),
  153. icon = Icons.Outlined.SelectAll,
  154. onClick = onSelectAll,
  155. ),
  156. AppBar.Action(
  157. title = stringResource(MR.strings.action_select_inverse),
  158. icon = Icons.Outlined.FlipToBack,
  159. onClick = onInvertSelection,
  160. ),
  161. ),
  162. )
  163. },
  164. scrollBehavior = scrollBehavior,
  165. )
  166. }
  167. @Composable
  168. private fun UpdatesBottomBar(
  169. selected: List<UpdatesItem>,
  170. onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
  171. onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
  172. onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
  173. onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
  174. ) {
  175. MangaBottomActionMenu(
  176. visible = selected.isNotEmpty(),
  177. modifier = Modifier.fillMaxWidth(),
  178. onBookmarkClicked = {
  179. onMultiBookmarkClicked.invoke(selected, true)
  180. }.takeIf { selected.fastAny { !it.update.bookmark } },
  181. onRemoveBookmarkClicked = {
  182. onMultiBookmarkClicked.invoke(selected, false)
  183. }.takeIf { selected.fastAll { it.update.bookmark } },
  184. onMarkAsReadClicked = {
  185. onMultiMarkAsReadClicked(selected, true)
  186. }.takeIf { selected.fastAny { !it.update.read } },
  187. onMarkAsUnreadClicked = {
  188. onMultiMarkAsReadClicked(selected, false)
  189. }.takeIf { selected.fastAny { it.update.read || it.update.lastPageRead > 0L } },
  190. onDownloadClicked = {
  191. onDownloadChapter(selected, ChapterDownloadAction.START)
  192. }.takeIf {
  193. selected.fastAny { it.downloadStateProvider() != Download.State.DOWNLOADED }
  194. },
  195. onDeleteClicked = {
  196. onMultiDeleteClicked(selected)
  197. }.takeIf { selected.fastAny { it.downloadStateProvider() == Download.State.DOWNLOADED } },
  198. )
  199. }
  200. sealed interface UpdatesUiModel {
  201. data class Header(val date: Date) : UpdatesUiModel
  202. data class Item(val item: UpdatesItem) : UpdatesUiModel
  203. }