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