UpdatesUiItem.kt 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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.LocalTextStyle
  19. import androidx.compose.material3.MaterialTheme
  20. import androidx.compose.material3.Text
  21. import androidx.compose.runtime.Composable
  22. import androidx.compose.runtime.getValue
  23. import androidx.compose.runtime.mutableStateOf
  24. import androidx.compose.runtime.remember
  25. import androidx.compose.runtime.setValue
  26. import androidx.compose.ui.Alignment
  27. import androidx.compose.ui.Modifier
  28. import androidx.compose.ui.draw.alpha
  29. import androidx.compose.ui.hapticfeedback.HapticFeedbackType
  30. import androidx.compose.ui.platform.LocalDensity
  31. import androidx.compose.ui.platform.LocalHapticFeedback
  32. import androidx.compose.ui.res.stringResource
  33. import androidx.compose.ui.text.font.FontStyle
  34. import androidx.compose.ui.text.style.TextOverflow
  35. import androidx.compose.ui.unit.dp
  36. import eu.kanade.domain.updates.model.UpdatesWithRelations
  37. import eu.kanade.presentation.components.ChapterDownloadAction
  38. import eu.kanade.presentation.components.ChapterDownloadIndicator
  39. import eu.kanade.presentation.components.MangaCover
  40. import eu.kanade.presentation.components.RelativeDateHeader
  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.text.DateFormat
  48. import java.util.Date
  49. import kotlin.time.Duration.Companion.minutes
  50. fun LazyListScope.updatesLastUpdatedItem(
  51. lastUpdated: Long,
  52. ) {
  53. item(key = "updates-lastUpdated") {
  54. val time = remember(lastUpdated) {
  55. val now = Date().time
  56. if (now - lastUpdated < 1.minutes.inWholeMilliseconds) {
  57. null
  58. } else {
  59. DateUtils.getRelativeTimeSpanString(lastUpdated, now, DateUtils.MINUTE_IN_MILLIS)
  60. }
  61. }
  62. Box(
  63. modifier = Modifier
  64. .animateItemPlacement()
  65. .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
  66. ) {
  67. Text(
  68. text = if (time.isNullOrEmpty()) {
  69. stringResource(R.string.updates_last_update_info, stringResource(R.string.updates_last_update_info_just_now))
  70. } else {
  71. stringResource(R.string.updates_last_update_info, time)
  72. },
  73. style = LocalTextStyle.current.copy(
  74. fontStyle = FontStyle.Italic,
  75. ),
  76. )
  77. }
  78. }
  79. }
  80. fun LazyListScope.updatesUiItems(
  81. uiModels: List<UpdatesUiModel>,
  82. selectionMode: Boolean,
  83. onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
  84. onClickCover: (UpdatesItem) -> Unit,
  85. onClickUpdate: (UpdatesItem) -> Unit,
  86. onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
  87. relativeTime: Int,
  88. dateFormat: DateFormat,
  89. ) {
  90. items(
  91. items = uiModels,
  92. contentType = {
  93. when (it) {
  94. is UpdatesUiModel.Header -> "header"
  95. is UpdatesUiModel.Item -> "item"
  96. }
  97. },
  98. key = {
  99. when (it) {
  100. is UpdatesUiModel.Header -> "updatesHeader-${it.hashCode()}"
  101. is UpdatesUiModel.Item -> "updates-${it.item.update.mangaId}-${it.item.update.chapterId}"
  102. }
  103. },
  104. ) { item ->
  105. when (item) {
  106. is UpdatesUiModel.Header -> {
  107. RelativeDateHeader(
  108. modifier = Modifier.animateItemPlacement(),
  109. date = item.date,
  110. relativeTime = relativeTime,
  111. dateFormat = dateFormat,
  112. )
  113. }
  114. is UpdatesUiModel.Item -> {
  115. val updatesItem = item.item
  116. UpdatesUiItem(
  117. modifier = Modifier.animateItemPlacement(),
  118. update = updatesItem.update,
  119. selected = updatesItem.selected,
  120. onLongClick = {
  121. onUpdateSelected(updatesItem, !updatesItem.selected, true, true)
  122. },
  123. onClick = {
  124. when {
  125. selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
  126. else -> onClickUpdate(updatesItem)
  127. }
  128. },
  129. onClickCover = { if (selectionMode.not()) onClickCover(updatesItem) },
  130. onDownloadChapter = {
  131. if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
  132. },
  133. downloadIndicatorEnabled = selectionMode.not(),
  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. onClick: () -> Unit,
  147. onLongClick: () -> Unit,
  148. onClickCover: () -> Unit,
  149. onDownloadChapter: (ChapterDownloadAction) -> Unit,
  150. // Download Indicator
  151. downloadIndicatorEnabled: Boolean,
  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. val secondaryTextColor = if (bookmark && !read) {
  186. MaterialTheme.colorScheme.primary
  187. } else {
  188. MaterialTheme.colorScheme.onSurface
  189. }
  190. Text(
  191. text = update.mangaTitle,
  192. maxLines = 1,
  193. style = MaterialTheme.typography.bodyMedium,
  194. overflow = TextOverflow.Ellipsis,
  195. modifier = Modifier.alpha(textAlpha),
  196. )
  197. Row(verticalAlignment = Alignment.CenterVertically) {
  198. var textHeight by remember { mutableStateOf(0) }
  199. if (bookmark) {
  200. Icon(
  201. imageVector = Icons.Filled.Bookmark,
  202. contentDescription = stringResource(R.string.action_filter_bookmarked),
  203. modifier = Modifier
  204. .sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
  205. tint = MaterialTheme.colorScheme.primary,
  206. )
  207. Spacer(modifier = Modifier.width(2.dp))
  208. }
  209. Text(
  210. text = update.chapterName,
  211. maxLines = 1,
  212. style = MaterialTheme.typography.bodySmall
  213. .copy(color = secondaryTextColor),
  214. overflow = TextOverflow.Ellipsis,
  215. onTextLayout = { textHeight = it.size.height },
  216. modifier = Modifier.alpha(textAlpha),
  217. )
  218. }
  219. }
  220. ChapterDownloadIndicator(
  221. enabled = downloadIndicatorEnabled,
  222. modifier = Modifier.padding(start = 4.dp),
  223. downloadStateProvider = downloadStateProvider,
  224. downloadProgressProvider = downloadProgressProvider,
  225. onClick = onDownloadChapter,
  226. )
  227. }
  228. }