Update extension details screen design (#7158)

* Update extension details screen design

* Review Changes

Co-Authored-By: Andreas <6576096+ghostbear@users.noreply.github.com>

* Review Changes 2

Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
This commit is contained in:
FourTOne5 2022-05-20 03:31:07 +06:00 committed by GitHub
parent fd9510e18f
commit 64da16f58f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 157 additions and 39 deletions

View File

@ -1,7 +1,11 @@
package eu.kanade.presentation.browse package eu.kanade.presentation.browse
import android.util.DisplayMetrics
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box 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
@ -12,27 +16,35 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
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.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
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.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.browse.components.ExtensionIcon import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.Divider import eu.kanade.presentation.components.Divider
@ -64,6 +76,8 @@ fun ExtensionDetailsScreen(
val sources by presenter.sourcesState.collectAsState() val sources by presenter.sourcesState.collectAsState()
val (showNsfwWarning, setShowNsfwWarning) = remember { mutableStateOf(false) }
LazyColumn( LazyColumn(
modifier = Modifier.nestedScroll(nestedScrollInterop), modifier = Modifier.nestedScroll(nestedScrollInterop),
contentPadding = WindowInsets.navigationBars.asPaddingValues(), contentPadding = WindowInsets.navigationBars.asPaddingValues(),
@ -80,7 +94,14 @@ fun ExtensionDetailsScreen(
} }
item { item {
DetailsHeader(extension, onClickUninstall, onClickAppInfo) DetailsHeader(
extension = extension,
onClickUninstall = onClickUninstall,
onClickAppInfo = onClickAppInfo,
onClickAgeRating = {
setShowNsfwWarning(true)
},
)
} }
items( items(
@ -95,6 +116,13 @@ fun ExtensionDetailsScreen(
) )
} }
} }
if (showNsfwWarning) {
NsfwWarningDialog(
onClickConfirm = {
setShowNsfwWarning(false)
},
)
}
} }
@Composable @Composable
@ -116,52 +144,77 @@ private fun WarningBanner(@StringRes textRes: Int) {
@Composable @Composable
private fun DetailsHeader( private fun DetailsHeader(
extension: Extension, extension: Extension,
onClickAgeRating: () -> Unit,
onClickUninstall: () -> Unit, onClickUninstall: () -> Unit,
onClickAppInfo: () -> Unit, onClickAppInfo: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column { Column {
Row( Column(
modifier = Modifier.padding( modifier = Modifier
start = horizontalPadding, .fillMaxWidth()
end = horizontalPadding, .padding(
top = 16.dp, start = horizontalPadding,
bottom = 8.dp, end = horizontalPadding,
), top = 16.dp,
bottom = 8.dp,
),
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
ExtensionIcon( ExtensionIcon(
modifier = Modifier modifier = Modifier
.height(56.dp) .size(112.dp),
.width(56.dp),
extension = extension, extension = extension,
density = DisplayMetrics.DENSITY_XXXHIGH,
) )
Column( Text(
modifier = Modifier.padding(start = 16.dp), text = extension.name,
) { style = MaterialTheme.typography.headlineSmall,
Text( )
text = extension.name,
style = MaterialTheme.typography.titleMedium, val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
)
Text( Text(
text = stringResource(R.string.ext_version_info, extension.versionName), text = strippedPkgName,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
) )
Text( }
text = stringResource(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context)),
style = MaterialTheme.typography.bodySmall, Row(
) modifier = Modifier
if (extension.isNsfw) { .fillMaxWidth()
Text( .padding(
text = stringResource(R.string.ext_nsfw_warning), horizontal = horizontalPadding * 2,
vertical = 8.dp,
),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
InfoText(
primaryText = extension.versionName,
secondaryText = stringResource(R.string.ext_info_version),
)
InfoDivider()
InfoText(
primaryText = LocaleHelper.getSourceDisplayName(extension.lang, context),
secondaryText = stringResource(R.string.ext_info_language),
)
if (extension.isNsfw) {
InfoDivider()
InfoText(
primaryText = stringResource(R.string.ext_nsfw_short),
primaryTextStyle = MaterialTheme.typography.bodyLarge.copy(
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Medium,
) ),
} secondaryText = stringResource(R.string.ext_info_age_rating),
Text( onCLick = onClickAgeRating,
text = extension.pkgName,
style = MaterialTheme.typography.bodySmall,
) )
} }
} }
@ -198,6 +251,47 @@ private fun DetailsHeader(
} }
} }
@Composable
private fun InfoText(
primaryText: String,
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
secondaryText: String,
onCLick: (() -> Unit)? = null,
) {
val interactionSource = remember { MutableInteractionSource() }
val modifier = if (onCLick != null) {
Modifier.clickable(interactionSource, indication = null) { onCLick() }
} else Modifier
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(
text = primaryText,
style = primaryTextStyle,
)
Text(
text = secondaryText + if (onCLick != null) "" else "",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5F),
)
}
}
@Composable
private fun InfoDivider() {
Divider(
modifier = Modifier
.height(20.dp)
.width(1.dp),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5F),
)
}
@Composable @Composable
private fun SourceSwitchPreference( private fun SourceSwitchPreference(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -234,3 +328,20 @@ private fun SourceSwitchPreference(
}, },
) )
} }
@Composable
fun NsfwWarningDialog(
onClickConfirm: () -> Unit,
) {
AlertDialog(
text = {
Text(text = stringResource(id = R.string.ext_nsfw_warning))
},
confirmButton = {
TextButton(onClick = onClickConfirm) {
Text(text = stringResource(id = R.string.ext_nsfw_warning_dismiss))
}
},
onDismissRequest = onClickConfirm,
)
}

View File

@ -1,5 +1,7 @@
package eu.kanade.presentation.browse.components package eu.kanade.presentation.browse.components
import android.content.pm.PackageManager
import android.util.DisplayMetrics
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
@ -57,6 +59,7 @@ fun SourceIcon(
fun ExtensionIcon( fun ExtensionIcon(
extension: Extension, extension: Extension,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
density: Int = DisplayMetrics.DENSITY_DEFAULT,
) { ) {
when (extension) { when (extension) {
is Extension.Available -> { is Extension.Available -> {
@ -71,7 +74,7 @@ fun ExtensionIcon(
) )
} }
is Extension.Installed -> { is Extension.Installed -> {
val icon by extension.getIcon() val icon by extension.getIcon(density)
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),
@ -95,13 +98,15 @@ fun ExtensionIcon(
} }
@Composable @Composable
private fun Extension.getIcon(): State<Result<ImageBitmap>> { private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): State<Result<ImageBitmap>> {
val context = LocalContext.current val context = LocalContext.current
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) { return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
withIOContext { withIOContext {
value = try { value = try {
val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
val appResources = context.packageManager.getResourcesForApplication(appInfo)
Result.Success( Result.Success(
context.packageManager.getApplicationIcon(pkgName) appResources.getDrawableForDensity(appInfo.icon, density, null)!!
.toBitmap() .toBitmap()
.asImageBitmap(), .asImageBitmap(),
) )

View File

@ -269,10 +269,12 @@
<string name="obsolete_extension_message">This extension is no longer available.</string> <string name="obsolete_extension_message">This extension is no longer available.</string>
<string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string> <string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string>
<string name="extension_api_error">Failed to get extensions list</string> <string name="extension_api_error">Failed to get extensions list</string>
<string name="ext_version_info">Version: %1$s</string> <string name="ext_info_version">Version</string>
<string name="ext_language_info">Language: %1$s</string> <string name="ext_info_language">Language</string>
<string name="ext_info_age_rating">Age rating</string>
<string name="ext_nsfw_short">18+</string> <string name="ext_nsfw_short">18+</string>
<string name="ext_nsfw_warning">May contain NSFW (18+) content</string> <string name="ext_nsfw_warning">May contain NSFW (18+) content</string>
<string name="ext_nsfw_warning_dismiss">Got it</string>
<string name="ext_install_service_notif">Installing extension…</string> <string name="ext_install_service_notif">Installing extension…</string>
<string name="ext_installer_pref">Installer</string> <string name="ext_installer_pref">Installer</string>
<string name="ext_installer_legacy">Legacy</string> <string name="ext_installer_legacy">Legacy</string>