diff --git a/app/src/main/kotlin/moe/rukamori/archivetune/App.kt b/app/src/main/kotlin/moe/rukamori/archivetune/App.kt index e3e3f7058..bd945e490 100644 --- a/app/src/main/kotlin/moe/rukamori/archivetune/App.kt +++ b/app/src/main/kotlin/moe/rukamori/archivetune/App.kt @@ -174,15 +174,28 @@ class App : Application(), SingletonImageLoader.Factory { } // Apply random theme on startup if enabled - if (prefs[RandomThemeOnStartupKey] == true) { - val randomPalette = ThemePalettes.generateRandomPalette() + if (prefs[RandomThemeOnStartupKey] ?: (Build.VERSION.SDK_INT < Build.VERSION_CODES.S)) { + val styleName = prefs[RandomThemeStyleKey] ?: RandomThemeStyle.TONAL_SPOT.name + val style = try { RandomThemeStyle.valueOf(styleName) } catch (_: Exception) { RandomThemeStyle.TONAL_SPOT } + val randomPalette = when (style) { + RandomThemeStyle.TONAL_SPOT -> ThemePalettes.generateTonalSpotPalette() + RandomThemeStyle.NEUTRAL -> ThemePalettes.generateNeutralPalette() + RandomThemeStyle.VIBRANT -> ThemePalettes.generateVibrantPalette() + RandomThemeStyle.EXPRESSIVE -> ThemePalettes.generateExpressivePalette() + RandomThemeStyle.RAINBOW -> ThemePalettes.generateRainbowPalette() + RandomThemeStyle.FRUIT_SALAD -> ThemePalettes.generateFruitSaladPalette() + RandomThemeStyle.MONOCHROME -> ThemePalettes.generateMonochromePalette() + RandomThemeStyle.FIDELITY -> ThemePalettes.generateFidelityPalette() + RandomThemeStyle.CONTENT -> ThemePalettes.generateContentPalette() + RandomThemeStyle.CHAOS -> ThemePalettes.generateChaosPalette() + } val seedPalette = ThemeSeedPalette( primary = randomPalette.primary, secondary = randomPalette.secondary, tertiary = randomPalette.tertiary, neutral = randomPalette.neutral ) - val encodedPalette = ThemeSeedPaletteCodec.encodeForPreference(seedPalette, "Random") + val encodedPalette = ThemeSeedPaletteCodec.encodeForPreference(seedPalette, style.name) dataStore.edit { settings -> settings[CustomThemeColorKey] = encodedPalette } diff --git a/app/src/main/kotlin/moe/rukamori/archivetune/constants/PreferenceKeys.kt b/app/src/main/kotlin/moe/rukamori/archivetune/constants/PreferenceKeys.kt index b58adfa6a..8330e4bd8 100644 --- a/app/src/main/kotlin/moe/rukamori/archivetune/constants/PreferenceKeys.kt +++ b/app/src/main/kotlin/moe/rukamori/archivetune/constants/PreferenceKeys.kt @@ -19,6 +19,21 @@ import java.time.ZoneOffset val DynamicThemeKey = booleanPreferencesKey("dynamicTheme") val CustomThemeColorKey = stringPreferencesKey("customThemeColor") val RandomThemeOnStartupKey = booleanPreferencesKey("randomThemeOnStartup") + +enum class RandomThemeStyle { + TONAL_SPOT, + NEUTRAL, + VIBRANT, + EXPRESSIVE, + RAINBOW, + FRUIT_SALAD, + MONOCHROME, + FIDELITY, + CONTENT, + CHAOS, +} + +val RandomThemeStyleKey = stringPreferencesKey("randomThemeStyle") val DarkModeKey = stringPreferencesKey("darkMode") val PureBlackKey = booleanPreferencesKey("pureBlack") val DisableAnimationsKey = booleanPreferencesKey("disableAnimations") diff --git a/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/AppearanceSettings.kt b/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/AppearanceSettings.kt index dc60cbc86..b1a600594 100644 --- a/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/AppearanceSettings.kt +++ b/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/AppearanceSettings.kt @@ -88,7 +88,10 @@ import moe.rukamori.archivetune.constants.PlayerBackgroundStyleKey import moe.rukamori.archivetune.constants.MiniPlayerBackgroundStyle import moe.rukamori.archivetune.constants.MiniPlayerBackgroundStyleKey import moe.rukamori.archivetune.constants.PureBlackKey +import moe.rukamori.archivetune.constants.CustomThemeColorKey import moe.rukamori.archivetune.constants.RandomThemeOnStartupKey +import moe.rukamori.archivetune.constants.RandomThemeStyle +import moe.rukamori.archivetune.constants.RandomThemeStyleKey import moe.rukamori.archivetune.constants.PlayerButtonsStyle import moe.rukamori.archivetune.constants.PlayerButtonsStyleKey import moe.rukamori.archivetune.constants.SliderStyle @@ -122,7 +125,11 @@ import moe.rukamori.archivetune.ui.component.SwitchPreference import moe.rukamori.archivetune.ui.component.ThumbnailCornerRadiusSelectorButton import moe.rukamori.archivetune.ui.player.StyledPlaybackSlider import moe.rukamori.archivetune.ui.theme.CustomFontLoader +import moe.rukamori.archivetune.ui.theme.ThemeSeedPalette +import moe.rukamori.archivetune.ui.theme.ThemeSeedPaletteCodec import moe.rukamori.archivetune.ui.utils.backToMain +import moe.rukamori.archivetune.utils.PreferenceStore +import moe.rukamori.archivetune.utils.dataStore import moe.rukamori.archivetune.utils.isLowRamDevice import moe.rukamori.archivetune.utils.rememberEnumPreference import moe.rukamori.archivetune.utils.rememberPreference @@ -142,7 +149,11 @@ fun AppearanceSettings( ) val (randomThemeOnStartup, onRandomThemeOnStartupChange) = rememberPreference( RandomThemeOnStartupKey, - defaultValue = false + defaultValue = Build.VERSION.SDK_INT < Build.VERSION_CODES.S + ) + val (randomThemeStyle, onRandomThemeStyleChange) = rememberEnumPreference( + RandomThemeStyleKey, + defaultValue = RandomThemeStyle.TONAL_SPOT ) val (darkMode, onDarkModeChange) = rememberEnumPreference( DarkModeKey, @@ -399,6 +410,64 @@ fun AppearanceSettings( ) } + item(visible = randomThemeOnStartup && (!dynamicTheme || Build.VERSION.SDK_INT < Build.VERSION_CODES.S)) { + EnumListPreference( + title = { Text(stringResource(R.string.random_theme_style)) }, + description = stringResource(R.string.random_theme_style_desc), + icon = { Icon(painterResource(R.drawable.shuffle), null) }, + selectedValue = randomThemeStyle, + valueText = { + stringResource( + when (it) { + RandomThemeStyle.TONAL_SPOT -> R.string.random_style_tonal_spot + RandomThemeStyle.NEUTRAL -> R.string.random_style_neutral + RandomThemeStyle.VIBRANT -> R.string.random_style_vibrant + RandomThemeStyle.EXPRESSIVE -> R.string.random_style_expressive + RandomThemeStyle.RAINBOW -> R.string.random_style_rainbow + RandomThemeStyle.FRUIT_SALAD -> R.string.random_style_fruit_salad + RandomThemeStyle.MONOCHROME -> R.string.random_style_monochrome + RandomThemeStyle.FIDELITY -> R.string.random_style_fidelity + RandomThemeStyle.CONTENT -> R.string.random_style_content + RandomThemeStyle.CHAOS -> R.string.random_style_chaos + } + ) + }, + onValueSelected = onRandomThemeStyleChange, + ) + } + + item(visible = randomThemeOnStartup && (!dynamicTheme || Build.VERSION.SDK_INT < Build.VERSION_CODES.S)) { + PreferenceEntry( + title = { Text(stringResource(R.string.reload_random_theme)) }, + description = stringResource(R.string.reload_random_theme_desc), + icon = { Icon(painterResource(R.drawable.casino), null) }, + onClick = { + val palette = when (randomThemeStyle) { + RandomThemeStyle.TONAL_SPOT -> ThemePalettes.generateTonalSpotPalette() + RandomThemeStyle.NEUTRAL -> ThemePalettes.generateNeutralPalette() + RandomThemeStyle.VIBRANT -> ThemePalettes.generateVibrantPalette() + RandomThemeStyle.EXPRESSIVE -> ThemePalettes.generateExpressivePalette() + RandomThemeStyle.RAINBOW -> ThemePalettes.generateRainbowPalette() + RandomThemeStyle.FRUIT_SALAD -> ThemePalettes.generateFruitSaladPalette() + RandomThemeStyle.MONOCHROME -> ThemePalettes.generateMonochromePalette() + RandomThemeStyle.FIDELITY -> ThemePalettes.generateFidelityPalette() + RandomThemeStyle.CONTENT -> ThemePalettes.generateContentPalette() + RandomThemeStyle.CHAOS -> ThemePalettes.generateChaosPalette() + } + val seedPalette = ThemeSeedPalette( + primary = palette.primary, + secondary = palette.secondary, + tertiary = palette.tertiary, + neutral = palette.neutral, + ) + val encoded = ThemeSeedPaletteCodec.encodeForPreference(seedPalette, randomThemeStyle.name) + PreferenceStore.launchEdit(context.dataStore) { + this[CustomThemeColorKey] = encoded + } + }, + ) + } + item(visible = !dynamicTheme || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { PreferenceEntry( title = { Text(stringResource(R.string.color_palette)) }, diff --git a/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/PalettePickerScreen.kt b/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/PalettePickerScreen.kt index 39d586bbe..d51d81224 100644 --- a/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/PalettePickerScreen.kt +++ b/app/src/main/kotlin/moe/rukamori/archivetune/ui/screens/settings/PalettePickerScreen.kt @@ -773,25 +773,138 @@ object ThemePalettes { fun getRandomPalette(): ThemePalette = allPalettes.random() - fun generateRandomPalette(): ThemePalette { + fun generateTonalSpotPalette(): ThemePalette { val random = java.util.Random() - val primaryHue = random.nextFloat() * 360f - val primarySaturation = 0.5f + random.nextFloat() * 0.4f - val primaryLightness = 0.4f + random.nextFloat() * 0.25f - val primary = hctToColor(primaryHue, primarySaturation, primaryLightness) - val secondaryHue = (primaryHue + 30f + random.nextFloat() * 60f) % 360f - val secondary = hctToColor(secondaryHue, primarySaturation * 0.9f, primaryLightness * 1.1f) - val tertiaryHue = (primaryHue - 30f - random.nextFloat() * 60f + 360f) % 360f - val tertiary = hctToColor(tertiaryHue, primarySaturation * 0.8f, primaryLightness * 0.95f) - val neutralHue = (primaryHue + random.nextFloat() * 20f - 10f) % 360f - val neutral = hctToColor(neutralHue, 0.1f, primaryLightness * 0.8f) + val primary = hctToColor( + random.nextFloat() * 360f, + 0.5f + random.nextFloat() * 0.4f, + 0.4f + random.nextFloat() * 0.25f, + ) + return ThemePalette( + id = "tonalspot_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateNeutralPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.1f + random.nextFloat() * 0.2f, + 0.4f + random.nextFloat() * 0.25f, + ) + return ThemePalette( + id = "neutral_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateVibrantPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.8f + random.nextFloat() * 0.2f, + 0.45f + random.nextFloat() * 0.15f, + ) + return ThemePalette( + id = "vibrant_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateExpressivePalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.6f + random.nextFloat() * 0.35f, + 0.35f + random.nextFloat() * 0.35f, + ) + return ThemePalette( + id = "expressive_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateRainbowPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.5f + random.nextFloat() * 0.4f, + 0.4f + random.nextFloat() * 0.25f, + ) + return ThemePalette( + id = "rainbow_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateFruitSaladPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.6f + random.nextFloat() * 0.3f, + 0.4f + random.nextFloat() * 0.3f, + ) + return ThemePalette( + id = "fruitsalad_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateMonochromePalette(): ThemePalette { + val random = java.util.Random() + val gray = hctToColor(0f, 0f, random.nextFloat()) + return ThemePalette( + id = "monochrome_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = gray, secondary = gray, tertiary = gray, neutral = gray, + ) + } + + fun generateFidelityPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.5f + random.nextFloat() * 0.4f, + 0.4f + random.nextFloat() * 0.25f, + ) + return ThemePalette( + id = "fidelity_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateContentPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor( + random.nextFloat() * 360f, + 0.5f + random.nextFloat() * 0.35f, + 0.4f + random.nextFloat() * 0.25f, + ) + return ThemePalette( + id = "content_" + System.currentTimeMillis(), + nameResId = R.string.palette_custom, + primary = primary, secondary = primary, tertiary = primary, neutral = primary, + ) + } + + fun generateChaosPalette(): ThemePalette { + val random = java.util.Random() + val primary = hctToColor(random.nextFloat() * 360f, random.nextFloat(), random.nextFloat()) + val secondary = hctToColor(random.nextFloat() * 360f, random.nextFloat(), random.nextFloat()) + val tertiary = hctToColor(random.nextFloat() * 360f, random.nextFloat(), random.nextFloat()) + val neutral = hctToColor(random.nextFloat() * 360f, random.nextFloat(), random.nextFloat()) return ThemePalette( - id = "random_" + System.currentTimeMillis(), + id = "chaos_" + System.currentTimeMillis(), nameResId = R.string.palette_custom, - primary = primary, - secondary = secondary, - tertiary = tertiary, - neutral = neutral, + primary = primary, secondary = secondary, tertiary = tertiary, neutral = neutral, ) } diff --git a/app/src/main/kotlin/moe/rukamori/archivetune/ui/theme/Theme.kt b/app/src/main/kotlin/moe/rukamori/archivetune/ui/theme/Theme.kt index 56a7d980b..4afa653da 100644 --- a/app/src/main/kotlin/moe/rukamori/archivetune/ui/theme/Theme.kt +++ b/app/src/main/kotlin/moe/rukamori/archivetune/ui/theme/Theme.kt @@ -45,6 +45,9 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import moe.rukamori.archivetune.constants.AppFontPreference +import moe.rukamori.archivetune.constants.RandomThemeStyle +import moe.rukamori.archivetune.constants.RandomThemeStyleKey +import moe.rukamori.archivetune.utils.rememberEnumPreference import kotlin.math.abs import kotlin.math.min @@ -113,22 +116,43 @@ fun ArchiveTuneTheme( } } val expressiveMotionScheme = remember { MotionScheme.expressive() } - val paletteStyle = remember(themeColor, seedPalette) { + val (randomThemeStyle, _) = rememberEnumPreference( + RandomThemeStyleKey, + defaultValue = RandomThemeStyle.TONAL_SPOT, + ) + + val paletteStyle = remember(themeColor, seedPalette, randomThemeStyle) { paletteStyleFor(seedPalette?.primary ?: themeColor) } + val forcedPaletteStyle = remember(randomThemeStyle) { + when (randomThemeStyle) { + RandomThemeStyle.TONAL_SPOT -> PaletteStyle.TonalSpot + RandomThemeStyle.NEUTRAL -> PaletteStyle.Neutral + RandomThemeStyle.VIBRANT -> PaletteStyle.Vibrant + RandomThemeStyle.EXPRESSIVE -> PaletteStyle.Expressive + RandomThemeStyle.RAINBOW -> PaletteStyle.Rainbow + RandomThemeStyle.FRUIT_SALAD -> PaletteStyle.FruitSalad + RandomThemeStyle.MONOCHROME -> PaletteStyle.Monochrome + RandomThemeStyle.FIDELITY -> PaletteStyle.Fidelity + RandomThemeStyle.CONTENT -> PaletteStyle.Content + RandomThemeStyle.CHAOS -> null + } + } + val appColorScheme = - remember(seedPalette, themeColor, darkTheme) { + remember(seedPalette, themeColor, darkTheme, forcedPaletteStyle) { if (seedPalette != null) { exactPaletteColorScheme( palette = seedPalette, isDark = darkTheme, + style = forcedPaletteStyle, ) } else { materialKolorDynamicColorScheme( keyColor = themeColor, isDark = darkTheme, - style = paletteStyle, + style = forcedPaletteStyle ?: paletteStyle, ) } } @@ -216,12 +240,14 @@ private fun animateColorScheme(targetColorScheme: ColorScheme): ColorScheme { private fun exactPaletteColorScheme( palette: ThemeSeedPalette, isDark: Boolean, + style: PaletteStyle? = null, ): ColorScheme = mergedSeedColorScheme( primarySeed = palette.primary, secondarySeed = palette.secondary, tertiarySeed = palette.tertiary, neutralSeed = palette.neutral, isDark = isDark, + style = style ?: paletteStyleFor(palette.primary), ) private fun materialKolorDynamicColorScheme( diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index cb9f963dd..b16151ac1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -322,6 +322,20 @@ 動的テーマを有効にする 起動時のランダムなテーマ アプリを起動するたびにランダムなカラーパレットを適用する + ランダムテーマのスタイル + ランダムな色の生成方法を選択 + Tonal Spot + Neutral + Vibrant + Expressive + Rainbow + Fruit Salad + モノクロ + Fidelity + Content + カオス + 新しいカラーテーマを生成 + 今すぐランダムなカラーパレットを生成して適用 システムフォントを使用する アプリのフォントの代わりにデバイスのフォントを使用する diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 234b1de8a..608d6d12c 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -297,6 +297,20 @@ Bật giao diện động Chủ đề ngẫu nhiên khi khởi động Áp dụng bảng màu ngẫu nhiên mỗi khi app khởi động + Kiểu chủ đề ngẫu nhiên + Chọn cách tạo màu ngẫu nhiên + Tonal Spot + Neutral + Vibrant + Expressive + Rainbow + Fruit Salad + Đơn sắc + Fidelity + Content + Hỗn loạn + Tạo chủ đề màu mới + Tạo và áp dụng bảng màu ngẫu nhiên mới ngay bây giờ Bảng màu Chọn màu giao diện ứng dụng của bạn diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a067b275d..49d4a10a0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -342,6 +342,20 @@ Enable dynamic theme Random theme on startup Apply a random color palette every time the app starts + Random theme style + Choose how random colors are generated + Tonal Spot + Neutral + Vibrant + Expressive + Rainbow + Fruit Salad + Monochrome + Fidelity + Content + Chaos + Reload new color theme + Generate and apply a new random color palette now Font Choose the app font source Default