mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-14 22:35:22 +01:00
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:
parent
fd9510e18f
commit
64da16f58f
@ -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
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
start = horizontalPadding,
|
start = horizontalPadding,
|
||||||
end = horizontalPadding,
|
end = horizontalPadding,
|
||||||
top = 16.dp,
|
top = 16.dp,
|
||||||
bottom = 8.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(
|
|
||||||
modifier = Modifier.padding(start = 16.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
text = extension.name,
|
text = extension.name,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.ext_language_info, LocaleHelper.getSourceDisplayName(extension.lang, context)),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
)
|
|
||||||
if (extension.isNsfw) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.ext_nsfw_warning),
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = extension.pkgName,
|
Row(
|
||||||
style = MaterialTheme.typography.bodySmall,
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
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,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
),
|
||||||
|
secondaryText = stringResource(R.string.ext_info_age_rating),
|
||||||
|
onCLick = onClickAgeRating,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user