Implement dark theme selector

This commit is contained in:
Suphon Thanakornpakapong
2021-06-08 23:56:48 +07:00
parent 9e6fa84f4f
commit 6a84175e1b
12 changed files with 254 additions and 18 deletions
+5
View File
@@ -91,4 +91,9 @@
<string name="minus_one_unavailable">No feed installed.</string>
<string name="fuzzy_search_title">Fuzzy Search</string>
<string name="fuzzy_search_desc">Approximate matching for app searches.</string>
<string name="theme_label">Theme</string>
<string name="theme_light">Light</string>
<string name="theme_dark">Dark</string>
<string name="theme_system_default">System default</string>
<string name="theme_follow_wallpaper">Follow wallpaper</string>
</resources>
@@ -30,6 +30,7 @@ import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
import app.lawnchair.gestures.GestureController
import app.lawnchair.nexuslauncher.OverlayCallbackImpl
import app.lawnchair.preferences.PreferenceManager
import app.lawnchair.util.restartLauncher
import com.android.launcher3.BaseActivity
import com.android.launcher3.LauncherAppState
@@ -49,6 +50,11 @@ open class LawnchairLauncher : QuickstepLauncher(), LifecycleOwner,
val gestureController by lazy { GestureController(this) }
private val defaultOverlay by lazy { OverlayCallbackImpl(this) }
private fun subscribePreferences() {
val preferenceManager = PreferenceManager.getInstance(this)
preferenceManager.launcherTheme.subscribe(this) { updateTheme() }
}
override fun setupViews() {
super.setupViews()
val launcherRootView = findViewById<LauncherRootView>(R.id.launcher)
@@ -60,6 +66,7 @@ open class LawnchairLauncher : QuickstepLauncher(), LifecycleOwner,
savedStateRegistryController.performRestore(savedInstanceState)
super.onCreate(savedInstanceState)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
subscribePreferences()
}
override fun onStart() {
@@ -74,6 +74,7 @@ class PreferenceManager private constructor(private val context: Context) : Base
val feedProvider = StringPref("pref_feedProvider", "")
val ignoreFeedWhitelist = BoolPref("pref_ignoreFeedWhitelist", false)
val workspaceDt2s = BoolPref("pref_doubleTap2Sleep", true)
val launcherTheme = StringPref("pref_launcherTheme", "system")
init {
sp.registerOnSharedPreferenceChangeListener(this)
@@ -15,16 +15,21 @@ import com.google.accompanist.insets.navigationBarsPadding
fun AlertBottomSheetContent(
buttons: @Composable RowScope.() -> Unit,
title: (@Composable () -> Unit)? = null,
text: @Composable (() -> Unit)? = null
text: @Composable (() -> Unit)? = null,
content: @Composable (() -> Unit)? = null
) {
val contentPadding = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
Column(
modifier = Modifier
.navigationBarsPadding()
.padding(16.dp)
.padding(
top = 16.dp,
bottom = 16.dp,
)
.fillMaxWidth()
) {
if (title != null) {
Box(modifier = Modifier.padding(bottom = 16.dp)) {
Box(modifier = Modifier.then(contentPadding)) {
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
val textStyle = MaterialTheme.typography.h6
ProvideTextStyle(textStyle, title)
@@ -32,15 +37,21 @@ fun AlertBottomSheetContent(
}
}
if (text != null) {
Box(modifier = Modifier.padding(bottom = 16.dp)) {
Box(modifier = Modifier.then(contentPadding)) {
val textStyle = MaterialTheme.typography.body2
ProvideTextStyle(textStyle, text)
}
}
if (content != null) {
Box(modifier = Modifier.padding(bottom = 16.dp)) {
content()
}
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.padding(top = 4.dp)
.then(contentPadding)
.fillMaxWidth()
) {
buttons()
@@ -56,11 +56,11 @@ fun GeneralPreferences() {
NavigationActionPreference(
label = stringResource(id = R.string.icon_pack),
destination = subRoute(name = GeneralRoutes.ICON_PACK),
showDivider = false,
subtitle =
LocalPreferenceInteractor.current.getIconPacks()
.find { it.packageName == preferenceManager().iconPackPackage.get() }?.name
)
ThemePreference(showDivider = false)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val wrapAdaptiveIcons = prefs.wrapAdaptiveIcons.observeAsState()
@@ -21,9 +21,9 @@ import kotlinx.coroutines.launch
@ExperimentalMaterialApi
@Composable
fun BottomSheet(
sheetContent: @Composable () -> Unit,
sheetState: BottomSheetState = rememberBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
sheetContent: @Composable () -> Unit,
) {
val currentSheetContent by rememberUpdatedState(sheetContent)
val modalBottomSheetState = sheetState.modalBottomSheetState
@@ -0,0 +1,116 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import app.lawnchair.preferences.PreferenceAdapter
import app.lawnchair.ui.AlertBottomSheetContent
import app.lawnchair.ui.util.addIf
import kotlinx.coroutines.launch
@ExperimentalMaterialApi
@Composable
fun <T> ListPreference(
adapter: PreferenceAdapter<T>,
entries: List<ListPreferenceEntry<T>>,
label: String,
enabled: Boolean = true,
showDivider: Boolean = true
) {
val sheetState = rememberBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
val currentValue = adapter.state.value
val currentLabel = entries
.firstOrNull { it.value == currentValue }
?.label?.invoke()
PreferenceTemplate(height = if (currentLabel != null) 72.dp else 52.dp, showDivider = showDivider) {
Row(
modifier = Modifier
.clickable(enabled) { scope.launch { sheetState.show() } }
.fillMaxHeight()
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier
.weight(1f)
.addIf(!enabled) { alpha(ContentAlpha.disabled) }
) {
Text(
text = label,
style = MaterialTheme.typography.subtitle1,
color = MaterialTheme.colors.onBackground,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
currentLabel?.let {
CompositionLocalProvider(
LocalContentAlpha provides ContentAlpha.medium,
LocalContentColor provides MaterialTheme.colors.onBackground
) {
Text(
text = it,
style = MaterialTheme.typography.body2,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
}
}
}
}
BottomSheet(sheetState = sheetState) {
AlertBottomSheetContent(
title = { Text(label) },
buttons = {
OutlinedButton(
shape = MaterialTheme.shapes.small,
onClick = { scope.launch { sheetState.hide() } }
) {
Text(text = stringResource(id = android.R.string.cancel))
}
}
) {
LazyColumn {
items(entries) { item ->
Row(
modifier = Modifier
.fillMaxSize()
.clickable {
adapter.onChange(item.value)
scope.launch { sheetState.hide() }
}
.padding(16.dp)
) {
RadioButton(
modifier = Modifier.padding(end = 16.dp),
selected = item.value == currentValue,
onClick = null
)
Text(text = item.label())
}
}
}
}
}
}
class ListPreferenceEntry<T>(
val value: T,
val label: @Composable () -> String,
)
@@ -0,0 +1,39 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import app.lawnchair.preferences.getAdapter
import app.lawnchair.preferences.preferenceManager
import com.android.launcher3.R
import com.android.launcher3.Utilities
object ThemeChoice {
val light = "light"
val dark = "dark"
val system = "system"
}
val themeEntries = listOf(
ListPreferenceEntry(ThemeChoice.light) { stringResource(id = R.string.theme_light) },
ListPreferenceEntry(ThemeChoice.dark) { stringResource(id = R.string.theme_dark) },
ListPreferenceEntry(ThemeChoice.system) {
when {
Utilities.ATLEAST_Q -> stringResource(id = R.string.theme_system_default)
else -> stringResource(id = R.string.theme_follow_wallpaper)
}
},
)
@ExperimentalMaterialApi
@Composable
fun ThemePreference(
showDivider: Boolean = true
) {
ListPreference(
adapter = preferenceManager().launcherTheme.getAdapter(),
entries = themeEntries,
label = stringResource(id = R.string.theme_label),
showDivider = showDivider
)
}
@@ -16,6 +16,7 @@
package app.lawnchair.ui.preferences.components
import androidx.compose.animation.Animatable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
@@ -33,6 +34,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material.icons.rounded.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -99,7 +101,10 @@ val shadowColors = listOf(Color(0, 0, 0, 31), Color.Transparent)
@Composable
fun TopBarSurface(floating: Boolean, content: @Composable () -> Unit) {
val (normalColor, floatingColor) = topBarColors()
val color by animateColorAsState(if (floating) floatingColor else normalColor)
val color = remember(key1 = normalColor) { Animatable(normalColor) }
LaunchedEffect(floating) {
color.animateTo(if (floating) floatingColor else normalColor)
}
val shadowAlpha by animateFloatAsState(if (floating) 1f else 0f)
Column(
modifier = Modifier
@@ -107,7 +112,7 @@ fun TopBarSurface(floating: Boolean, content: @Composable () -> Unit) {
) {
Box(
modifier = Modifier
.background(color)
.background(color.value)
.statusBarsPadding()
.pointerInput(remember { MutableInteractionSource() }) {
// consume touch
+39 -3
View File
@@ -20,8 +20,14 @@ import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import app.lawnchair.preferences.observeAsState
import app.lawnchair.preferences.preferenceManager
import app.lawnchair.ui.preferences.components.ThemeChoice
import com.android.launcher3.Utilities
import com.android.launcher3.uioverrides.WallpaperColorInfo
private val DarkColorPalette = darkColors(
primary = Blue600,
@@ -36,7 +42,7 @@ private val LightColorPalette = lightColors(
@Composable
fun LawnchairTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
darkTheme: Boolean = isSelectedThemeDark(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
@@ -51,4 +57,34 @@ fun LawnchairTheme(
shapes = Shapes,
content = content
)
}
}
@Composable
fun isSelectedThemeDark(): Boolean {
val themeChoice by preferenceManager().launcherTheme.observeAsState()
return when (themeChoice) {
ThemeChoice.light -> false
ThemeChoice.dark -> true
else -> isAutoThemeDark()
}
}
@Composable
fun isAutoThemeDark(): Boolean {
return when {
Utilities.ATLEAST_Q -> isSystemInDarkTheme()
else -> isWallpaperDark()
}
}
@Composable
fun isWallpaperDark(): Boolean {
val wallpaperColorInfo = WallpaperColorInfo.INSTANCE[LocalContext.current]
var isDark by remember { mutableStateOf(wallpaperColorInfo.isDark) }
DisposableEffect(wallpaperColorInfo) {
val listener = WallpaperColorInfo.OnChangeListener { isDark = it.isDark }
wallpaperColorInfo.addOnChangeListener(listener)
onDispose { wallpaperColorInfo.removeOnChangeListener(listener) }
}
return isDark
}
@@ -106,7 +106,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
updateTheme();
}
private void updateTheme() {
protected void updateTheme() {
if (mThemeRes != Themes.getActivityThemeRes(this)) {
recreate();
}
+22 -6
View File
@@ -30,6 +30,8 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import app.lawnchair.preferences.PreferenceManager;
/**
* Various utility methods associated with theming.
*/
@@ -37,13 +39,27 @@ public class Themes {
public static int getActivityThemeRes(Context context) {
WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
PreferenceManager preferenceManager = PreferenceManager.getInstance(context);
String themeChoice = preferenceManager.getLauncherTheme().get();
boolean darkTheme;
if (Utilities.ATLEAST_Q) {
Configuration configuration = context.getResources().getConfiguration();
int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
darkTheme = nightMode == Configuration.UI_MODE_NIGHT_YES;
} else {
darkTheme = wallpaperColorInfo.isDark();
switch(themeChoice) {
case "light":
darkTheme = false;
break;
case "dark":
darkTheme = true;
break;
default:
if (Utilities.ATLEAST_Q) {
Configuration configuration = context.getResources().getConfiguration();
int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
darkTheme = nightMode == Configuration.UI_MODE_NIGHT_YES;
} else {
darkTheme = wallpaperColorInfo.isDark();
}
break;
}
if (darkTheme) {