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