Implement color scheme preferences

This commit is contained in:
MM20 2021-09-27 16:05:57 +02:00
parent a2cf713b56
commit 30e8af57ac
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
8 changed files with 293 additions and 40 deletions

View File

@ -390,6 +390,13 @@
<string name="preference_card_background_default">Standard (weiß/dunkelgrau)</string> <string name="preference_card_background_default">Standard (weiß/dunkelgrau)</string>
<string name="preference_card_background_black">Weiß/schwarz</string> <string name="preference_card_background_black">Weiß/schwarz</string>
<string name="preference_card_background_colored">Farbig (aus Hintergrundbild)</string> <string name="preference_card_background_colored">Farbig (aus Hintergrundbild)</string>
<string name="preference_screen_colors">Farbschema</string>
<string name="preference_colors_default">Standard</string>
<string name="preference_colors_mm20">MM20</string>
<string name="preference_colors_wallpaper">Aus Hintergrundbild</string>
<string name="preference_colors_mdyou">System / Material You</string>
<string name="preference_colors_bw">Schwarz-Weiß</string>
<string name="preference_colors_custom">Benutzerdefiniert</string>
<string name="date_today">Heute</string> <string name="date_today">Heute</string>
<string name="date_tomorrow">Morgen</string> <string name="date_tomorrow">Morgen</string>

View File

@ -65,6 +65,13 @@
<string name="preference_theme_light">Light</string> <string name="preference_theme_light">Light</string>
<string name="preference_theme_dark">Dark</string> <string name="preference_theme_dark">Dark</string>
<string name="preference_theme_black">Black</string> <string name="preference_theme_black">Black</string>
<string name="preference_screen_colors">Color scheme</string>
<string name="preference_colors_default">Default</string>
<string name="preference_colors_mm20">MM20</string>
<string name="preference_colors_wallpaper">From wallpaper</string>
<string name="preference_colors_mdyou">System / Material You</string>
<string name="preference_colors_bw">Black and White</string>
<string name="preference_colors_custom">Custom</string>
<string name="preference_screen_about">About</string> <string name="preference_screen_about">About</string>
<string name="preference_version">Version</string> <string name="preference_version">Version</string>
<string name="preference_category_developer">Developer</string> <string name="preference_category_developer">Developer</string>

View File

@ -11,6 +11,16 @@ message Settings {
System = 2; System = 2;
} }
Theme theme = 1; 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_status_bar = 2;
bool light_nav_bar = 3; bool light_nav_bar = 3;
bool dim_wallpaper = 4; bool dim_wallpaper = 4;

View File

@ -1,24 +1,24 @@
package de.mm20.launcher2.ui.activity package de.mm20.launcher2.ui.activity
import android.app.WallpaperManager
import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetHost
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View import android.view.View
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.platform.LocalContext
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.doOnLayout import androidx.core.view.doOnLayout
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.google.accompanist.insets.ProvideWindowInsets 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.LauncherTheme
import de.mm20.launcher2.ui.locals.LocalAppWidgetHost import de.mm20.launcher2.ui.locals.LocalAppWidgetHost
import de.mm20.launcher2.ui.locals.LocalColorScheme 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.locals.LocalWindowSize
import de.mm20.launcher2.ui.screens.LauncherMainScreen import de.mm20.launcher2.ui.screens.LauncherMainScreen
import de.mm20.launcher2.ui.screens.settings.* import de.mm20.launcher2.ui.screens.settings.*
import de.mm20.launcher2.ui.theme.WallpaperColors import de.mm20.launcher2.ui.theme.colors.*
import de.mm20.launcher2.ui.theme.colors.DefaultColorScheme import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
import de.mm20.launcher2.ui.theme.colors.WallpaperColorScheme import kotlinx.coroutines.flow.map
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ComposeActivity : AppCompatActivity() { class ComposeActivity : AppCompatActivity() {
@ -46,43 +43,35 @@ class ComposeActivity : AppCompatActivity() {
setContent { setContent {
val navController = rememberNavController() val navController = rememberNavController()
val context = LocalContext.current
var windowSize by remember { mutableStateOf(Size(0f, 0f)) } var windowSize by remember { mutableStateOf(Size(0f, 0f)) }
findViewById<View>(android.R.id.content).doOnLayout { findViewById<View>(android.R.id.content).doOnLayout {
windowSize = Size(it.width.toFloat(), it.height.toFloat()) windowSize = Size(it.width.toFloat(), it.height.toFloat())
} }
var wallpaperColors by remember { mutableStateOf<WallpaperColors?>(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 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") { composable("settings/appearance") {
SettingsAppearanceScreen() SettingsAppearanceScreen()
} }
composable("settings/appearance/colors") {
SettingsColorsScreen()
}
composable( composable(
"settings/license?library={libraryName}", "settings/license?library={libraryName}",
arguments = listOf(navArgument("libraryName") { arguments = listOf(navArgument("libraryName") {

View File

@ -7,8 +7,10 @@ import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.dataStore import de.mm20.launcher2.preferences.dataStore
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.ListPreference 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.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.locals.LocalNavController
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -16,6 +18,7 @@ import kotlinx.coroutines.launch
fun SettingsAppearanceScreen() { fun SettingsAppearanceScreen() {
val context = LocalContext.current val context = LocalContext.current
val dataStore = context.dataStore val dataStore = context.dataStore
val navController = LocalNavController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) { PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) {
item { item {
@ -41,6 +44,9 @@ fun SettingsAppearanceScreen() {
} }
} }
) )
Preference(title = stringResource(id = R.string.preference_screen_colors), onClick = {
navController?.navigate("settings/appearance/colors")
})
} }
} }
} }

View File

@ -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,
)

View File

@ -1,8 +1,17 @@
package de.mm20.launcher2.ui.theme package de.mm20.launcher2.ui.theme
import android.app.WallpaperManager
import android.os.Build import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color 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?) { data class WallpaperColors(val primary: Color, val secondary: Color?, val tertiary: Color?) {
companion object { companion object {
@ -15,4 +24,37 @@ data class WallpaperColors(val primary: Color, val secondary: Color?, val tertia
) )
} }
} }
} }
@RequiresApi(Build.VERSION_CODES.O_MR1)
@Composable
fun wallpaperColorsAsState(): State<WallpaperColors> {
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)

View File

@ -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
}