From 30e8af57ac21f0aee52db8676be8ef03190c6ad7 Mon Sep 17 00:00:00 2001
From: MM20 <15646950+MM2-0@users.noreply.github.com>
Date: Mon, 27 Sep 2021 16:05:57 +0200
Subject: [PATCH] Implement color scheme preferences
---
i18n/src/main/res/values-de/strings.xml | 7 +
i18n/src/main/res/values/strings.xml | 7 +
preferences/src/main/proto/settings.proto | 10 ++
.../launcher2/ui/activity/ComposeActivity.kt | 70 ++++----
.../settings/SettingsAppearanceScreen.kt | 6 +
.../screens/settings/SettingsColorsScreen.kt | 159 ++++++++++++++++++
.../launcher2/ui/theme/WallpaperColors.kt | 44 ++++-
.../ui/theme/colors/BlackWhiteColorScheme.kt | 30 ++++
8 files changed, 293 insertions(+), 40 deletions(-)
create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsColorsScreen.kt
create mode 100644 ui/src/main/java/de/mm20/launcher2/ui/theme/colors/BlackWhiteColorScheme.kt
diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml
index ee9f002a..8d87d29e 100644
--- a/i18n/src/main/res/values-de/strings.xml
+++ b/i18n/src/main/res/values-de/strings.xml
@@ -390,6 +390,13 @@
Standard (weiß/dunkelgrau)
Weiß/schwarz
Farbig (aus Hintergrundbild)
+ Farbschema
+ Standard
+ MM20
+ Aus Hintergrundbild
+ System / Material You
+ Schwarz-Weiß
+ Benutzerdefiniert
Heute
Morgen
diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml
index a4507594..d2911889 100644
--- a/i18n/src/main/res/values/strings.xml
+++ b/i18n/src/main/res/values/strings.xml
@@ -65,6 +65,13 @@
Light
Dark
Black
+ Color scheme
+ Default
+ MM20
+ From wallpaper
+ System / Material You
+ Black and White
+ Custom
About
Version
Developer
diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto
index 28a7a158..00cbeef1 100644
--- a/preferences/src/main/proto/settings.proto
+++ b/preferences/src/main/proto/settings.proto
@@ -11,6 +11,16 @@ message Settings {
System = 2;
}
Theme theme = 1;
+ enum ColorScheme {
+ Default = 0;
+ MM20 = 1;
+ MaterialYou = 2;
+ Wallpaper = 3;
+ BlackAndWhite = 4;
+ Custom = 5;
+ }
+ ColorScheme color_scheme = 6;
+
bool light_status_bar = 2;
bool light_nav_bar = 3;
bool dim_wallpaper = 4;
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/activity/ComposeActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/activity/ComposeActivity.kt
index a724ac05..0d16ccf3 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/activity/ComposeActivity.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/activity/ComposeActivity.kt
@@ -1,24 +1,24 @@
package de.mm20.launcher2.ui.activity
-import android.app.WallpaperManager
import android.appwidget.AppWidgetHost
import android.os.Build
import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
import android.view.View
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalContext
import androidx.core.view.WindowCompat
import androidx.core.view.doOnLayout
-import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.google.accompanist.insets.ProvideWindowInsets
+import de.mm20.launcher2.ktx.isAtLeastApiLevel
+import de.mm20.launcher2.preferences.Settings
+import de.mm20.launcher2.preferences.dataStore
import de.mm20.launcher2.ui.LauncherTheme
import de.mm20.launcher2.ui.locals.LocalAppWidgetHost
import de.mm20.launcher2.ui.locals.LocalColorScheme
@@ -26,12 +26,9 @@ import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.ui.screens.LauncherMainScreen
import de.mm20.launcher2.ui.screens.settings.*
-import de.mm20.launcher2.ui.theme.WallpaperColors
-import de.mm20.launcher2.ui.theme.colors.DefaultColorScheme
-import de.mm20.launcher2.ui.theme.colors.WallpaperColorScheme
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import de.mm20.launcher2.ui.theme.colors.*
+import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
+import kotlinx.coroutines.flow.map
class ComposeActivity : AppCompatActivity() {
@@ -46,43 +43,35 @@ class ComposeActivity : AppCompatActivity() {
setContent {
val navController = rememberNavController()
+ val context = LocalContext.current
var windowSize by remember { mutableStateOf(Size(0f, 0f)) }
findViewById(android.R.id.content).doOnLayout {
windowSize = Size(it.width.toFloat(), it.height.toFloat())
}
- var wallpaperColors by remember { mutableStateOf(null) }
-
- DisposableEffect(null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
- val wallpaperManager = WallpaperManager.getInstance(this@ComposeActivity)
- val callback = { colors: android.app.WallpaperColors?, which: Int ->
- if (colors != null && which or WallpaperManager.FLAG_SYSTEM != 0) {
- wallpaperColors = WallpaperColors.fromPlatformType(colors)
- }
- }
- wallpaperManager.addOnColorsChangedListener(
- callback,
- Handler(Looper.getMainLooper())
- )
-
- lifecycleScope.launch {
- val colors = withContext(Dispatchers.IO) {
- wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
- } ?: return@launch
- wallpaperColors = WallpaperColors.fromPlatformType(colors)
- }
- return@DisposableEffect onDispose {
- wallpaperManager.removeOnColorsChangedListener(callback)
- }
- }
- onDispose {}
- }
-
if (windowSize.height <= 0 || windowSize.width <= 0) return@setContent
- val colorScheme = wallpaperColors?.let { WallpaperColorScheme(it) } ?: DefaultColorScheme()
+ val colorSchemePreference by remember { dataStore.data.map { it.appearance.colorScheme } }
+ .collectAsState(initial = Settings.AppearanceSettings.ColorScheme.Default)
+
+ val colorScheme = when (colorSchemePreference) {
+ Settings.AppearanceSettings.ColorScheme.MM20 -> MM20ColorScheme()
+ Settings.AppearanceSettings.ColorScheme.Wallpaper -> {
+ if (isAtLeastApiLevel(Build.VERSION_CODES.O_MR1)) {
+ val wallpaperColors by wallpaperColorsAsState()
+ WallpaperColorScheme(wallpaperColors)
+ } else DefaultColorScheme()
+ }
+ Settings.AppearanceSettings.ColorScheme.MaterialYou -> {
+ if (isAtLeastApiLevel(Build.VERSION_CODES.S)) {
+ SystemColorScheme(context)
+ } else DefaultColorScheme()
+ }
+ Settings.AppearanceSettings.ColorScheme.BlackAndWhite -> BlackWhiteColorScheme()
+ Settings.AppearanceSettings.ColorScheme.Custom -> TODO()
+ else -> DefaultColorScheme()
+ }
@@ -110,6 +99,9 @@ class ComposeActivity : AppCompatActivity() {
composable("settings/appearance") {
SettingsAppearanceScreen()
}
+ composable("settings/appearance/colors") {
+ SettingsColorsScreen()
+ }
composable(
"settings/license?library={libraryName}",
arguments = listOf(navArgument("libraryName") {
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsAppearanceScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsAppearanceScreen.kt
index a8541b1b..1dcd1d96 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsAppearanceScreen.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsAppearanceScreen.kt
@@ -7,8 +7,10 @@ import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.dataStore
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.ListPreference
+import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
+import de.mm20.launcher2.ui.locals.LocalNavController
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -16,6 +18,7 @@ import kotlinx.coroutines.launch
fun SettingsAppearanceScreen() {
val context = LocalContext.current
val dataStore = context.dataStore
+ val navController = LocalNavController.current
val scope = rememberCoroutineScope()
PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) {
item {
@@ -41,6 +44,9 @@ fun SettingsAppearanceScreen() {
}
}
)
+ Preference(title = stringResource(id = R.string.preference_screen_colors), onClick = {
+ navController?.navigate("settings/appearance/colors")
+ })
}
}
}
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsColorsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsColorsScreen.kt
new file mode 100644
index 00000000..4fc9243e
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/screens/settings/SettingsColorsScreen.kt
@@ -0,0 +1,159 @@
+package de.mm20.launcher2.ui.screens.settings
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.RadioButtonChecked
+import androidx.compose.material.icons.rounded.RadioButtonUnchecked
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import de.mm20.launcher2.ktx.isAtLeastApiLevel
+import de.mm20.launcher2.preferences.dataStore
+import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.component.preferences.Preference
+import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
+import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
+import de.mm20.launcher2.ui.theme.colors.*
+import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme as ColorSchemeOption
+
+@Composable
+fun SettingsColorsScreen() {
+ val context = LocalContext.current
+ val dataStore = context.dataStore
+ val scope = rememberCoroutineScope()
+ PreferenceScreen(title = stringResource(id = R.string.preference_screen_colors)) {
+ item {
+ val colorScheme by remember {
+ dataStore.data.map {
+ it.appearance.colorScheme
+ }
+ }.collectAsState(initial = ColorSchemeOption.Default)
+ val schemes = mutableListOf(
+ ColorSchemeItem(
+ ColorSchemeOption.Default,
+ DefaultColorScheme(),
+ stringResource(id = R.string.preference_colors_default)
+ ),
+ ColorSchemeItem(
+ ColorSchemeOption.MM20,
+ MM20ColorScheme(),
+ stringResource(id = R.string.preference_colors_mm20)
+ ),
+ ColorSchemeItem(
+ ColorSchemeOption.BlackAndWhite,
+ BlackWhiteColorScheme(),
+ stringResource(id = R.string.preference_colors_bw)
+ )
+ )
+ if (isAtLeastApiLevel(Build.VERSION_CODES.S)) {
+ schemes.add(
+ ColorSchemeItem(
+ ColorSchemeOption.MaterialYou,
+ SystemColorScheme(context),
+ stringResource(id = R.string.preference_colors_mdyou)
+ )
+ )
+ }
+ if (isAtLeastApiLevel(Build.VERSION_CODES.O_MR1)) {
+ val wallpaperColors by wallpaperColorsAsState()
+ schemes.add(
+ ColorSchemeItem(
+ ColorSchemeOption.Wallpaper,
+ WallpaperColorScheme(wallpaperColors),
+ stringResource(id = R.string.preference_colors_wallpaper)
+ )
+ )
+ }
+ PreferenceCategory {
+ for (scheme in schemes) {
+ Preference(
+ title = scheme.label,
+ icon = if (colorScheme == scheme.value) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
+ controls = {
+ ColorSchemePreview(scheme.colorScheme)
+ },
+ onClick = {
+ scope.launch {
+ dataStore.updateData {
+ it.toBuilder()
+ .setAppearance(
+ it.appearance.toBuilder().setColorScheme(scheme.value)
+ )
+ .build()
+ }
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ColorSchemePreview(colorScheme: ColorScheme) {
+ val isDark = !MaterialTheme.colors.isLight
+ val neutral1 = if (isDark) colorScheme.neutral1.shade800 else colorScheme.neutral1.shade100
+ val neutral2 = if (isDark) colorScheme.neutral2.shade800 else colorScheme.neutral2.shade100
+ val accent1 = if (isDark) colorScheme.accent1.shade300 else colorScheme.accent1.shade500
+ val accent2 = if (isDark) colorScheme.accent2.shade300 else colorScheme.accent2.shade500
+ val accent3 = if (isDark) colorScheme.accent3.shade300 else colorScheme.accent3.shade500
+ Box(
+ modifier = Modifier.height(48.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier.fillMaxHeight()
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .width(48.dp)
+ .background(neutral1)
+ )
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .width(48.dp)
+ .background(neutral2)
+ )
+ }
+
+ Row(
+ modifier = Modifier.height(16.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(16.dp)
+ .background(accent1)
+ )
+ Box(
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .size(16.dp)
+ .background(accent2)
+ )
+ Box(
+ modifier = Modifier
+ .size(16.dp)
+ .background(accent3)
+ )
+ }
+
+ }
+}
+
+private data class ColorSchemeItem(
+ val value: ColorSchemeOption,
+ val colorScheme: ColorScheme,
+ val label: String,
+)
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/theme/WallpaperColors.kt b/ui/src/main/java/de/mm20/launcher2/ui/theme/WallpaperColors.kt
index 32fdfa48..2133c3e0 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/theme/WallpaperColors.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/theme/WallpaperColors.kt
@@ -1,8 +1,17 @@
package de.mm20.launcher2.ui.theme
+import android.app.WallpaperManager
import android.os.Build
+import android.os.Handler
+import android.os.Looper
import androidx.annotation.RequiresApi
+import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
data class WallpaperColors(val primary: Color, val secondary: Color?, val tertiary: Color?) {
companion object {
@@ -15,4 +24,37 @@ data class WallpaperColors(val primary: Color, val secondary: Color?, val tertia
)
}
}
-}
\ No newline at end of file
+}
+
+@RequiresApi(Build.VERSION_CODES.O_MR1)
+@Composable
+fun wallpaperColorsAsState(): State {
+ val scope = rememberCoroutineScope()
+ val context = LocalContext.current
+ val state = remember { mutableStateOf(DefaultWallpaperColors) }
+ DisposableEffect(null) {
+ val wallpaperManager = WallpaperManager.getInstance(context)
+ val callback = { colors: android.app.WallpaperColors?, which: Int ->
+ if (colors != null && which or WallpaperManager.FLAG_SYSTEM != 0) {
+ state.value = WallpaperColors.fromPlatformType(colors)
+ }
+ }
+ wallpaperManager.addOnColorsChangedListener(
+ callback,
+ Handler(Looper.getMainLooper())
+ )
+
+ scope.launch {
+ val colors = withContext(Dispatchers.IO) {
+ wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
+ } ?: return@launch
+ state.value = WallpaperColors.fromPlatformType(colors)
+ }
+ onDispose {
+ wallpaperManager.removeOnColorsChangedListener(callback)
+ }
+ }
+ return state
+}
+
+internal val DefaultWallpaperColors = WallpaperColors(Color.White, null, null)
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/theme/colors/BlackWhiteColorScheme.kt b/ui/src/main/java/de/mm20/launcher2/ui/theme/colors/BlackWhiteColorScheme.kt
new file mode 100644
index 00000000..184d9c68
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/theme/colors/BlackWhiteColorScheme.kt
@@ -0,0 +1,30 @@
+package de.mm20.launcher2.ui.theme.colors
+
+import androidx.compose.ui.graphics.Color
+
+class BlackWhiteColorScheme: ColorScheme() {
+ override val neutral1: ColorSwatch
+ get() = ColorSwatch(
+ Color.White,
+ Color.White,
+ Color.White,
+ Color.White,
+ Color.White,
+ Color.White,
+ Color.White,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ Color.Black,
+ )
+ override val neutral2: ColorSwatch
+ get() = neutral1
+ override val accent1: ColorSwatch
+ get() = neutral1
+ override val accent2: ColorSwatch
+ get() = neutral1
+ override val accent3: ColorSwatch
+ get() = neutral1
+}
\ No newline at end of file