From ef897647dd085b2207575e8b08abfa8476734b65 Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 20 Apr 2024 13:36:02 +0200 Subject: [PATCH 1/5] First step to abomination --- app/build.gradle.kts | 2 + .../java/eu/kanade/domain/ui/UiPreferences.kt | 15 ++++ .../eu/kanade/domain/ui/model/AppTheme.kt | 1 + .../screen/SettingsAppearanceScreen.kt | 35 ++++++++ .../settings/widget/ThemeColorPickerWidget.kt | 36 ++++++++ .../presentation/theme/TachiyomiTheme.kt | 3 + .../theme/colorscheme/CustomColorScheme.kt | 85 +++++++++++++++++++ .../ui/base/delegate/ThemingDelegate.kt | 1 + app/src/main/res/values/themes.xml | 3 + gradle/compose.versions.toml | 2 + .../moko-resources/base/strings.xml | 4 + 11 files changed, 187 insertions(+) create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/widget/ThemeColorPickerWidget.kt create mode 100644 app/src/main/java/eu/kanade/presentation/theme/colorscheme/CustomColorScheme.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 854ba7a37f..8dce04e0de 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -171,6 +171,8 @@ dependencies { implementation(androidx.interpolator) + implementation(compose.colorpicker) + implementation(androidx.paging.runtime) implementation(androidx.paging.compose) 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 e54886b456..9fd16418fa 100644 --- a/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt @@ -1,10 +1,15 @@ package eu.kanade.domain.ui +import android.os.Build +import androidx.compose.ui.graphics.toArgb import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.NavStyle import eu.kanade.domain.ui.model.StartScreen import eu.kanade.domain.ui.model.TabletUiMode import eu.kanade.domain.ui.model.ThemeMode +import eu.kanade.presentation.theme.colorscheme.BaseColorScheme +import eu.kanade.presentation.theme.colorscheme.CloudflareColorScheme +import eu.kanade.presentation.theme.colorscheme.TachiyomiColorScheme import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import tachiyomi.core.common.preference.PreferenceStore @@ -24,6 +29,16 @@ class UiPreferences( if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT }, ) + fun colorTheme() = preferenceStore.getInt( + "pref_color-theme", + if(themeMode().get() == ThemeMode.DARK) { TachiyomiColorScheme.darkScheme.primary.toArgb() } else {TachiyomiColorScheme.lightScheme.primary.toArgb() } + ) + + fun colorPickerCoordinates() = preferenceStore.getString( + "color_picker_coordinates", + "" + ) + fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false) fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true) diff --git a/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt b/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt index d81f6b34c6..df94f8e75d 100644 --- a/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt +++ b/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt @@ -6,6 +6,7 @@ import tachiyomi.i18n.MR enum class AppTheme(val titleRes: StringResource?) { DEFAULT(MR.strings.label_default), MONET(MR.strings.theme_monet), + CUSTOM(MR.strings.theme_custom), CLOUDFLARE(MR.strings.theme_cloudflare), COTTONCANDY(MR.strings.theme_cottoncandy), DOOM(MR.strings.theme_doom), 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 99fc61eb28..67cbb9474e 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 @@ -2,15 +2,25 @@ package eu.kanade.presentation.more.settings.screen import android.app.Activity import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import com.github.skydoves.colorpicker.compose.ColorEnvelope +import com.github.skydoves.colorpicker.compose.HsvColorPicker +import com.github.skydoves.colorpicker.compose.rememberColorPickerController import eu.kanade.domain.ui.UiPreferences +import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.NavStyle import eu.kanade.domain.ui.model.StartScreen import eu.kanade.domain.ui.model.TabletUiMode @@ -20,7 +30,9 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget +import eu.kanade.presentation.more.settings.widget.ThemeColorPickerWidget import eu.kanade.tachiyomi.util.system.toast +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableMap import tachiyomi.core.common.i18n.stringResource @@ -52,6 +64,7 @@ object SettingsAppearanceScreen : SearchableSettings { uiPreferences: UiPreferences, ): Preference.PreferenceGroup { val context = LocalContext.current + val controller = rememberColorPickerController() val themeModePref = uiPreferences.themeMode() val themeMode by themeModePref.collectAsState() @@ -59,9 +72,30 @@ object SettingsAppearanceScreen : SearchableSettings { val appThemePref = uiPreferences.appTheme() val appTheme by appThemePref.collectAsState() + val customColorPref = uiPreferences.colorTheme() + val customColor by customColorPref.collectAsState() + val amoledPref = uiPreferences.themeDarkAmoled() val amoled by amoledPref.collectAsState() + val customPreferenceItem = if (appTheme == AppTheme.CUSTOM) { + listOf(Preference.PreferenceItem.CustomPreference( + title = stringResource(MR.strings.pref_custom_color), + ) { + Column { + ThemeColorPickerWidget( + value = controller.selectedPoint.value, + controller = controller, + onItemClick = { color -> + customColorPref.set(color) + } + ) + } + }) + } else { + emptyList() + } + return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_category_theme), preferenceItems = persistentListOf( @@ -84,6 +118,7 @@ object SettingsAppearanceScreen : SearchableSettings { ) } }, + *customPreferenceItem.toTypedArray(), Preference.PreferenceItem.SwitchPreference( pref = amoledPref, title = stringResource(MR.strings.pref_dark_theme_pure_black), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/ThemeColorPickerWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/ThemeColorPickerWidget.kt new file mode 100644 index 0000000000..fa820c79a3 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/ThemeColorPickerWidget.kt @@ -0,0 +1,36 @@ +package eu.kanade.presentation.more.settings.widget + +import android.graphics.PointF +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.dp +import com.github.skydoves.colorpicker.compose.ColorEnvelope +import com.github.skydoves.colorpicker.compose.ColorPickerController +import com.github.skydoves.colorpicker.compose.HsvColorPicker + +@Composable +internal fun ThemeColorPickerWidget( + value: PointF, + controller: ColorPickerController, + onItemClick: (Int) -> Unit, +) { + BasePreferenceWidget( + subcomponent = { + HsvColorPicker( + modifier = Modifier + .fillMaxWidth() + .height(450.dp) + .padding(10.dp), + controller = controller, + onColorChanged = { colorEnvelope: ColorEnvelope -> + val color: Int = colorEnvelope.color.toArgb() + onItemClick(color) + } + ) + } + ) +} 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 5d860b702e..bdccf07d3d 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt @@ -11,6 +11,7 @@ import eu.kanade.domain.ui.model.AppTheme import eu.kanade.presentation.theme.colorscheme.BaseColorScheme import eu.kanade.presentation.theme.colorscheme.CloudflareColorScheme import eu.kanade.presentation.theme.colorscheme.CottoncandyColorScheme +import eu.kanade.presentation.theme.colorscheme.CustomColorScheme import eu.kanade.presentation.theme.colorscheme.DoomColorScheme import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme @@ -72,6 +73,8 @@ private fun getThemeColorScheme( val uiPreferences = Injekt.get() val colorScheme = if (appTheme == AppTheme.MONET) { MonetColorScheme(LocalContext.current) + } else if (appTheme == AppTheme.CUSTOM) { + CustomColorScheme(uiPreferences) } else { colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme) } 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 new file mode 100644 index 0000000000..d8c7bb7179 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/CustomColorScheme.kt @@ -0,0 +1,85 @@ +package eu.kanade.presentation.theme.colorscheme + +import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.material3.ColorScheme +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 + +internal class CustomColorScheme(uiPreferences: UiPreferences) : BaseColorScheme() { + + private val seed = uiPreferences.colorTheme().get() + + private val custom = CustomCompatColorScheme(seed) + + override val darkScheme + get() = custom.darkScheme + + override val lightScheme + get() = custom.lightScheme + +} + +private class CustomCompatColorScheme(seed: Int) : 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 { + Log.i("customcolorscheme", seed.toString()) + 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(), + ) + } + } +} + + diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt index 93b69adb3e..cd1b44693a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt @@ -32,6 +32,7 @@ class ThemingDelegateImpl : ThemingDelegate { } private val themeResources: Map = mapOf( + AppTheme.CUSTOM to R.style.Theme_Tachiyomi_Custom, AppTheme.MONET to R.style.Theme_Tachiyomi_Monet, AppTheme.COTTONCANDY to R.style.Theme_Tachiyomi_CottonCandy, AppTheme.GREEN_APPLE to R.style.Theme_Tachiyomi_GreenApple, diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 14d86a2473..0e43847e14 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -85,6 +85,9 @@