ChapterDownloadIndicator: Fixes and improvements (#7485)

* Increased touch target
* Fix downloaded icon smaller than other states
* Deferred state reads to minimize recompose works
* Move things around to eliminate unnecessary elements
This commit is contained in:
Ivan Iskandar 2022-07-09 23:38:33 +07:00 committed by GitHub
parent 34906a7425
commit e56f6c1017
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 109 deletions

View file

@ -1,21 +1,22 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -24,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.ChapterDownloadAction import eu.kanade.presentation.manga.ChapterDownloadAction
import eu.kanade.presentation.util.secondaryItemAlpha import eu.kanade.presentation.util.secondaryItemAlpha
@ -33,23 +35,18 @@ import eu.kanade.tachiyomi.data.download.model.Download
@Composable @Composable
fun ChapterDownloadIndicator( fun ChapterDownloadIndicator(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
downloadState: Download.State, downloadStateProvider: () -> Download.State,
downloadProgress: Int, downloadProgressProvider: () -> Int,
onClick: (ChapterDownloadAction) -> Unit, onClick: (ChapterDownloadAction) -> Unit,
) { ) {
Box(modifier = modifier, contentAlignment = Alignment.Center) { val downloadState = downloadStateProvider()
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) { val isDownloaded = downloadState == Download.State.DOWNLOADED
val isDownloaded = downloadState == Download.State.DOWNLOADED val isDownloading = downloadState != Download.State.NOT_DOWNLOADED
val isDownloading = downloadState != Download.State.NOT_DOWNLOADED var isMenuExpanded by remember(downloadState) { mutableStateOf(false) }
var isMenuExpanded by remember(downloadState) { mutableStateOf(false) } Box(
IconButton( modifier = modifier
onClick = { .size(IconButtonTokens.StateLayerSize)
if (isDownloaded || isDownloading) { .combinedClickable(
isMenuExpanded = true
} else {
onClick(ChapterDownloadAction.START)
}
},
onLongClick = { onLongClick = {
val chapterDownloadAction = when { val chapterDownloadAction = when {
isDownloaded -> ChapterDownloadAction.DELETE isDownloaded -> ChapterDownloadAction.DELETE
@ -58,93 +55,101 @@ fun ChapterDownloadIndicator(
} }
onClick(chapterDownloadAction) onClick(chapterDownloadAction)
}, },
) { onClick = {
val indicatorModifier = Modifier if (isDownloaded || isDownloading) {
.size(IndicatorSize) isMenuExpanded = true
.padding(IndicatorPadding)
if (isDownloaded) {
Icon(
imageVector = Icons.Default.CheckCircle,
contentDescription = null,
modifier = indicatorModifier,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_delete)) },
onClick = {
onClick(ChapterDownloadAction.DELETE)
isMenuExpanded = false
},
)
}
} else {
val inactiveAlphaModifier = if (!isDownloading) Modifier.secondaryItemAlpha() else Modifier
val arrowModifier = Modifier
.size(IndicatorSize - 7.dp)
.then(inactiveAlphaModifier)
val arrowColor: Color
val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
if (isDownloading) {
val indeterminate = downloadState == Download.State.QUEUE ||
(downloadState == Download.State.DOWNLOADING && downloadProgress == 0)
if (indeterminate) {
arrowColor = strokeColor
CircularProgressIndicator(
modifier = indicatorModifier,
color = strokeColor,
strokeWidth = IndicatorStrokeWidth,
)
} else {
val animatedProgress by animateFloatAsState(
targetValue = downloadProgress / 100f,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
)
arrowColor = if (animatedProgress < 0.5f) {
strokeColor
} else {
MaterialTheme.colorScheme.background
}
CircularProgressIndicator(
progress = animatedProgress,
modifier = indicatorModifier,
color = strokeColor,
strokeWidth = IndicatorSize / 2,
)
}
} else { } else {
arrowColor = strokeColor onClick(ChapterDownloadAction.START)
CircularProgressIndicator(
progress = 1f,
modifier = indicatorModifier.then(inactiveAlphaModifier),
color = strokeColor,
strokeWidth = IndicatorStrokeWidth,
)
} }
Icon( },
imageVector = Icons.Default.ArrowDownward, role = Role.Button,
contentDescription = null, interactionSource = remember { MutableInteractionSource() },
modifier = arrowModifier, indication = rememberRipple(
tint = arrowColor, bounded = false,
) radius = IconButtonTokens.StateLayerSize / 2,
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) { ),
DropdownMenuItem( ),
text = { Text(text = stringResource(R.string.action_start_downloading_now)) }, contentAlignment = Alignment.Center,
onClick = { ) {
onClick(ChapterDownloadAction.START_NOW) if (isDownloaded) {
isMenuExpanded = false Icon(
}, imageVector = Icons.Default.CheckCircle,
) contentDescription = null,
DropdownMenuItem( modifier = Modifier.size(IndicatorSize),
text = { Text(text = stringResource(R.string.action_cancel)) }, tint = MaterialTheme.colorScheme.onSurfaceVariant,
onClick = { )
onClick(ChapterDownloadAction.CANCEL) DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
isMenuExpanded = false DropdownMenuItem(
}, text = { Text(text = stringResource(R.string.action_delete)) },
) onClick = {
} onClick(ChapterDownloadAction.DELETE)
} isMenuExpanded = false
},
)
} }
} else {
val inactiveAlphaModifier = if (!isDownloading) Modifier.secondaryItemAlpha() else Modifier
val arrowColor: Color
val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
if (isDownloading) {
val downloadProgress = downloadProgressProvider()
val indeterminate = downloadState == Download.State.QUEUE ||
(downloadState == Download.State.DOWNLOADING && downloadProgress == 0)
if (indeterminate) {
arrowColor = strokeColor
CircularProgressIndicator(
modifier = IndicatorModifier,
color = strokeColor,
strokeWidth = IndicatorStrokeWidth,
)
} else {
val animatedProgress by animateFloatAsState(
targetValue = downloadProgress / 100f,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
)
arrowColor = if (animatedProgress < 0.5f) {
strokeColor
} else {
MaterialTheme.colorScheme.background
}
CircularProgressIndicator(
progress = animatedProgress,
modifier = IndicatorModifier,
color = strokeColor,
strokeWidth = IndicatorSize / 2,
)
}
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_start_downloading_now)) },
onClick = {
onClick(ChapterDownloadAction.START_NOW)
isMenuExpanded = false
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_cancel)) },
onClick = {
onClick(ChapterDownloadAction.CANCEL)
isMenuExpanded = false
},
)
}
} else {
arrowColor = strokeColor
CircularProgressIndicator(
progress = 1f,
modifier = IndicatorModifier.then(inactiveAlphaModifier),
color = strokeColor,
strokeWidth = IndicatorStrokeWidth,
)
}
Icon(
imageVector = Icons.Default.ArrowDownward,
contentDescription = null,
modifier = ArrowModifier.then(inactiveAlphaModifier),
tint = arrowColor,
)
} }
} }
} }
@ -154,3 +159,9 @@ private val IndicatorPadding = 2.dp
// To match composable parameter name when used later // To match composable parameter name when used later
private val IndicatorStrokeWidth = IndicatorPadding private val IndicatorStrokeWidth = IndicatorPadding
private val IndicatorModifier = Modifier
.size(IndicatorSize)
.padding(IndicatorPadding)
private val ArrowModifier = Modifier
.size(IndicatorSize - 7.dp)

View file

@ -685,8 +685,8 @@ private fun LazyListScope.sharedChapterItems(
read = chapter.read, read = chapter.read,
bookmark = chapter.bookmark, bookmark = chapter.bookmark,
selected = selected.contains(chapterItem), selected = selected.contains(chapterItem),
downloadState = downloadState, downloadStateProvider = { downloadState },
downloadProgress = downloadProgress, downloadProgressProvider = { downloadProgress },
onLongClick = { onLongClick = {
val dispatched = onChapterItemLongClick( val dispatched = onChapterItemLongClick(
chapterItem = chapterItem, chapterItem = chapterItem,

View file

@ -44,8 +44,8 @@ fun MangaChapterListItem(
read: Boolean, read: Boolean,
bookmark: Boolean, bookmark: Boolean,
selected: Boolean, selected: Boolean,
downloadState: Download.State, downloadStateProvider: () -> Download.State,
downloadProgress: Int, downloadProgressProvider: () -> Int,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onDownloadClick: ((ChapterDownloadAction) -> Unit)?, onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
@ -127,8 +127,8 @@ fun MangaChapterListItem(
if (onDownloadClick != null) { if (onDownloadClick != null) {
ChapterDownloadIndicator( ChapterDownloadIndicator(
modifier = Modifier.padding(start = 4.dp), modifier = Modifier.padding(start = 4.dp),
downloadState = downloadState, downloadStateProvider = downloadStateProvider,
downloadProgress = downloadProgress, downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick, onClick = onDownloadClick,
) )
} }

View file

@ -27,8 +27,8 @@ class ChapterDownloadView @JvmOverloads constructor(
override fun Content() { override fun Content() {
TachiyomiTheme { TachiyomiTheme {
ChapterDownloadIndicator( ChapterDownloadIndicator(
downloadState = state, downloadStateProvider = { state },
downloadProgress = progress, downloadProgressProvider = { progress },
onClick = listener, onClick = listener,
) )
} }