mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 14:27:57 +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:
		| @@ -9,6 +9,7 @@ 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.icons.filled.ErrorOutline | ||||
| import androidx.compose.material.ripple.rememberRipple | ||||
| import androidx.compose.material3.CircularProgressIndicator | ||||
| import androidx.compose.material3.DropdownMenuItem | ||||
| @@ -23,7 +24,9 @@ import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.composed | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.res.painterResource | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.semantics.Role | ||||
| import androidx.compose.ui.unit.dp | ||||
| @@ -45,121 +48,186 @@ fun ChapterDownloadIndicator( | ||||
|     downloadProgressProvider: () -> Int, | ||||
|     onClick: (ChapterDownloadAction) -> Unit, | ||||
| ) { | ||||
|     val downloadState = downloadStateProvider() | ||||
|     val isDownloaded = downloadState == Download.State.DOWNLOADED | ||||
|     val isDownloading = downloadState != Download.State.NOT_DOWNLOADED | ||||
|     var isMenuExpanded by remember(downloadState) { mutableStateOf(false) } | ||||
|     when (val downloadState = downloadStateProvider()) { | ||||
|         Download.State.NOT_DOWNLOADED -> NotDownloadedIndicator(modifier = modifier, onClick = onClick) | ||||
|         Download.State.QUEUE, Download.State.DOWNLOADING -> DownloadingIndicator( | ||||
|             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( | ||||
|         modifier = modifier | ||||
|             .size(IconButtonTokens.StateLayerSize) | ||||
|             .combinedClickable( | ||||
|                 onLongClick = { | ||||
|                     val chapterDownloadAction = when { | ||||
|                         isDownloaded -> ChapterDownloadAction.DELETE | ||||
|                         isDownloading -> ChapterDownloadAction.CANCEL | ||||
|                         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, | ||||
|                 ), | ||||
|             .commonClickable( | ||||
|                 onLongClick = { onClick(ChapterDownloadAction.START_NOW) }, | ||||
|                 onClick = { onClick(ChapterDownloadAction.START) }, | ||||
|             ) | ||||
|             .secondaryItemAlpha(), | ||||
|         contentAlignment = Alignment.Center, | ||||
|     ) { | ||||
|         Icon( | ||||
|             painter = painterResource(id = R.drawable.ic_download_chapter_24dp), | ||||
|             contentDescription = null, | ||||
|             modifier = Modifier.size(IndicatorSize), | ||||
|             tint = MaterialTheme.colorScheme.onSurfaceVariant, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @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, | ||||
|     ) { | ||||
|         if (isDownloaded) { | ||||
|             Icon( | ||||
|                 imageVector = Icons.Default.CheckCircle, | ||||
|                 contentDescription = null, | ||||
|                 modifier = Modifier.size(IndicatorSize), | ||||
|                 tint = MaterialTheme.colorScheme.onSurfaceVariant, | ||||
|         val arrowColor: Color | ||||
|         val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant | ||||
|         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, | ||||
|             ) | ||||
|             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 | ||||
|                         }, | ||||
|                     ) | ||||
|                 } | ||||
|             val animatedProgress by animateFloatAsState( | ||||
|                 targetValue = downloadProgress / 100f, | ||||
|                 animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, | ||||
|             ) | ||||
|             arrowColor = if (animatedProgress < 0.5f) { | ||||
|                 strokeColor | ||||
|             } else { | ||||
|                 arrowColor = strokeColor | ||||
|                 CircularProgressIndicator( | ||||
|                     progress = 1f, | ||||
|                     modifier = IndicatorModifier.then(inactiveAlphaModifier), | ||||
|                     color = strokeColor, | ||||
|                     strokeWidth = IndicatorStrokeWidth, | ||||
|                 ) | ||||
|                 MaterialTheme.colorScheme.background | ||||
|             } | ||||
|             Icon( | ||||
|                 imageVector = Icons.Default.ArrowDownward, | ||||
|                 contentDescription = null, | ||||
|                 modifier = ArrowModifier.then(inactiveAlphaModifier), | ||||
|                 tint = arrowColor, | ||||
|             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 | ||||
|                 }, | ||||
|             ) | ||||
|         } | ||||
|         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 IndicatorPadding = 2.dp | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user