Implement dark theme selector
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user