mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	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:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,8 @@ class ChapterDownloadView @JvmOverloads constructor(
 | 
			
		||||
    override fun Content() {
 | 
			
		||||
        TachiyomiTheme {
 | 
			
		||||
            ChapterDownloadIndicator(
 | 
			
		||||
                downloadState = state,
 | 
			
		||||
                downloadProgress = progress,
 | 
			
		||||
                downloadStateProvider = { state },
 | 
			
		||||
                downloadProgressProvider = { progress },
 | 
			
		||||
                onClick = listener,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user