ChapterDownloadIndicator.kt 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package eu.kanade.presentation.components
  2. import androidx.compose.animation.core.animateFloatAsState
  3. import androidx.compose.foundation.combinedClickable
  4. import androidx.compose.foundation.interaction.MutableInteractionSource
  5. import androidx.compose.foundation.layout.Box
  6. import androidx.compose.foundation.layout.padding
  7. import androidx.compose.foundation.layout.size
  8. import androidx.compose.material.icons.Icons
  9. import androidx.compose.material.icons.filled.ArrowDownward
  10. import androidx.compose.material.icons.filled.CheckCircle
  11. import androidx.compose.material.ripple.rememberRipple
  12. import androidx.compose.material3.CircularProgressIndicator
  13. import androidx.compose.material3.DropdownMenuItem
  14. import androidx.compose.material3.Icon
  15. import androidx.compose.material3.MaterialTheme
  16. import androidx.compose.material3.ProgressIndicatorDefaults
  17. import androidx.compose.material3.Text
  18. import androidx.compose.runtime.Composable
  19. import androidx.compose.runtime.getValue
  20. import androidx.compose.runtime.mutableStateOf
  21. import androidx.compose.runtime.remember
  22. import androidx.compose.runtime.setValue
  23. import androidx.compose.ui.Alignment
  24. import androidx.compose.ui.Modifier
  25. import androidx.compose.ui.graphics.Color
  26. import androidx.compose.ui.res.stringResource
  27. import androidx.compose.ui.semantics.Role
  28. import androidx.compose.ui.unit.dp
  29. import eu.kanade.presentation.manga.ChapterDownloadAction
  30. import eu.kanade.presentation.util.secondaryItemAlpha
  31. import eu.kanade.tachiyomi.R
  32. import eu.kanade.tachiyomi.data.download.model.Download
  33. @Composable
  34. fun ChapterDownloadIndicator(
  35. modifier: Modifier = Modifier,
  36. downloadStateProvider: () -> Download.State,
  37. downloadProgressProvider: () -> Int,
  38. onClick: (ChapterDownloadAction) -> Unit,
  39. ) {
  40. val downloadState = downloadStateProvider()
  41. val isDownloaded = downloadState == Download.State.DOWNLOADED
  42. val isDownloading = downloadState != Download.State.NOT_DOWNLOADED
  43. var isMenuExpanded by remember(downloadState) { mutableStateOf(false) }
  44. Box(
  45. modifier = modifier
  46. .size(IconButtonTokens.StateLayerSize)
  47. .combinedClickable(
  48. onLongClick = {
  49. val chapterDownloadAction = when {
  50. isDownloaded -> ChapterDownloadAction.DELETE
  51. isDownloading -> ChapterDownloadAction.CANCEL
  52. else -> ChapterDownloadAction.START_NOW
  53. }
  54. onClick(chapterDownloadAction)
  55. },
  56. onClick = {
  57. if (isDownloaded || isDownloading) {
  58. isMenuExpanded = true
  59. } else {
  60. onClick(ChapterDownloadAction.START)
  61. }
  62. },
  63. role = Role.Button,
  64. interactionSource = remember { MutableInteractionSource() },
  65. indication = rememberRipple(
  66. bounded = false,
  67. radius = IconButtonTokens.StateLayerSize / 2,
  68. ),
  69. ),
  70. contentAlignment = Alignment.Center,
  71. ) {
  72. if (isDownloaded) {
  73. Icon(
  74. imageVector = Icons.Default.CheckCircle,
  75. contentDescription = null,
  76. modifier = Modifier.size(IndicatorSize),
  77. tint = MaterialTheme.colorScheme.onSurfaceVariant,
  78. )
  79. DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
  80. DropdownMenuItem(
  81. text = { Text(text = stringResource(R.string.action_delete)) },
  82. onClick = {
  83. onClick(ChapterDownloadAction.DELETE)
  84. isMenuExpanded = false
  85. },
  86. )
  87. }
  88. } else {
  89. val inactiveAlphaModifier = if (!isDownloading) Modifier.secondaryItemAlpha() else Modifier
  90. val arrowColor: Color
  91. val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
  92. if (isDownloading) {
  93. val downloadProgress = downloadProgressProvider()
  94. val indeterminate = downloadState == Download.State.QUEUE ||
  95. (downloadState == Download.State.DOWNLOADING && downloadProgress == 0)
  96. if (indeterminate) {
  97. arrowColor = strokeColor
  98. CircularProgressIndicator(
  99. modifier = IndicatorModifier,
  100. color = strokeColor,
  101. strokeWidth = IndicatorStrokeWidth,
  102. )
  103. } else {
  104. val animatedProgress by animateFloatAsState(
  105. targetValue = downloadProgress / 100f,
  106. animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
  107. )
  108. arrowColor = if (animatedProgress < 0.5f) {
  109. strokeColor
  110. } else {
  111. MaterialTheme.colorScheme.background
  112. }
  113. CircularProgressIndicator(
  114. progress = animatedProgress,
  115. modifier = IndicatorModifier,
  116. color = strokeColor,
  117. strokeWidth = IndicatorSize / 2,
  118. )
  119. }
  120. DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
  121. DropdownMenuItem(
  122. text = { Text(text = stringResource(R.string.action_start_downloading_now)) },
  123. onClick = {
  124. onClick(ChapterDownloadAction.START_NOW)
  125. isMenuExpanded = false
  126. },
  127. )
  128. DropdownMenuItem(
  129. text = { Text(text = stringResource(R.string.action_cancel)) },
  130. onClick = {
  131. onClick(ChapterDownloadAction.CANCEL)
  132. isMenuExpanded = false
  133. },
  134. )
  135. }
  136. } else {
  137. arrowColor = strokeColor
  138. CircularProgressIndicator(
  139. progress = 1f,
  140. modifier = IndicatorModifier.then(inactiveAlphaModifier),
  141. color = strokeColor,
  142. strokeWidth = IndicatorStrokeWidth,
  143. )
  144. }
  145. Icon(
  146. imageVector = Icons.Default.ArrowDownward,
  147. contentDescription = null,
  148. modifier = ArrowModifier.then(inactiveAlphaModifier),
  149. tint = arrowColor,
  150. )
  151. }
  152. }
  153. }
  154. private val IndicatorSize = 26.dp
  155. private val IndicatorPadding = 2.dp
  156. // To match composable parameter name when used later
  157. private val IndicatorStrokeWidth = IndicatorPadding
  158. private val IndicatorModifier = Modifier
  159. .size(IndicatorSize)
  160. .padding(IndicatorPadding)
  161. private val ArrowModifier = Modifier
  162. .size(IndicatorSize - 7.dp)