mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
ExtensionScreen: Adjust item visual (#8120)
* ExtensionScreen: Adjust item visual * Move install status view and add progress indicator * Add secondary item modifier to info texts * Wrap info texts with FlowRow in case of unavailable space * Remove language text in non-installed items Extra content: * Change the list key to be more consistent * General cleanups * typo
This commit is contained in:
parent
80b2ebc45b
commit
58c47c4c50
4 changed files with 113 additions and 89 deletions
|
@ -1,8 +1,9 @@
|
|||
package eu.kanade.presentation.browse
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
|
@ -10,15 +11,18 @@ import androidx.compose.foundation.layout.WindowInsets
|
|||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -32,6 +36,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
||||
|
@ -40,10 +45,12 @@ import eu.kanade.presentation.components.EmptyScreen
|
|||
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||
import eu.kanade.presentation.components.LoadingScreen
|
||||
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||
import eu.kanade.presentation.theme.header
|
||||
import eu.kanade.presentation.util.bottomNavPaddingValues
|
||||
import eu.kanade.presentation.util.horizontalPadding
|
||||
import eu.kanade.presentation.util.plus
|
||||
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||
import eu.kanade.presentation.util.topPaddingValues
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
|
@ -117,9 +124,8 @@ private fun ExtensionContent(
|
|||
},
|
||||
key = {
|
||||
when (it) {
|
||||
is ExtensionUiModel.Header.Resource -> it.textRes
|
||||
is ExtensionUiModel.Header.Text -> it.text
|
||||
is ExtensionUiModel.Item -> "extension-${it.key()}"
|
||||
is ExtensionUiModel.Header -> "extensionHeader-${it.hashCode()}"
|
||||
is ExtensionUiModel.Item -> "extension-${it.extension.hashCode()}"
|
||||
}
|
||||
},
|
||||
) { item ->
|
||||
|
@ -219,7 +225,27 @@ private fun ExtensionItem(
|
|||
onClickItem = { onClickItem(extension) },
|
||||
onLongClickItem = { onLongClickItem(extension) },
|
||||
icon = {
|
||||
ExtensionIcon(extension = extension)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val idle = installStep.isCompleted()
|
||||
if (!idle) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(40.dp),
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
}
|
||||
|
||||
val padding by animateDpAsState(targetValue = if (idle) 0.dp else 8.dp)
|
||||
ExtensionIcon(
|
||||
extension = extension,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.padding(padding),
|
||||
)
|
||||
}
|
||||
},
|
||||
action = {
|
||||
ExtensionItemActions(
|
||||
|
@ -232,6 +258,7 @@ private fun ExtensionItem(
|
|||
) {
|
||||
ExtensionItemContent(
|
||||
extension = extension,
|
||||
installStep = installStep,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
@ -240,19 +267,9 @@ private fun ExtensionItem(
|
|||
@Composable
|
||||
private fun ExtensionItemContent(
|
||||
extension: Extension,
|
||||
installStep: InstallStep,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val warning = remember(extension) {
|
||||
when {
|
||||
extension is Extension.Untrusted -> R.string.ext_untrusted
|
||||
extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
|
||||
extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
|
||||
extension.isNsfw -> R.string.ext_nsfw_short
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.padding(start = horizontalPadding),
|
||||
) {
|
||||
|
@ -262,32 +279,51 @@ private fun ExtensionItemContent(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
// Won't look good but it's not like we can ellipsize overflowing content
|
||||
FlowRow(
|
||||
modifier = Modifier.secondaryItemAlpha(),
|
||||
mainAxisSpacing = 4.dp,
|
||||
) {
|
||||
if (extension.lang.isNullOrEmpty().not()) {
|
||||
Text(
|
||||
text = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||
Text(
|
||||
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||
)
|
||||
}
|
||||
|
||||
if (extension.versionName.isNotEmpty()) {
|
||||
Text(
|
||||
text = extension.versionName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
if (extension.versionName.isNotEmpty()) {
|
||||
Text(
|
||||
text = extension.versionName,
|
||||
)
|
||||
}
|
||||
|
||||
if (warning != null) {
|
||||
Text(
|
||||
text = stringResource(warning).uppercase(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
val warning = when {
|
||||
extension is Extension.Untrusted -> R.string.ext_untrusted
|
||||
extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
|
||||
extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
|
||||
extension.isNsfw -> R.string.ext_nsfw_short
|
||||
else -> null
|
||||
}
|
||||
if (warning != null) {
|
||||
Text(
|
||||
text = stringResource(warning).uppercase(),
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
),
|
||||
)
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
if (!installStep.isCompleted()) {
|
||||
DotSeparatorNoSpaceText()
|
||||
Text(
|
||||
text = when (installStep) {
|
||||
InstallStep.Pending -> stringResource(R.string.ext_pending)
|
||||
InstallStep.Downloading -> stringResource(R.string.ext_downloading)
|
||||
InstallStep.Installing -> stringResource(R.string.ext_installing)
|
||||
else -> error("Must not show non-install process text")
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -301,46 +337,38 @@ private fun ExtensionItemActions(
|
|||
onClickItemCancel: (Extension) -> Unit = {},
|
||||
onClickItemAction: (Extension) -> Unit = {},
|
||||
) {
|
||||
val isIdle = remember(installStep) {
|
||||
installStep == InstallStep.Idle || installStep == InstallStep.Error
|
||||
}
|
||||
val isIdle = installStep.isCompleted()
|
||||
Row(modifier = modifier) {
|
||||
TextButton(
|
||||
onClick = { onClickItemAction(extension) },
|
||||
enabled = isIdle,
|
||||
) {
|
||||
Text(
|
||||
text = when (installStep) {
|
||||
InstallStep.Pending -> stringResource(R.string.ext_pending)
|
||||
InstallStep.Downloading -> stringResource(R.string.ext_downloading)
|
||||
InstallStep.Installing -> stringResource(R.string.ext_installing)
|
||||
InstallStep.Installed -> stringResource(R.string.ext_installed)
|
||||
InstallStep.Error -> stringResource(R.string.action_retry)
|
||||
InstallStep.Idle -> {
|
||||
when (extension) {
|
||||
is Extension.Installed -> {
|
||||
if (extension.hasUpdate) {
|
||||
stringResource(R.string.ext_update)
|
||||
} else {
|
||||
stringResource(R.string.action_settings)
|
||||
if (isIdle) {
|
||||
TextButton(
|
||||
onClick = { onClickItemAction(extension) },
|
||||
) {
|
||||
Text(
|
||||
text = when (installStep) {
|
||||
InstallStep.Installed -> stringResource(R.string.ext_installed)
|
||||
InstallStep.Error -> stringResource(R.string.action_retry)
|
||||
InstallStep.Idle -> {
|
||||
when (extension) {
|
||||
is Extension.Installed -> {
|
||||
if (extension.hasUpdate) {
|
||||
stringResource(R.string.ext_update)
|
||||
} else {
|
||||
stringResource(R.string.action_settings)
|
||||
}
|
||||
}
|
||||
is Extension.Untrusted -> stringResource(R.string.ext_trust)
|
||||
is Extension.Available -> stringResource(R.string.ext_install)
|
||||
}
|
||||
is Extension.Untrusted -> stringResource(R.string.ext_trust)
|
||||
is Extension.Available -> stringResource(R.string.ext_install)
|
||||
}
|
||||
}
|
||||
},
|
||||
style = LocalTextStyle.current.copy(
|
||||
color = if (isIdle) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceTint,
|
||||
),
|
||||
)
|
||||
}
|
||||
if (isIdle.not()) {
|
||||
else -> error("Must not show install process text")
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "",
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
contentDescription = stringResource(id = R.string.action_cancel),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,12 +81,11 @@ fun ExtensionIcon(
|
|||
is Extension.Available -> {
|
||||
AsyncImage(
|
||||
model = extension.iconUrl,
|
||||
contentDescription = "",
|
||||
contentDescription = null,
|
||||
placeholder = ColorPainter(Color(0x1F888888)),
|
||||
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.then(defaultModifier),
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
)
|
||||
}
|
||||
is Extension.Installed -> {
|
||||
|
@ -94,20 +93,20 @@ fun ExtensionIcon(
|
|||
when (icon) {
|
||||
Result.Error -> Image(
|
||||
bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_local_source),
|
||||
contentDescription = "",
|
||||
modifier = modifier.then(defaultModifier),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
)
|
||||
Result.Loading -> Box(modifier = modifier.then(defaultModifier))
|
||||
Result.Loading -> Box(modifier = modifier)
|
||||
is Result.Success -> Image(
|
||||
bitmap = (icon as Result.Success<ImageBitmap>).value,
|
||||
contentDescription = "",
|
||||
modifier = modifier.then(defaultModifier),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
is Extension.Untrusted -> Image(
|
||||
imageVector = Icons.Default.Dangerous,
|
||||
contentDescription = "",
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||
modifier = modifier.then(defaultModifier),
|
||||
)
|
||||
|
|
|
@ -7,3 +7,8 @@ import androidx.compose.runtime.Composable
|
|||
fun DotSeparatorText() {
|
||||
Text(text = " • ")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DotSeparatorNoSpaceText() {
|
||||
Text(text = "•")
|
||||
}
|
||||
|
|
|
@ -212,13 +212,5 @@ sealed interface ExtensionUiModel {
|
|||
data class Item(
|
||||
val extension: Extension,
|
||||
val installStep: InstallStep,
|
||||
) : ExtensionUiModel {
|
||||
|
||||
fun key(): String {
|
||||
return when {
|
||||
extension is Extension.Installed && extension.hasUpdate -> "${extension.pkgName}_update"
|
||||
else -> "${extension.pkgName}_${installStep.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
) : ExtensionUiModel
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue