UpdatesUiItem.kt 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package eu.kanade.presentation.updates
  2. import android.text.format.DateUtils
  3. import androidx.compose.foundation.combinedClickable
  4. import androidx.compose.foundation.layout.Box
  5. import androidx.compose.foundation.layout.Column
  6. import androidx.compose.foundation.layout.Row
  7. import androidx.compose.foundation.layout.Spacer
  8. import androidx.compose.foundation.layout.fillMaxHeight
  9. import androidx.compose.foundation.layout.height
  10. import androidx.compose.foundation.layout.padding
  11. import androidx.compose.foundation.layout.sizeIn
  12. import androidx.compose.foundation.layout.width
  13. import androidx.compose.foundation.lazy.LazyListScope
  14. import androidx.compose.foundation.lazy.items
  15. import androidx.compose.material.icons.Icons
  16. import androidx.compose.material.icons.filled.Bookmark
  17. import androidx.compose.material3.Icon
  18. import androidx.compose.material3.MaterialTheme
  19. import androidx.compose.material3.Text
  20. import androidx.compose.runtime.Composable
  21. import androidx.compose.runtime.getValue
  22. import androidx.compose.runtime.mutableStateOf
  23. import androidx.compose.runtime.remember
  24. import androidx.compose.runtime.setValue
  25. import androidx.compose.ui.Alignment
  26. import androidx.compose.ui.Modifier
  27. import androidx.compose.ui.draw.alpha
  28. import androidx.compose.ui.hapticfeedback.HapticFeedbackType
  29. import androidx.compose.ui.platform.LocalDensity
  30. import androidx.compose.ui.platform.LocalHapticFeedback
  31. import androidx.compose.ui.res.stringResource
  32. import androidx.compose.ui.text.font.FontStyle
  33. import androidx.compose.ui.text.style.TextOverflow
  34. import androidx.compose.ui.unit.dp
  35. import eu.kanade.domain.updates.model.UpdatesWithRelations
  36. import eu.kanade.presentation.components.ChapterDownloadAction
  37. import eu.kanade.presentation.components.ChapterDownloadIndicator
  38. import eu.kanade.presentation.components.ListGroupHeader
  39. import eu.kanade.presentation.components.MangaCover
  40. import eu.kanade.presentation.manga.components.DotSeparatorText
  41. import eu.kanade.presentation.util.ReadItemAlpha
  42. import eu.kanade.presentation.util.padding
  43. import eu.kanade.presentation.util.selectedBackground
  44. import eu.kanade.tachiyomi.R
  45. import eu.kanade.tachiyomi.data.download.model.Download
  46. import eu.kanade.tachiyomi.ui.updates.UpdatesItem
  47. import java.util.Date
  48. import kotlin.time.Duration.Companion.minutes
  49. fun LazyListScope.updatesLastUpdatedItem(
  50. lastUpdated: Long,
  51. ) {
  52. item(key = "updates-lastUpdated") {
  53. val time = remember(lastUpdated) {
  54. val now = Date().time
  55. if (now - lastUpdated < 1.minutes.inWholeMilliseconds) {
  56. null
  57. } else {
  58. DateUtils.getRelativeTimeSpanString(lastUpdated, now, DateUtils.MINUTE_IN_MILLIS)
  59. }
  60. }
  61. Box(
  62. modifier = Modifier
  63. .animateItemPlacement()
  64. .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
  65. ) {
  66. Text(
  67. text = if (time.isNullOrEmpty()) {
  68. stringResource(R.string.updates_last_update_info, stringResource(R.string.updates_last_update_info_just_now))
  69. } else {
  70. stringResource(R.string.updates_last_update_info, time)
  71. },
  72. fontStyle = FontStyle.Italic,
  73. )
  74. }
  75. }
  76. }
  77. fun LazyListScope.updatesUiItems(
  78. uiModels: List<UpdatesUiModel>,
  79. selectionMode: Boolean,
  80. onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
  81. onClickCover: (UpdatesItem) -> Unit,
  82. onClickUpdate: (UpdatesItem) -> Unit,
  83. onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
  84. ) {
  85. items(
  86. items = uiModels,
  87. contentType = {
  88. when (it) {
  89. is UpdatesUiModel.Header -> "header"
  90. is UpdatesUiModel.Item -> "item"
  91. }
  92. },
  93. key = {
  94. when (it) {
  95. is UpdatesUiModel.Header -> "updatesHeader-${it.hashCode()}"
  96. is UpdatesUiModel.Item -> "updates-${it.item.update.mangaId}-${it.item.update.chapterId}"
  97. }
  98. },
  99. ) { item ->
  100. when (item) {
  101. is UpdatesUiModel.Header -> {
  102. ListGroupHeader(
  103. modifier = Modifier.animateItemPlacement(),
  104. text = item.date,
  105. )
  106. }
  107. is UpdatesUiModel.Item -> {
  108. val updatesItem = item.item
  109. UpdatesUiItem(
  110. modifier = Modifier.animateItemPlacement(),
  111. update = updatesItem.update,
  112. selected = updatesItem.selected,
  113. readProgress = updatesItem.update.lastPageRead
  114. .takeIf { !updatesItem.update.read && it > 0L }
  115. ?.let {
  116. stringResource(
  117. R.string.chapter_progress,
  118. it + 1,
  119. )
  120. },
  121. onLongClick = {
  122. onUpdateSelected(updatesItem, !updatesItem.selected, true, true)
  123. },
  124. onClick = {
  125. when {
  126. selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
  127. else -> onClickUpdate(updatesItem)
  128. }
  129. },
  130. onClickCover = { onClickCover(updatesItem) }.takeIf { !selectionMode },
  131. onDownloadChapter = { action: ChapterDownloadAction ->
  132. onDownloadChapter(listOf(updatesItem), action)
  133. }.takeIf { !selectionMode },
  134. downloadStateProvider = updatesItem.downloadStateProvider,
  135. downloadProgressProvider = updatesItem.downloadProgressProvider,
  136. )
  137. }
  138. }
  139. }
  140. }
  141. @Composable
  142. fun UpdatesUiItem(
  143. modifier: Modifier,
  144. update: UpdatesWithRelations,
  145. selected: Boolean,
  146. readProgress: String?,
  147. onClick: () -> Unit,
  148. onLongClick: () -> Unit,
  149. onClickCover: (() -> Unit)?,
  150. onDownloadChapter: ((ChapterDownloadAction) -> Unit)?,
  151. // Download Indicator
  152. downloadStateProvider: () -> Download.State,
  153. downloadProgressProvider: () -> Int,
  154. ) {
  155. val haptic = LocalHapticFeedback.current
  156. Row(
  157. modifier = modifier
  158. .selectedBackground(selected)
  159. .combinedClickable(
  160. onClick = onClick,
  161. onLongClick = {
  162. onLongClick()
  163. haptic.performHapticFeedback(HapticFeedbackType.LongPress)
  164. },
  165. )
  166. .height(56.dp)
  167. .padding(horizontal = MaterialTheme.padding.medium),
  168. verticalAlignment = Alignment.CenterVertically,
  169. ) {
  170. MangaCover.Square(
  171. modifier = Modifier
  172. .padding(vertical = 6.dp)
  173. .fillMaxHeight(),
  174. data = update.coverData,
  175. onClick = onClickCover,
  176. )
  177. Column(
  178. modifier = Modifier
  179. .padding(horizontal = MaterialTheme.padding.medium)
  180. .weight(1f),
  181. ) {
  182. val bookmark = remember(update.bookmark) { update.bookmark }
  183. val read = remember(update.read) { update.read }
  184. val textAlpha = remember(read) { if (read) ReadItemAlpha else 1f }
  185. Text(
  186. text = update.mangaTitle,
  187. maxLines = 1,
  188. style = MaterialTheme.typography.bodyMedium,
  189. overflow = TextOverflow.Ellipsis,
  190. modifier = Modifier.alpha(textAlpha),
  191. )
  192. Row(verticalAlignment = Alignment.CenterVertically) {
  193. var textHeight by remember { mutableStateOf(0) }
  194. if (bookmark) {
  195. Icon(
  196. imageVector = Icons.Filled.Bookmark,
  197. contentDescription = stringResource(R.string.action_filter_bookmarked),
  198. modifier = Modifier
  199. .sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
  200. tint = MaterialTheme.colorScheme.primary,
  201. )
  202. Spacer(modifier = Modifier.width(2.dp))
  203. }
  204. Text(
  205. text = update.chapterName,
  206. maxLines = 1,
  207. style = MaterialTheme.typography.bodySmall,
  208. overflow = TextOverflow.Ellipsis,
  209. onTextLayout = { textHeight = it.size.height },
  210. modifier = Modifier
  211. .weight(weight = 1f, fill = false)
  212. .alpha(textAlpha),
  213. )
  214. if (readProgress != null) {
  215. DotSeparatorText()
  216. Text(
  217. text = readProgress,
  218. maxLines = 1,
  219. overflow = TextOverflow.Ellipsis,
  220. modifier = Modifier.alpha(ReadItemAlpha),
  221. )
  222. }
  223. }
  224. }
  225. ChapterDownloadIndicator(
  226. enabled = onDownloadChapter != null,
  227. modifier = Modifier.padding(start = 4.dp),
  228. downloadStateProvider = downloadStateProvider,
  229. downloadProgressProvider = downloadProgressProvider,
  230. onClick = { onDownloadChapter?.invoke(it) },
  231. )
  232. }
  233. }