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:
Ivan Iskandar 2022-07-24 21:27:00 +07:00 committed by GitHub
parent 6f94777530
commit aeffb5eeb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 180 additions and 100 deletions

View File

@ -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,59 +48,62 @@ 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
}
onClick(chapterDownloadAction)
},
onClick = {
if (isDownloaded || isDownloading) {
isMenuExpanded = true
} else {
onClick(ChapterDownloadAction.START)
}
},
role = Role.Button,
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(
bounded = false,
radius = IconButtonTokens.StateLayerSize / 2,
),
),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
if (isDownloaded) {
Icon( Icon(
imageVector = Icons.Default.CheckCircle, painter = painterResource(id = R.drawable.ic_download_chapter_24dp),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(IndicatorSize), modifier = Modifier.size(IndicatorSize),
tint = MaterialTheme.colorScheme.onSurfaceVariant, 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
@Composable
private fun DownloadingIndicator(
modifier: Modifier = Modifier,
downloadState: Download.State,
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,
) {
val arrowColor: Color val arrowColor: Color
val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
if (isDownloading) {
val downloadProgress = downloadProgressProvider() val downloadProgress = downloadProgressProvider()
val indeterminate = downloadState == Download.State.QUEUE || val indeterminate = downloadState == Download.State.QUEUE ||
(downloadState == Download.State.DOWNLOADING && downloadProgress == 0) (downloadState == Download.State.DOWNLOADING && downloadProgress == 0)
@ -141,23 +147,85 @@ fun ChapterDownloadIndicator(
}, },
) )
} }
} else {
arrowColor = strokeColor
CircularProgressIndicator(
progress = 1f,
modifier = IndicatorModifier.then(inactiveAlphaModifier),
color = strokeColor,
strokeWidth = IndicatorStrokeWidth,
)
}
Icon( Icon(
imageVector = Icons.Default.ArrowDownward, imageVector = Icons.Default.ArrowDownward,
contentDescription = null, contentDescription = null,
modifier = ArrowModifier.then(inactiveAlphaModifier), modifier = ArrowModifier,
tint = arrowColor, 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

View 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>