diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt index c18d573e6c..ad9a35f37d 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt @@ -1,7 +1,11 @@ package eu.kanade.presentation.browse +import android.util.DisplayMetrics import androidx.annotation.StringRes 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.Column 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.navigationBars import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext 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 eu.kanade.presentation.browse.components.ExtensionIcon import eu.kanade.presentation.components.Divider @@ -64,6 +76,8 @@ fun ExtensionDetailsScreen( val sources by presenter.sourcesState.collectAsState() + val (showNsfwWarning, setShowNsfwWarning) = remember { mutableStateOf(false) } + LazyColumn( modifier = Modifier.nestedScroll(nestedScrollInterop), contentPadding = WindowInsets.navigationBars.asPaddingValues(), @@ -80,7 +94,14 @@ fun ExtensionDetailsScreen( } item { - DetailsHeader(extension, onClickUninstall, onClickAppInfo) + DetailsHeader( + extension = extension, + onClickUninstall = onClickUninstall, + onClickAppInfo = onClickAppInfo, + onClickAgeRating = { + setShowNsfwWarning(true) + }, + ) } items( @@ -95,6 +116,13 @@ fun ExtensionDetailsScreen( ) } } + if (showNsfwWarning) { + NsfwWarningDialog( + onClickConfirm = { + setShowNsfwWarning(false) + }, + ) + } } @Composable @@ -116,52 +144,77 @@ private fun WarningBanner(@StringRes textRes: Int) { @Composable private fun DetailsHeader( extension: Extension, + onClickAgeRating: () -> Unit, onClickUninstall: () -> Unit, onClickAppInfo: () -> Unit, ) { val context = LocalContext.current Column { - Row( - modifier = Modifier.padding( - start = horizontalPadding, - end = horizontalPadding, - top = 16.dp, - bottom = 8.dp, - ), + Column( + modifier = Modifier + .fillMaxWidth() + .padding( + start = horizontalPadding, + end = horizontalPadding, + top = 16.dp, + bottom = 8.dp, + ), + horizontalAlignment = Alignment.CenterHorizontally, ) { ExtensionIcon( modifier = Modifier - .height(56.dp) - .width(56.dp), + .size(112.dp), extension = extension, + density = DisplayMetrics.DENSITY_XXXHIGH, ) - Column( - modifier = Modifier.padding(start = 16.dp), - ) { - Text( - text = extension.name, - style = MaterialTheme.typography.titleMedium, - ) - Text( - text = stringResource(R.string.ext_version_info, extension.versionName), - 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), + Text( + text = extension.name, + style = MaterialTheme.typography.headlineSmall, + ) + + val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.") + + Text( + text = strippedPkgName, + style = MaterialTheme.typography.bodySmall, + ) + } + + Row( + 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, - style = MaterialTheme.typography.bodySmall, - ) - } - Text( - text = extension.pkgName, - style = MaterialTheme.typography.bodySmall, + 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 private fun SourceSwitchPreference( 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, + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseIcons.kt b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseIcons.kt index 0cd3661e72..bd333228b6 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/BrowseIcons.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/BrowseIcons.kt @@ -1,5 +1,7 @@ package eu.kanade.presentation.browse.components +import android.content.pm.PackageManager +import android.util.DisplayMetrics import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio @@ -57,6 +59,7 @@ fun SourceIcon( fun ExtensionIcon( extension: Extension, modifier: Modifier = Modifier, + density: Int = DisplayMetrics.DENSITY_DEFAULT, ) { when (extension) { is Extension.Available -> { @@ -71,7 +74,7 @@ fun ExtensionIcon( ) } is Extension.Installed -> { - val icon by extension.getIcon() + val icon by extension.getIcon(density) when (icon) { Result.Error -> Image( bitmap = ImageBitmap.imageResource(id = R.mipmap.ic_local_source), @@ -95,13 +98,15 @@ fun ExtensionIcon( } @Composable -private fun Extension.getIcon(): State> { +private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): State> { val context = LocalContext.current return produceState>(initialValue = Result.Loading, this) { withIOContext { value = try { + val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA) + val appResources = context.packageManager.getResourcesForApplication(appInfo) Result.Success( - context.packageManager.getApplicationIcon(pkgName) + appResources.getDrawableForDensity(appInfo.icon, density, null)!! .toBitmap() .asImageBitmap(), ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4db5f3cff0..58ff4e1e61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,10 +269,12 @@ This extension is no longer available. This extension is not from the official Tachiyomi extensions list. Failed to get extensions list - Version: %1$s - Language: %1$s + Version + Language + Age rating 18+ May contain NSFW (18+) content + Got it Installing extension… Installer Legacy