mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2025-01-03 03:01:52 +01: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
@ -1,8 +1,9 @@
|
|||||||
package eu.kanade.presentation.browse
|
package eu.kanade.presentation.browse
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.combinedClickable
|
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.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
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.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import eu.kanade.presentation.browse.components.BaseBrowseItem
|
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.FastScrollLazyColumn
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
||||||
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
import eu.kanade.presentation.theme.header
|
import eu.kanade.presentation.theme.header
|
||||||
import eu.kanade.presentation.util.bottomNavPaddingValues
|
import eu.kanade.presentation.util.bottomNavPaddingValues
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
import eu.kanade.presentation.util.horizontalPadding
|
||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
|
import eu.kanade.presentation.util.secondaryItemAlpha
|
||||||
import eu.kanade.presentation.util.topPaddingValues
|
import eu.kanade.presentation.util.topPaddingValues
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
@ -117,9 +124,8 @@ private fun ExtensionContent(
|
|||||||
},
|
},
|
||||||
key = {
|
key = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is ExtensionUiModel.Header.Resource -> it.textRes
|
is ExtensionUiModel.Header -> "extensionHeader-${it.hashCode()}"
|
||||||
is ExtensionUiModel.Header.Text -> it.text
|
is ExtensionUiModel.Item -> "extension-${it.extension.hashCode()}"
|
||||||
is ExtensionUiModel.Item -> "extension-${it.key()}"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { item ->
|
) { item ->
|
||||||
@ -219,7 +225,27 @@ private fun ExtensionItem(
|
|||||||
onClickItem = { onClickItem(extension) },
|
onClickItem = { onClickItem(extension) },
|
||||||
onLongClickItem = { onLongClickItem(extension) },
|
onLongClickItem = { onLongClickItem(extension) },
|
||||||
icon = {
|
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 = {
|
action = {
|
||||||
ExtensionItemActions(
|
ExtensionItemActions(
|
||||||
@ -232,6 +258,7 @@ private fun ExtensionItem(
|
|||||||
) {
|
) {
|
||||||
ExtensionItemContent(
|
ExtensionItemContent(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
|
installStep = installStep,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -240,19 +267,9 @@ private fun ExtensionItem(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionItemContent(
|
private fun ExtensionItemContent(
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
|
installStep: InstallStep,
|
||||||
modifier: Modifier = Modifier,
|
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(
|
Column(
|
||||||
modifier = modifier.padding(start = horizontalPadding),
|
modifier = modifier.padding(start = horizontalPadding),
|
||||||
) {
|
) {
|
||||||
@ -262,32 +279,51 @@ private fun ExtensionItemContent(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
)
|
)
|
||||||
Row(
|
// Won't look good but it's not like we can ellipsize overflowing content
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
FlowRow(
|
||||||
|
modifier = Modifier.secondaryItemAlpha(),
|
||||||
|
mainAxisSpacing = 4.dp,
|
||||||
) {
|
) {
|
||||||
if (extension.lang.isNullOrEmpty().not()) {
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
Text(
|
if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
|
||||||
text = LocaleHelper.getSourceDisplayName(extension.lang, context),
|
Text(
|
||||||
style = MaterialTheme.typography.bodySmall,
|
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extension.versionName.isNotEmpty()) {
|
if (extension.versionName.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = extension.versionName,
|
text = extension.versionName,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (warning != null) {
|
val warning = when {
|
||||||
Text(
|
extension is Extension.Untrusted -> R.string.ext_untrusted
|
||||||
text = stringResource(warning).uppercase(),
|
extension is Extension.Installed && extension.isUnofficial -> R.string.ext_unofficial
|
||||||
maxLines = 1,
|
extension is Extension.Installed && extension.isObsolete -> R.string.ext_obsolete
|
||||||
overflow = TextOverflow.Ellipsis,
|
extension.isNsfw -> R.string.ext_nsfw_short
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
else -> null
|
||||||
|
}
|
||||||
|
if (warning != null) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(warning).uppercase(),
|
||||||
color = MaterialTheme.colorScheme.error,
|
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 = {},
|
onClickItemCancel: (Extension) -> Unit = {},
|
||||||
onClickItemAction: (Extension) -> Unit = {},
|
onClickItemAction: (Extension) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val isIdle = remember(installStep) {
|
val isIdle = installStep.isCompleted()
|
||||||
installStep == InstallStep.Idle || installStep == InstallStep.Error
|
|
||||||
}
|
|
||||||
Row(modifier = modifier) {
|
Row(modifier = modifier) {
|
||||||
TextButton(
|
if (isIdle) {
|
||||||
onClick = { onClickItemAction(extension) },
|
TextButton(
|
||||||
enabled = isIdle,
|
onClick = { onClickItemAction(extension) },
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = when (installStep) {
|
text = when (installStep) {
|
||||||
InstallStep.Pending -> stringResource(R.string.ext_pending)
|
InstallStep.Installed -> stringResource(R.string.ext_installed)
|
||||||
InstallStep.Downloading -> stringResource(R.string.ext_downloading)
|
InstallStep.Error -> stringResource(R.string.action_retry)
|
||||||
InstallStep.Installing -> stringResource(R.string.ext_installing)
|
InstallStep.Idle -> {
|
||||||
InstallStep.Installed -> stringResource(R.string.ext_installed)
|
when (extension) {
|
||||||
InstallStep.Error -> stringResource(R.string.action_retry)
|
is Extension.Installed -> {
|
||||||
InstallStep.Idle -> {
|
if (extension.hasUpdate) {
|
||||||
when (extension) {
|
stringResource(R.string.ext_update)
|
||||||
is Extension.Installed -> {
|
} else {
|
||||||
if (extension.hasUpdate) {
|
stringResource(R.string.action_settings)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
else -> error("Must not show install process text")
|
||||||
},
|
},
|
||||||
style = LocalTextStyle.current.copy(
|
)
|
||||||
color = if (isIdle) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceTint,
|
}
|
||||||
),
|
} else {
|
||||||
)
|
|
||||||
}
|
|
||||||
if (isIdle.not()) {
|
|
||||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
contentDescription = "",
|
contentDescription = stringResource(id = R.string.action_cancel),
|
||||||
tint = MaterialTheme.colorScheme.onBackground,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,12 +81,11 @@ fun ExtensionIcon(
|
|||||||
is Extension.Available -> {
|
is Extension.Available -> {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = extension.iconUrl,
|
model = extension.iconUrl,
|
||||||
contentDescription = "",
|
contentDescription = null,
|
||||||
placeholder = ColorPainter(Color(0x1F888888)),
|
placeholder = ColorPainter(Color(0x1F888888)),
|
||||||
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
error = rememberResourceBitmapPainter(id = R.drawable.cover_error),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp)),
|
||||||
.then(defaultModifier),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Extension.Installed -> {
|
is Extension.Installed -> {
|
||||||
@ -94,20 +93,20 @@ fun ExtensionIcon(
|
|||||||
when (icon) {
|
when (icon) {
|
||||||
Result.Error -> Image(
|
Result.Error -> Image(
|
||||||
bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_local_source),
|
bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_local_source),
|
||||||
contentDescription = "",
|
contentDescription = null,
|
||||||
modifier = modifier.then(defaultModifier),
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
Result.Loading -> Box(modifier = modifier.then(defaultModifier))
|
Result.Loading -> Box(modifier = modifier)
|
||||||
is Result.Success -> Image(
|
is Result.Success -> Image(
|
||||||
bitmap = (icon as Result.Success<ImageBitmap>).value,
|
bitmap = (icon as Result.Success<ImageBitmap>).value,
|
||||||
contentDescription = "",
|
contentDescription = null,
|
||||||
modifier = modifier.then(defaultModifier),
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Extension.Untrusted -> Image(
|
is Extension.Untrusted -> Image(
|
||||||
imageVector = Icons.Default.Dangerous,
|
imageVector = Icons.Default.Dangerous,
|
||||||
contentDescription = "",
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
|
||||||
modifier = modifier.then(defaultModifier),
|
modifier = modifier.then(defaultModifier),
|
||||||
)
|
)
|
||||||
|
@ -7,3 +7,8 @@ import androidx.compose.runtime.Composable
|
|||||||
fun DotSeparatorText() {
|
fun DotSeparatorText() {
|
||||||
Text(text = " • ")
|
Text(text = " • ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DotSeparatorNoSpaceText() {
|
||||||
|
Text(text = "•")
|
||||||
|
}
|
||||||
|
@ -212,13 +212,5 @@ sealed interface ExtensionUiModel {
|
|||||||
data class Item(
|
data class Item(
|
||||||
val extension: Extension,
|
val extension: Extension,
|
||||||
val installStep: InstallStep,
|
val installStep: InstallStep,
|
||||||
) : ExtensionUiModel {
|
) : ExtensionUiModel
|
||||||
|
|
||||||
fun key(): String {
|
|
||||||
return when {
|
|
||||||
extension is Extension.Installed && extension.hasUpdate -> "${extension.pkgName}_update"
|
|
||||||
else -> "${extension.pkgName}_${installStep.name}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user