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_black">Weiß/schwarz</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_tomorrow">Morgen</string>

View File

@ -65,6 +65,13 @@
<string name="preference_theme_light">Light</string>
<string name="preference_theme_dark">Dark</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_version">Version</string>
<string name="preference_category_developer">Developer</string>

View File

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

View File

@ -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<View>(android.R.id.content).doOnLayout {
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
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") {

View File

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

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