From f3e455b007da32941dd29b79c3f0271b1729a55f Mon Sep 17 00:00:00 2001 From: Syer10 Date: Thu, 10 Nov 2022 13:16:10 -0500 Subject: [PATCH] Themeing improvements - Add Tertiary color - Add theme display from Tachiyomi - Show color changes in theme display --- .../jui/domain/ui/service/UiPreferences.kt | 8 + .../resources/MR/values/base/strings.xml | 4 + .../jui/ui/base/theme/AppColorsPreference.kt | 15 +- .../ca/gosyer/jui/ui/base/theme/AppTheme.kt | 28 +- .../library/components/LibraryMangaBadges.kt | 5 +- .../ui/settings/SettingsAppearanceScreen.kt | 264 ++++++++++++++---- .../gosyer/jui/uicore/components/Constants.kt | 21 ++ .../{SelectedBackground.kt => Modifier.kt} | 3 + .../ca/gosyer/jui/uicore/theme/ExtraColors.kt | 62 ++++ .../ca/gosyer/jui/uicore/theme/Themes.kt | 102 ++++++- 10 files changed, 428 insertions(+), 84 deletions(-) create mode 100644 ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Constants.kt rename ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/{SelectedBackground.kt => Modifier.kt} (86%) create mode 100644 ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/ExtraColors.kt diff --git a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/ui/service/UiPreferences.kt b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/ui/service/UiPreferences.kt index f6297bd6..7c5acb07 100644 --- a/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/ui/service/UiPreferences.kt +++ b/domain/src/commonMain/kotlin/ca/gosyer/jui/domain/ui/service/UiPreferences.kt @@ -42,6 +42,14 @@ class UiPreferences(private val preferenceStore: PreferenceStore) { return preferenceStore.getInt("color_secondary_dark", 0) } + fun colorTertiaryLight(): Preference { + return preferenceStore.getInt("color_tertiary_light", 0) + } + + fun colorTertiaryDark(): Preference { + return preferenceStore.getInt("color_tertiary_dark", 0) + } + fun startScreen(): Preference { return preferenceStore.getJsonObject("start_screen", StartScreen.Library, StartScreen.serializer()) } diff --git a/i18n/src/commonMain/resources/MR/values/base/strings.xml b/i18n/src/commonMain/resources/MR/values/base/strings.xml index 723f9c53..e7c4bbd7 100644 --- a/i18n/src/commonMain/resources/MR/values/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/values/base/strings.xml @@ -198,10 +198,14 @@ Dark Text Preset themes + Default + Legacy Blue + AMOLED Primary Displayed most frequently across your app Secondary Accents select parts of the UI + Tertiary Window decorations Restart JUI when this setting is changed diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppColorsPreference.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppColorsPreference.kt index be3f4b9f..9d0b15ab 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppColorsPreference.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppColorsPreference.kt @@ -16,31 +16,36 @@ import kotlinx.coroutines.CoroutineScope data class AppColorsPreference( val primary: Preference, - val secondary: Preference + val secondary: Preference, + val tertiary: Preference ) class AppColorsPreferenceState( val primaryStateFlow: PreferenceMutableStateFlow, - val secondaryStateFlow: PreferenceMutableStateFlow + val secondaryStateFlow: PreferenceMutableStateFlow, + val tertiaryStateFlow: PreferenceMutableStateFlow ) fun UiPreferences.getLightColors(): AppColorsPreference { return AppColorsPreference( colorPrimaryLight().asColor(), - colorSecondaryLight().asColor() + colorSecondaryLight().asColor(), + colorTertiaryLight().asColor() ) } fun UiPreferences.getDarkColors(): AppColorsPreference { return AppColorsPreference( colorPrimaryDark().asColor(), - colorSecondaryDark().asColor() + colorSecondaryDark().asColor(), + colorTertiaryDark().asColor() ) } fun AppColorsPreference.asStateFlow(scope: CoroutineScope): AppColorsPreferenceState { return AppColorsPreferenceState( primary.asStateIn(scope), - secondary.asStateIn(scope) + secondary.asStateIn(scope), + tertiary.asStateIn(scope) ) } diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppTheme.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppTheme.kt index f5890ee5..9afa807b 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppTheme.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/base/theme/AppTheme.kt @@ -23,6 +23,7 @@ import ca.gosyer.jui.domain.ui.service.UiPreferences import ca.gosyer.jui.ui.base.LocalViewModels import ca.gosyer.jui.ui.base.theme.ThemeScrollbarStyle.getScrollbarStyle import ca.gosyer.jui.uicore.components.LocalScrollbarStyle +import ca.gosyer.jui.uicore.theme.ExtraColors import ca.gosyer.jui.uicore.theme.Theme import ca.gosyer.jui.uicore.theme.themes import ca.gosyer.jui.uicore.vm.ContextWrapper @@ -42,17 +43,19 @@ import me.tatarka.inject.annotations.Inject fun AppTheme(content: @Composable () -> Unit) { val viewModels = LocalViewModels.current val vm = remember { viewModels.appThemeViewModel() } - val colors = vm.getColors() + val (colors, extraColors) = vm.getColors() /*val systemUiController = rememberSystemUiController()*/ DisposableEffect(vm) { onDispose(vm::onDispose) } MaterialTheme(colors = colors) { - CompositionLocalProvider( - LocalScrollbarStyle provides getScrollbarStyle(), - content = content - ) + ExtraColors.WithExtraColors(extraColors) { + CompositionLocalProvider( + LocalScrollbarStyle provides getScrollbarStyle(), + content = content + ) + } } } @@ -70,7 +73,7 @@ class AppThemeViewModel @Inject constructor( private val baseThemeScope = CoroutineScope(baseThemeJob) @Composable - fun getColors(): Colors { + fun getColors(): Pair { val themeMode by themeMode.collectAsState() val lightTheme by lightTheme.collectAsState() val darkTheme by darkTheme.collectAsState() @@ -88,8 +91,9 @@ class AppThemeViewModel @Inject constructor( val primary by colors.primaryStateFlow.collectAsState() val secondary by colors.secondaryStateFlow.collectAsState() + val tertiary by colors.tertiaryStateFlow.collectAsState() - return getMaterialColors(baseTheme.colors, primary, secondary) + return getMaterialColors(baseTheme.colors, primary, secondary) to getExtraColors(baseTheme.extraColors, tertiary) } @Composable @@ -131,6 +135,16 @@ class AppThemeViewModel @Inject constructor( ) } + private fun getExtraColors( + baseExtraColors: ExtraColors, + colorTertiary: Color + ): ExtraColors { + val tertiary = colorTertiary.takeOrElse { baseExtraColors.tertiary } + return baseExtraColors.copy( + tertiary = tertiary + ) + } + override fun onDispose() { baseThemeScope.cancel() scope.cancel() diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/library/components/LibraryMangaBadges.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/library/components/LibraryMangaBadges.kt index 46de6724..3354b070 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/library/components/LibraryMangaBadges.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/library/components/LibraryMangaBadges.kt @@ -24,6 +24,7 @@ import ca.gosyer.jui.domain.manga.model.Manga import ca.gosyer.jui.domain.source.model.Source import ca.gosyer.jui.i18n.MR import ca.gosyer.jui.uicore.resources.stringResource +import ca.gosyer.jui.uicore.theme.extraColors @Composable fun LibraryMangaBadges( @@ -52,9 +53,9 @@ fun LibraryMangaBadges( if (showUnread && unread != null && unread > 0) { Text( text = unread.toString(), - modifier = Modifier.background(MaterialTheme.colors.primary).then(BadgesInnerPadding), + modifier = Modifier.background(MaterialTheme.extraColors.tertiary).then(BadgesInnerPadding), style = MaterialTheme.typography.caption, - color = MaterialTheme.colors.onPrimary + color = MaterialTheme.extraColors.onTertiary ) } if (showDownloaded && downloaded != null && downloaded > 0) { diff --git a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsAppearanceScreen.kt b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsAppearanceScreen.kt index 861ee958..f18dd7fe 100644 --- a/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsAppearanceScreen.kt +++ b/presentation/src/commonMain/kotlin/ca/gosyer/jui/ui/settings/SettingsAppearanceScreen.kt @@ -6,39 +6,54 @@ package ca.gosyer.jui.ui.settings +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Colors +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.primarySurface import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import ca.gosyer.jui.domain.ui.model.ThemeMode import ca.gosyer.jui.domain.ui.service.UiPreferences import ca.gosyer.jui.i18n.MR @@ -53,13 +68,16 @@ import ca.gosyer.jui.ui.base.theme.getLightColors import ca.gosyer.jui.ui.main.components.bottomNav import ca.gosyer.jui.ui.viewModel import ca.gosyer.jui.uicore.components.VerticalScrollbar +import ca.gosyer.jui.uicore.components.mangaAspectRatio import ca.gosyer.jui.uicore.components.rememberScrollbarAdapter import ca.gosyer.jui.uicore.components.scrollbarPadding +import ca.gosyer.jui.uicore.components.secondaryItemAlpha import ca.gosyer.jui.uicore.insets.navigationBars import ca.gosyer.jui.uicore.insets.statusBars import ca.gosyer.jui.uicore.prefs.PreferenceMutableStateFlow import ca.gosyer.jui.uicore.resources.stringResource -import ca.gosyer.jui.uicore.theme.Theme +import ca.gosyer.jui.uicore.theme.ExtraColors +import ca.gosyer.jui.uicore.theme.extraColors import ca.gosyer.jui.uicore.theme.themes import ca.gosyer.jui.uicore.vm.ContextWrapper import ca.gosyer.jui.uicore.vm.ViewModel @@ -75,12 +93,16 @@ class SettingsAppearanceScreen : Screen { @Composable override fun Content() { val vm = viewModel { themesViewModel() } + val appThemeVM = viewModel { appThemeViewModel() } + val (colors, extraColors) = appThemeVM.getColors() SettingsAppearanceScreenContent( activeColors = vm.getActiveColors(), themeMode = vm.themeMode, lightTheme = vm.lightTheme, darkTheme = vm.darkTheme, - windowDecorations = vm.windowDecorations + windowDecorations = vm.windowDecorations, + customColors = colors, + customExtraColors = extraColors ) } } @@ -112,12 +134,16 @@ fun SettingsAppearanceScreenContent( themeMode: PreferenceMutableStateFlow, lightTheme: PreferenceMutableStateFlow, darkTheme: PreferenceMutableStateFlow, - windowDecorations: PreferenceMutableStateFlow + windowDecorations: PreferenceMutableStateFlow, + customColors: Colors, + customExtraColors: ExtraColors ) { val isLight = MaterialTheme.colors.isLight val themesForCurrentMode = remember(isLight) { themes.filter { it.colors.isLight == isLight } } + val currentLightTheme by lightTheme.collectAsState() + val currentDarkTheme by darkTheme.collectAsState() Scaffold( modifier = Modifier.windowInsetsPadding( @@ -138,7 +164,7 @@ fun SettingsAppearanceScreenContent( WindowInsets.navigationBars.only( WindowInsetsSides.Bottom ) - ).asPaddingValues() + ).asPaddingValues(), ) { item { ChoicePreference( @@ -156,16 +182,51 @@ fun SettingsAppearanceScreenContent( stringResource(MR.strings.preset_themes), modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 4.dp) ) - LazyRow(modifier = Modifier.padding(horizontal = 8.dp)) { + LazyRow( + modifier = Modifier + .animateContentSize() + .padding(vertical = 8.dp), + contentPadding = PaddingValues(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { items(themesForCurrentMode) { theme -> - ThemeItem( - theme, - onClick = { - (if (isLight) lightTheme else darkTheme).value = it.id - activeColors.primaryStateFlow.value = it.colors.primary - activeColors.secondaryStateFlow.value = it.colors.secondary + Column( + modifier = Modifier + .width(114.dp) + .padding(top = 8.dp), + ) { + val isSelected = (isLight && currentLightTheme == theme.id) || + (!isLight && currentDarkTheme == theme.id) + MaterialTheme( + colors = if (isSelected) customColors else theme.colors, + ) { + ExtraColors.WithExtraColors( + if (isSelected) customExtraColors else theme.extraColors + ) { + AppThemePreviewItem( + selected = isSelected, + onClick = { + (if (isLight) lightTheme else darkTheme).value = theme.id + activeColors.primaryStateFlow.value = theme.colors.primary + activeColors.secondaryStateFlow.value = theme.colors.secondary + activeColors.tertiaryStateFlow.value = theme.extraColors.tertiary + }, + ) + } } - ) + + Text( + text = stringResource(theme.titleRes), + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .secondaryItemAlpha(), + color = MaterialTheme.colors.onSurface, + textAlign = TextAlign.Center, + maxLines = 2, + style = MaterialTheme.typography.body2, + ) + } } } } @@ -185,6 +246,14 @@ fun SettingsAppearanceScreenContent( unsetColor = MaterialTheme.colors.secondary ) } + item { + ColorPreference( + preference = activeColors.tertiaryStateFlow, + title = stringResource(MR.strings.color_tertiary), + subtitle = stringResource(MR.strings.color_secondary_sub), + unsetColor = MaterialTheme.extraColors.tertiary + ) + } if (showWindowDecorationsOption) { item { SwitchPreference( @@ -213,56 +282,133 @@ fun SettingsAppearanceScreenContent( } @Composable -private fun ThemeItem( - theme: Theme, - onClick: (Theme) -> Unit +fun AppThemePreviewItem( + selected: Boolean, + onClick: () -> Unit, ) { - val borders = MaterialTheme.shapes.small - val borderColor = if (theme.colors.isLight) { - Color.Black.copy(alpha = 0.25f) - } else { - Color.White.copy(alpha = 0.15f) - } - Surface( - onClick = { onClick(theme) }, - elevation = 4.dp, - color = theme.colors.background, - shape = borders, + val dividerColor = MaterialTheme.colors.onSurface.copy(alpha = 0.2F) + Column( modifier = Modifier - .size(100.dp, 160.dp) - .padding(8.dp) - .border(1.dp, borderColor, borders) + .fillMaxWidth() + .aspectRatio(9f / 16f) + .border( + width = 4.dp, + color = if (selected) { + MaterialTheme.colors.primary + } else { + dividerColor + }, + shape = RoundedCornerShape(17.dp), + ) + .padding(4.dp) + .clip(RoundedCornerShape(13.dp)) + .background(MaterialTheme.colors.background) + .clickable(onClick = onClick), ) { - Column { + // App Bar + Row( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { Box( - Modifier - .fillMaxWidth() - .weight(1f) - .padding(6.dp) + modifier = Modifier + .fillMaxHeight(0.8f) + .weight(0.7f) + .padding(end = 4.dp) + .background( + color = MaterialTheme.colors.onSurface, + shape = MaterialTheme.shapes.small, + ), + ) + + Box( + modifier = Modifier.weight(0.3f), + contentAlignment = Alignment.CenterEnd, ) { - Text(stringResource(MR.strings.theme_text), fontSize = 11.sp) - Button( - onClick = {}, - enabled = false, - contentPadding = PaddingValues(), - modifier = Modifier - .align(Alignment.BottomStart) - .size(40.dp, 20.dp), - content = {}, - colors = ButtonDefaults.buttonColors( - disabledBackgroundColor = theme.colors.primary + if (selected) { + Icon( + imageVector = Icons.Filled.CheckCircle, + contentDescription = null, + tint = MaterialTheme.colors.primary, ) + } + } + } + + // Cover + Box( + modifier = Modifier + .padding(start = 8.dp, top = 2.dp) + .background( + color = dividerColor, + shape = MaterialTheme.shapes.small, ) - Surface( - Modifier - .size(24.dp) - .align(Alignment.BottomEnd), - shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)), - color = theme.colors.secondary, - elevation = 6.dp, - content = { } + .fillMaxWidth(0.5f) + .aspectRatio(mangaAspectRatio), + ) { + Row( + modifier = Modifier + .padding(4.dp) + .size(width = 24.dp, height = 16.dp) + .clip(RoundedCornerShape(5.dp)), + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .width(12.dp) + .background(MaterialTheme.extraColors.tertiary), + ) + Box( + modifier = Modifier + .fillMaxHeight() + .width(12.dp) + .background(MaterialTheme.colors.secondary), ) } } + + // Bottom bar + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + contentAlignment = Alignment.BottomCenter, + ) { + Surface( + elevation = 3.dp, + ) { + Row( + modifier = Modifier + .height(32.dp) + .fillMaxWidth() + .background(MaterialTheme.colors.primarySurface) + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = Modifier + .size(17.dp) + .background( + color = MaterialTheme.colors.primary, + shape = CircleShape, + ), + ) + Box( + modifier = Modifier + .padding(start = 8.dp) + .alpha(0.6f) + .height(17.dp) + .weight(1f) + .background( + color = MaterialTheme.colors.onSurface, + shape = MaterialTheme.shapes.small, + ), + ) + } + } + } } -} +} \ No newline at end of file diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Constants.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Constants.kt new file mode 100644 index 00000000..3efff90c --- /dev/null +++ b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Constants.kt @@ -0,0 +1,21 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.uicore.components + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.unit.dp + +private val horizontal = 16.dp +private val vertical = 8.dp + +val horizontalPadding = horizontal +val verticalPadding = vertical + +val topPaddingValues = PaddingValues(top = vertical) + +const val ReadItemAlpha = .38f +const val SecondaryItemAlpha = .78f \ No newline at end of file diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/SelectedBackground.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Modifier.kt similarity index 86% rename from ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/SelectedBackground.kt rename to ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Modifier.kt index a0409f3c..7ff18ce0 100644 --- a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/SelectedBackground.kt +++ b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/components/Modifier.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.draw.alpha fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed { if (isSelected) { @@ -20,3 +21,5 @@ fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed { this } } + +fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha) \ No newline at end of file diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/ExtraColors.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/ExtraColors.kt new file mode 100644 index 00000000..83f30331 --- /dev/null +++ b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/ExtraColors.kt @@ -0,0 +1,62 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package ca.gosyer.jui.uicore.theme + +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.runtime.structuralEqualityPolicy +import androidx.compose.ui.graphics.Color + +@Stable +class ExtraColors( + tertiary: Color, + onTertiary: Color, +) { + var tertiary by mutableStateOf(tertiary, structuralEqualityPolicy()) + internal set + var onTertiary by mutableStateOf(onTertiary, structuralEqualityPolicy()) + internal set + + fun copy( + tertiary: Color = this.tertiary, + onTertiary: Color = this.onTertiary, + ): ExtraColors = ExtraColors( + tertiary, + onTertiary, + ) + + override fun toString(): String { + return "ExtraColors(" + + "tertiary=$tertiary, " + + "onTertiary=$onTertiary, " + + ")" + } + + companion object { + @Composable + fun WithExtraColors(extraColors: ExtraColors, content: @Composable () -> Unit) { + CompositionLocalProvider( + LocalExtraColors provides extraColors, + content = content + ) + } + } +} + +val MaterialTheme.extraColors: ExtraColors + @Composable + get() = LocalExtraColors.current + +private val LocalExtraColors = staticCompositionLocalOf { + error("The AppColors composable must be called before usage") +} \ No newline at end of file diff --git a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/Themes.kt b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/Themes.kt index 7f0c65dd..3801860f 100644 --- a/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/Themes.kt +++ b/ui-core/src/commonMain/kotlin/ca/gosyer/jui/uicore/theme/Themes.kt @@ -10,21 +10,92 @@ import androidx.compose.material.Colors import androidx.compose.material.darkColors import androidx.compose.material.lightColors import androidx.compose.ui.graphics.Color +import ca.gosyer.jui.i18n.MR +import dev.icerock.moko.resources.StringResource data class Theme( val id: Int, - val colors: Colors + val titleRes: StringResource, + val colors: Colors, + val extraColors: ExtraColors +) + +fun tachiyomiLightColors( + primary: Color = Color(0xFF0057CE), + primaryVariant: Color = Color(0xFF001947), + secondary: Color = Color(0xFF0057CE), + secondaryVariant: Color = Color(0xFF018786), + background: Color = Color(0xFFFDFBFF), + surface: Color = Color(0xFFFDFBFF), + error: Color = Color(0xFFB00020), + onPrimary: Color = Color.White, + onSecondary: Color = Color.White, + onBackground: Color = Color(0xFF1B1B1E), + onSurface: Color = Color(0xFF1B1B1E), + onError: Color = Color.White +) = lightColors( + primary = primary, + primaryVariant = primaryVariant, + secondary = secondary, + secondaryVariant = secondaryVariant, + background = background, + surface = surface, + error = error, + onPrimary = onPrimary, + onSecondary = onSecondary, + onBackground = onBackground, + onSurface = onSurface, + onError = onError +) + +fun tachiyomiDarkColors( + primary: Color = Color(0xFFAEC6FF), + primaryVariant: Color = Color(0xFF00419E), + secondary: Color = Color(0xFFAEC6FF), + secondaryVariant: Color = Color(0xFF00419E), + background: Color = Color(0xFF1B1B1E), + surface: Color = Color(0xFF1B1B1E), + error: Color = Color(0xFFCF6679), + onPrimary: Color = Color(0xFF002C71), + onSecondary: Color = Color(0xFF002C71), + onBackground: Color = Color(0xFFE4E2E6), + onSurface: Color = Color(0xFFE4E2E6), + onError: Color = Color.White +) = darkColors( + primary = primary, + primaryVariant = primaryVariant, + secondary = secondary, + secondaryVariant = secondaryVariant, + background = background, + surface = surface, + error = error, + onPrimary = onPrimary, + onSecondary = onSecondary, + onBackground = onBackground, + onSurface = onSurface, + onError = onError +) + +fun extraColors( + tertiary: Color = Color(0xFF006E17), + onTertiary: Color = Color.White, +) = ExtraColors( + tertiary = tertiary, + onTertiary = onTertiary ) val themes = listOf( - // Pure white - Theme( - 1, - lightColors() - ), // Tachiyomi 0.x default colors + Theme( + 1, + MR.strings.theme_default, + tachiyomiLightColors(), + extraColors() + ), + // Tachiyomi 0.x legacy blue theme Theme( 2, + MR.strings.theme_legacy_blue, lightColors( primary = Color(0xFF2979FF), primaryVariant = Color(0xFF2979FF), @@ -32,20 +103,29 @@ val themes = listOf( secondary = Color(0xFF2979FF), secondaryVariant = Color(0xFF2979FF), onSecondary = Color.White - ) + ), + extraColors() ), // Tachiyomi 0.x dark theme Theme( 3, - darkColors() + MR.strings.theme_default, + tachiyomiDarkColors(), + extraColors( + tertiary = Color(0xFF7ADC77), + onTertiary = Color(0xFF003907) + ) ), // AMOLED theme Theme( 4, - darkColors( + MR.strings.theme_amoled, + tachiyomiDarkColors( primary = Color.Black, onPrimary = Color.White, - background = Color.Black - ) + background = Color.Black, + surface = Color.Black + ), + extraColors() ) )