From b72d910cc90a31ad6d089ea928e1b92784b8aa9d Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Mon, 23 Sep 2024 19:41:34 +0700 Subject: [PATCH] Improve custom theme * update string * migrate to use DynamicMaterialTheme instead * support various style for custom theme * use BaseColorScheme with dynamicColorScheme instead, to avoid Activity being recreated * apply theme right when changing theme style * change default style to Fidelity * adaptable to system contrast with Custom theme & Theme based cover --- .../java/eu/kanade/domain/ui/UiPreferences.kt | 2 + .../screen/SettingsAppearanceScreen.kt | 34 ++++++ .../presentation/theme/TachiyomiTheme.kt | 16 ++- .../theme/colorscheme/CustomColorScheme.kt | 105 +++++++----------- .../tachiyomi/ui/reader/ReaderActivity.kt | 7 ++ .../moko-resources/base/strings.xml | 5 +- 6 files changed, 101 insertions(+), 68 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt index 1ad83d455..7f3652bf1 100644 --- a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt @@ -32,6 +32,8 @@ class UiPreferences( // KMK --> fun colorTheme() = preferenceStore.getInt("pref_color_theme", 0xFFDF0090.toInt()) + fun customThemeStyle() = preferenceStore.getEnum("pref_custom_theme_style_key", PaletteStyle.Fidelity) + fun themeCoverBased() = preferenceStore.getBoolean("pref_theme_cover_based_key", true) fun themeCoverBasedStyle() = preferenceStore.getEnum("pref_theme_cover_based_style_key", PaletteStyle.Vibrant) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt index 3aed7a821..48c388999 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt @@ -106,6 +106,40 @@ object SettingsAppearanceScreen : SearchableSettings { subtitle = stringResource(KMR.strings.custom_color_description), onClick = { navigator.push(AppCustomThemeColorPickerScreen()) }, ), + Preference.PreferenceItem.ListPreference( + pref = uiPreferences.customThemeStyle(), + title = stringResource(KMR.strings.pref_custom_theme_style), + enabled = appTheme == AppTheme.CUSTOM, + entries = PaletteStyle.entries + .associateWith { + when (it) { + PaletteStyle.TonalSpot -> + stringResource(KMR.strings.pref_theme_cover_based_style_tonalspot) + PaletteStyle.Neutral -> + stringResource(KMR.strings.pref_theme_cover_based_style_neutral) + PaletteStyle.Vibrant -> + stringResource(KMR.strings.pref_theme_cover_based_style_vibrant) + PaletteStyle.Expressive -> + stringResource(KMR.strings.pref_theme_cover_based_style_expressive) + PaletteStyle.Rainbow -> + stringResource(KMR.strings.pref_theme_cover_based_style_rainbow) + PaletteStyle.FruitSalad -> + stringResource(KMR.strings.pref_theme_cover_based_style_fruitsalad) + PaletteStyle.Monochrome -> + stringResource(KMR.strings.pref_theme_cover_based_style_monochrome) + PaletteStyle.Fidelity -> + stringResource(KMR.strings.pref_theme_cover_based_style_fidelity) + PaletteStyle.Content -> + stringResource(KMR.strings.pref_theme_cover_based_style_content) + else -> it.name + } + } + .toImmutableMap(), + onValueChanged = { + (context as? Activity)?.let { ActivityCompat.recreate(it) } + true + }, + ), // KMK <-- Preference.PreferenceItem.SwitchPreference( pref = amoledPref, diff --git a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt index 70a26d1ce..ca89f250b 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt @@ -1,5 +1,7 @@ package eu.kanade.presentation.theme +import android.app.UiModeManager +import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme @@ -7,6 +9,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.core.content.getSystemService +import com.materialkolor.Contrast import com.materialkolor.DynamicMaterialTheme import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.model.AppTheme @@ -58,6 +62,7 @@ fun TachiyomiTheme( ) { val uiPreferences = Injekt.get() // KMK --> + val context = LocalContext.current val isAmoled = amoled ?: uiPreferences.themeDarkAmoled().get() if (seedColor != null) { DynamicMaterialTheme( @@ -67,6 +72,11 @@ fun TachiyomiTheme( style = uiPreferences.themeCoverBasedStyle().get(), animate = true, content = content, + contrastLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + context.getSystemService()?.contrast?.toDouble() ?: Contrast.Default.value + } else { + Contrast.Default.value + }, ) } else { // KMK <-- @@ -110,7 +120,11 @@ private fun getThemeColorScheme( } // KMK --> AppTheme.CUSTOM -> { - CustomColorScheme(uiPreferences) + CustomColorScheme( + context = LocalContext.current, + seed = uiPreferences.colorTheme().get(), + style = uiPreferences.customThemeStyle().get(), + ) } // KMK <-- else -> { diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/CustomColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/CustomColorScheme.kt index 8a1c32616..37db5d1cb 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/CustomColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/CustomColorScheme.kt @@ -1,18 +1,21 @@ package eu.kanade.presentation.theme.colorscheme -import android.annotation.SuppressLint -import androidx.compose.material3.ColorScheme +import android.app.UiModeManager +import android.content.Context +import android.os.Build import androidx.compose.ui.graphics.Color -import com.google.android.material.color.utilities.Hct -import com.google.android.material.color.utilities.MaterialDynamicColors -import com.google.android.material.color.utilities.SchemeContent -import eu.kanade.domain.ui.UiPreferences +import androidx.core.content.getSystemService +import com.materialkolor.Contrast +import com.materialkolor.PaletteStyle +import com.materialkolor.dynamicColorScheme -internal class CustomColorScheme(uiPreferences: UiPreferences) : BaseColorScheme() { +internal class CustomColorScheme( + context: Context, + seed: Int, + style: PaletteStyle, +) : BaseColorScheme() { - private val seed = uiPreferences.colorTheme().get() - - private val custom = CustomCompatColorScheme(seed) + private val custom = CustomCompatColorScheme(context, seed, style) override val darkScheme get() = custom.darkScheme @@ -21,60 +24,32 @@ internal class CustomColorScheme(uiPreferences: UiPreferences) : BaseColorScheme get() = custom.lightScheme } -private class CustomCompatColorScheme(seed: Int) : BaseColorScheme() { +private class CustomCompatColorScheme( + context: Context, + seed: Int, + style: PaletteStyle, +) : BaseColorScheme() { - override val lightScheme = generateColorSchemeFromSeed(seed = seed, dark = false) - override val darkScheme = generateColorSchemeFromSeed(seed = seed, dark = true) - - companion object { - private fun Int.toComposeColor(): Color = Color(this) - - @SuppressLint("RestrictedApi") - private fun generateColorSchemeFromSeed(seed: Int, dark: Boolean): ColorScheme { - val scheme = SchemeContent( - Hct.fromInt(seed), - dark, - 0.0, - ) - val dynamicColors = MaterialDynamicColors() - return ColorScheme( - primary = dynamicColors.primary().getArgb(scheme).toComposeColor(), - onPrimary = dynamicColors.onPrimary().getArgb(scheme).toComposeColor(), - primaryContainer = dynamicColors.primaryContainer().getArgb(scheme).toComposeColor(), - onPrimaryContainer = dynamicColors.onPrimaryContainer().getArgb(scheme).toComposeColor(), - inversePrimary = dynamicColors.inversePrimary().getArgb(scheme).toComposeColor(), - secondary = dynamicColors.secondary().getArgb(scheme).toComposeColor(), - onSecondary = dynamicColors.onSecondary().getArgb(scheme).toComposeColor(), - secondaryContainer = dynamicColors.secondaryContainer().getArgb(scheme).toComposeColor(), - onSecondaryContainer = dynamicColors.onSecondaryContainer().getArgb(scheme).toComposeColor(), - tertiary = dynamicColors.tertiary().getArgb(scheme).toComposeColor(), - onTertiary = dynamicColors.onTertiary().getArgb(scheme).toComposeColor(), - tertiaryContainer = dynamicColors.tertiary().getArgb(scheme).toComposeColor(), - onTertiaryContainer = dynamicColors.onTertiaryContainer().getArgb(scheme).toComposeColor(), - background = dynamicColors.background().getArgb(scheme).toComposeColor(), - onBackground = dynamicColors.onBackground().getArgb(scheme).toComposeColor(), - surface = dynamicColors.surface().getArgb(scheme).toComposeColor(), - onSurface = dynamicColors.onSurface().getArgb(scheme).toComposeColor(), - surfaceVariant = dynamicColors.surfaceVariant().getArgb(scheme).toComposeColor(), - onSurfaceVariant = dynamicColors.onSurfaceVariant().getArgb(scheme).toComposeColor(), - surfaceTint = dynamicColors.surfaceTint().getArgb(scheme).toComposeColor(), - inverseSurface = dynamicColors.inverseSurface().getArgb(scheme).toComposeColor(), - inverseOnSurface = dynamicColors.inverseOnSurface().getArgb(scheme).toComposeColor(), - error = dynamicColors.error().getArgb(scheme).toComposeColor(), - onError = dynamicColors.onError().getArgb(scheme).toComposeColor(), - errorContainer = dynamicColors.errorContainer().getArgb(scheme).toComposeColor(), - onErrorContainer = dynamicColors.onErrorContainer().getArgb(scheme).toComposeColor(), - outline = dynamicColors.outline().getArgb(scheme).toComposeColor(), - outlineVariant = dynamicColors.outlineVariant().getArgb(scheme).toComposeColor(), - scrim = Color.Black, - surfaceBright = dynamicColors.surfaceBright().getArgb(scheme).toComposeColor(), - surfaceDim = dynamicColors.surfaceDim().getArgb(scheme).toComposeColor(), - surfaceContainer = dynamicColors.surfaceContainer().getArgb(scheme).toComposeColor(), - surfaceContainerHigh = dynamicColors.surfaceContainerHigh().getArgb(scheme).toComposeColor(), - surfaceContainerHighest = dynamicColors.surfaceContainerHighest().getArgb(scheme).toComposeColor(), - surfaceContainerLow = dynamicColors.surfaceContainerLow().getArgb(scheme).toComposeColor(), - surfaceContainerLowest = dynamicColors.surfaceContainerLowest().getArgb(scheme).toComposeColor(), - ) - } - } + override val lightScheme = dynamicColorScheme( + seedColor = Color(seed), + isDark = false, + isAmoled = false, + style = style, + contrastLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + context.getSystemService()?.contrast?.toDouble() ?: Contrast.Default.value + } else { + Contrast.Default.value + }, + ) + override val darkScheme = dynamicColorScheme( + seedColor = Color(seed), + isDark = true, + isAmoled = false, + style = style, + contrastLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + context.getSystemService()?.contrast?.toDouble() ?: Contrast.Default.value + } else { + Contrast.Default.value + }, + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index e541bfaa6..78d296d15 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader import android.Manifest import android.annotation.SuppressLint import android.app.Activity +import android.app.UiModeManager import android.app.assist.AssistContent import android.content.ClipData import android.content.ClipboardManager @@ -59,6 +60,7 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.google.android.material.elevation.SurfaceColors import com.google.android.material.transition.platform.MaterialContainerTransform import com.hippo.unifile.UniFile +import com.materialkolor.Contrast import com.materialkolor.dynamicColorScheme import dev.chrisbanes.insetter.applyInsetter import eu.kanade.domain.base.BasePreferences @@ -744,6 +746,11 @@ class ReaderActivity : BaseActivity() { isDark = isNightMode(), isAmoled = themeDarkAmoled, style = themeCoverBasedStyle, + contrastLevel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + getSystemService()?.contrast?.toDouble() ?: Contrast.Default.value + } else { + Contrast.Default.value + }, ) } // KMK <-- diff --git a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml index 1ed31eae2..065e6b8f7 100644 --- a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml @@ -17,9 +17,10 @@ + Custom theme style Details page Theme based on cover - Theme style + Cover based theme style Tonal Spot Neutral Vibrant @@ -44,7 +45,7 @@ Palette Personalizer - Pick a color for the custom Theme + Pick a seed color for the custom Theme Custom Theme