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
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -24,6 +25,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.manga.ChapterDownloadAction
import eu.kanade.presentation.util.secondaryItemAlpha
@ -33,23 +35,18 @@ import eu.kanade.tachiyomi.data.download.model.Download
@Composable
fun ChapterDownloadIndicator(
modifier: Modifier = Modifier,
downloadState: Download.State,
downloadProgress: Int,
downloadStateProvider: () -> Download.State,
downloadProgressProvider: () -> Int,
onClick: (ChapterDownloadAction) -> Unit,
) {
Box(modifier = modifier, contentAlignment = Alignment.Center) {
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
val isDownloaded = downloadState == Download.State.DOWNLOADED
val isDownloading = downloadState != Download.State.NOT_DOWNLOADED
var isMenuExpanded by remember(downloadState) { mutableStateOf(false) }
IconButton(
onClick = {
if (isDownloaded || isDownloading) {
isMenuExpanded = true
} else {
onClick(ChapterDownloadAction.START)
}
},
val downloadState = downloadStateProvider()
val isDownloaded = downloadState == Download.State.DOWNLOADED
val isDownloading = downloadState != Download.State.NOT_DOWNLOADED
var isMenuExpanded by remember(downloadState) { mutableStateOf(false) }
Box(
modifier = modifier
.size(IconButtonTokens.StateLayerSize)
.combinedClickable(
onLongClick = {
val chapterDownloadAction = when {
isDownloaded -> ChapterDownloadAction.DELETE
@ -58,93 +55,101 @@ fun ChapterDownloadIndicator(
}
onClick(chapterDownloadAction)
},
) {
val indicatorModifier = Modifier
.size(IndicatorSize)
.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,
)
}
onClick = {
if (isDownloaded || isDownloading) {
isMenuExpanded = true
} else {
arrowColor = strokeColor
CircularProgressIndicator(
progress = 1f,
modifier = indicatorModifier.then(inactiveAlphaModifier),
color = strokeColor,
strokeWidth = IndicatorStrokeWidth,
)
onClick(ChapterDownloadAction.START)
}
Icon(
imageVector = Icons.Default.ArrowDownward,
contentDescription = null,
modifier = arrowModifier,
tint = arrowColor,
)
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
},
)
}
}
},
role = Role.Button,
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(
bounded = false,
radius = IconButtonTokens.StateLayerSize / 2,
),
),
contentAlignment = Alignment.Center,
) {
if (isDownloaded) {
Icon(
imageVector = Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.size(IndicatorSize),
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 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
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,
bookmark = chapter.bookmark,
selected = selected.contains(chapterItem),
downloadState = downloadState,
downloadProgress = downloadProgress,
downloadStateProvider = { downloadState },
downloadProgressProvider = { downloadProgress },
onLongClick = {
val dispatched = onChapterItemLongClick(
chapterItem = chapterItem,

View file

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

View file

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