mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-05 18:45:07 +01:00
ChapterDownloadIndicator: Optimize further and reimplement error state (#7599)
In the context of a weaker device--remembering objects inside a list item is expensive. So only do it when we really need to. This also flattens the download button by drawing a single icon instead of using separate icon and progress indicator.
This commit is contained in:
parent
6f94777530
commit
aeffb5eeb8
@ -9,6 +9,7 @@ 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.icons.filled.ErrorOutline
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
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
|
||||||
@ -23,7 +24,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -45,121 +48,186 @@ fun ChapterDownloadIndicator(
|
|||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
onClick: (ChapterDownloadAction) -> Unit,
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
) {
|
) {
|
||||||
val downloadState = downloadStateProvider()
|
when (val downloadState = downloadStateProvider()) {
|
||||||
val isDownloaded = downloadState == Download.State.DOWNLOADED
|
Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(modifier = modifier, onClick = onClick)
|
||||||
val isDownloading = downloadState != Download.State.NOT_DOWNLOADED
|
Download.State.QUEUE, Download.State.DOWNLOADING -> DownloadingIndicator(
|
||||||
var isMenuExpanded by remember(downloadState) { mutableStateOf(false) }
|
modifier = modifier,
|
||||||
|
downloadState = downloadState,
|
||||||
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
|
onClick = onClick,
|
||||||
|
)
|
||||||
|
Download.State.DOWNLOADED -> DownloadedIndicator(modifier = modifier, onClick = onClick)
|
||||||
|
Download.State.ERROR -> ErrorIndicator(modifier = modifier, onClick = onClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NotDownloadedIndicator(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.combinedClickable(
|
.commonClickable(
|
||||||
onLongClick = {
|
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
||||||
val chapterDownloadAction = when {
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
isDownloaded -> ChapterDownloadAction.DELETE
|
)
|
||||||
isDownloading -> ChapterDownloadAction.CANCEL
|
.secondaryItemAlpha(),
|
||||||
else -> ChapterDownloadAction.START_NOW
|
contentAlignment = Alignment.Center,
|
||||||
}
|
) {
|
||||||
onClick(chapterDownloadAction)
|
Icon(
|
||||||
},
|
painter = painterResource(id = R.drawable.ic_download_chapter_24dp),
|
||||||
onClick = {
|
contentDescription = null,
|
||||||
if (isDownloaded || isDownloading) {
|
modifier = Modifier.size(IndicatorSize),
|
||||||
isMenuExpanded = true
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
} else {
|
)
|
||||||
onClick(ChapterDownloadAction.START)
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
role = Role.Button,
|
@Composable
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
private fun DownloadingIndicator(
|
||||||
indication = rememberRipple(
|
modifier: Modifier = Modifier,
|
||||||
bounded = false,
|
downloadState: Download.State,
|
||||||
radius = IconButtonTokens.StateLayerSize / 2,
|
downloadProgressProvider: () -> Int,
|
||||||
),
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
|
) {
|
||||||
|
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
|
.commonClickable(
|
||||||
|
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
||||||
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
if (isDownloaded) {
|
val arrowColor: Color
|
||||||
Icon(
|
val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
imageVector = Icons.Default.CheckCircle,
|
val downloadProgress = downloadProgressProvider()
|
||||||
contentDescription = null,
|
val indeterminate = downloadState == Download.State.QUEUE ||
|
||||||
modifier = Modifier.size(IndicatorSize),
|
(downloadState == Download.State.DOWNLOADING && downloadProgress == 0)
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
if (indeterminate) {
|
||||||
|
arrowColor = strokeColor
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = IndicatorModifier,
|
||||||
|
color = strokeColor,
|
||||||
|
strokeWidth = IndicatorStrokeWidth,
|
||||||
)
|
)
|
||||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = { Text(text = stringResource(R.string.action_delete)) },
|
|
||||||
onClick = {
|
|
||||||
onClick(ChapterDownloadAction.DELETE)
|
|
||||||
isMenuExpanded = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val inactiveAlphaModifier = if (!isDownloading) Modifier.secondaryItemAlpha() else Modifier
|
val animatedProgress by animateFloatAsState(
|
||||||
val arrowColor: Color
|
targetValue = downloadProgress / 100f,
|
||||||
val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
|
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||||
if (isDownloading) {
|
)
|
||||||
val downloadProgress = downloadProgressProvider()
|
arrowColor = if (animatedProgress < 0.5f) {
|
||||||
val indeterminate = downloadState == Download.State.QUEUE ||
|
strokeColor
|
||||||
(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 {
|
} else {
|
||||||
arrowColor = strokeColor
|
MaterialTheme.colorScheme.background
|
||||||
CircularProgressIndicator(
|
|
||||||
progress = 1f,
|
|
||||||
modifier = IndicatorModifier.then(inactiveAlphaModifier),
|
|
||||||
color = strokeColor,
|
|
||||||
strokeWidth = IndicatorStrokeWidth,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Icon(
|
CircularProgressIndicator(
|
||||||
imageVector = Icons.Default.ArrowDownward,
|
progress = animatedProgress,
|
||||||
contentDescription = null,
|
modifier = IndicatorModifier,
|
||||||
modifier = ArrowModifier.then(inactiveAlphaModifier),
|
color = strokeColor,
|
||||||
tint = arrowColor,
|
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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowDownward,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = ArrowModifier,
|
||||||
|
tint = arrowColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DownloadedIndicator(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
|
) {
|
||||||
|
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
|
.commonClickable(
|
||||||
|
onLongClick = { onClick(ChapterDownloadAction.DELETE) },
|
||||||
|
onClick = { isMenuExpanded = true },
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ErrorIndicator(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: (ChapterDownloadAction) -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
|
.commonClickable(
|
||||||
|
onLongClick = { onClick(ChapterDownloadAction.START) },
|
||||||
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ErrorOutline,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(IndicatorSize),
|
||||||
|
tint = MaterialTheme.colorScheme.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.commonClickable(
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
) = composed {
|
||||||
|
this.combinedClickable(
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
onClick = onClick,
|
||||||
|
role = Role.Button,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = rememberRipple(
|
||||||
|
bounded = false,
|
||||||
|
radius = IconButtonTokens.StateLayerSize / 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val IndicatorSize = 26.dp
|
private val IndicatorSize = 26.dp
|
||||||
private val IndicatorPadding = 2.dp
|
private val IndicatorPadding = 2.dp
|
||||||
|
|
||||||
|
12
app/src/main/res/drawable/ic_download_chapter_24dp.xml
Normal file
12
app/src/main/res/drawable/ic_download_chapter_24dp.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M11.99,2C6.47,2 2,6.48 2,12C2,17.52 6.47,22 11.99,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 11.99,2zM12,4C16.42,4 20,7.58 20,12C20,16.42 16.42,20 12,20C7.58,20 4,16.42 4,12C4,7.58 7.58,4 12,4z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M18.041,12 L16.976,10.935 12.755,15.149L12.755,5.959L11.245,5.959L11.245,15.149L7.031,10.928 5.959,12l6.041,6.041z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
Loading…
Reference in New Issue
Block a user