mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
car: a Dark mode setting with black, black and colour, deep colour and the old grey, black by default
This commit is contained in:
1 parent
894712c38d
commit
08eef32e59
5 files changed
+97
-2
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
package app.seb3thehacker.gearslip.car
|
package app.seb3thehacker.gearslip.car
|
||||||
|
|
||||||
|
import app.seb3thehacker.gearslip.car.theme.DarkStyle
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.seb3thehacker.gearslip.AppSettings
|
import app.seb3thehacker.gearslip.AppSettings
|
||||||
import app.seb3thehacker.gearslip.audio.CarAudio
|
import app.seb3thehacker.gearslip.audio.CarAudio
|
||||||
@@ -67,6 +68,7 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
|||||||
private const val KEY_MORE_LYRICS = "car_more_lyrics"
|
private const val KEY_MORE_LYRICS = "car_more_lyrics"
|
||||||
private const val KEY_THEME = "car_app_theme"
|
private const val KEY_THEME = "car_app_theme"
|
||||||
private const val KEY_UI_THEME = "car_ui_theme"
|
private const val KEY_UI_THEME = "car_ui_theme"
|
||||||
|
private const val KEY_DARK_STYLE = "car_dark_style"
|
||||||
private const val KEY_PINNED = "car_pinned_apps"
|
private const val KEY_PINNED = "car_pinned_apps"
|
||||||
private const val KEY_APP_ORDER = "car_app_order"
|
private const val KEY_APP_ORDER = "car_app_order"
|
||||||
private const val KEY_EXPERIMENTAL_FEATURES = "car_experimental_features"
|
private const val KEY_EXPERIMENTAL_FEATURES = "car_experimental_features"
|
||||||
@@ -108,6 +110,10 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
|||||||
private val moreLyricsFlow = MutableStateFlow(true)
|
private val moreLyricsFlow = MutableStateFlow(true)
|
||||||
private val themeFlow = MutableStateFlow(AppTheme.PHONE)
|
private val themeFlow = MutableStateFlow(AppTheme.PHONE)
|
||||||
val appTheme: StateFlow<AppTheme> = themeFlow
|
val appTheme: StateFlow<AppTheme> = themeFlow
|
||||||
|
private val darkStyleFlow = MutableStateFlow(DarkStyle.BLACK)
|
||||||
|
|
||||||
|
/** How dark the car's dark mode goes; black until the driver picks another. */
|
||||||
|
val darkStyle: StateFlow<DarkStyle> = darkStyleFlow
|
||||||
private val uiThemeFlow = MutableStateFlow(GsThemes.default.id)
|
private val uiThemeFlow = MutableStateFlow(GsThemes.default.id)
|
||||||
|
|
||||||
/** Id of the [GsTheme] controls are drawn in; an unknown id falls back to the default. */
|
/** Id of the [GsTheme] controls are drawn in; an unknown id falls back to the default. */
|
||||||
@@ -317,6 +323,9 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
|||||||
themeFlow.value = runCatching {
|
themeFlow.value = runCatching {
|
||||||
AppTheme.valueOf(AppSettings.getString(app, KEY_THEME, AppTheme.PHONE.name))
|
AppTheme.valueOf(AppSettings.getString(app, KEY_THEME, AppTheme.PHONE.name))
|
||||||
}.getOrDefault(AppTheme.PHONE)
|
}.getOrDefault(AppTheme.PHONE)
|
||||||
|
darkStyleFlow.value = runCatching {
|
||||||
|
DarkStyle.valueOf(AppSettings.getString(app, KEY_DARK_STYLE, DarkStyle.BLACK.name))
|
||||||
|
}.getOrDefault(DarkStyle.BLACK)
|
||||||
uiThemeFlow.value = AppSettings.getString(app, KEY_UI_THEME, GsThemes.default.id)
|
uiThemeFlow.value = AppSettings.getString(app, KEY_UI_THEME, GsThemes.default.id)
|
||||||
autoplayFlow.value = AppSettings.getString(app, KEY_AUTOPLAY, "false") == "true"
|
autoplayFlow.value = AppSettings.getString(app, KEY_AUTOPLAY, "false") == "true"
|
||||||
playerTitleFlow.value = AppSettings.getString(app, KEY_PLAYER_TITLE, "false") == "true"
|
playerTitleFlow.value = AppSettings.getString(app, KEY_PLAYER_TITLE, "false") == "true"
|
||||||
@@ -384,6 +393,11 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
|||||||
AppSettings.putString(app, KEY_THEME, theme.name)
|
AppSettings.putString(app, KEY_THEME, theme.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDarkStyle(style: DarkStyle) {
|
||||||
|
darkStyleFlow.value = style
|
||||||
|
AppSettings.putString(app, KEY_DARK_STYLE, style.name)
|
||||||
|
}
|
||||||
|
|
||||||
fun setUiTheme(id: String) {
|
fun setUiTheme(id: String) {
|
||||||
uiThemeFlow.value = id
|
uiThemeFlow.value = id
|
||||||
AppSettings.putString(app, KEY_UI_THEME, id)
|
AppSettings.putString(app, KEY_UI_THEME, id)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.seb3thehacker.gearslip.car
|
package app.seb3thehacker.gearslip.car
|
||||||
|
|
||||||
|
|
||||||
|
import app.seb3thehacker.gearslip.car.theme.DarkStyle
|
||||||
import app.seb3thehacker.gearslip.car.theme.*
|
import app.seb3thehacker.gearslip.car.theme.*
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -68,6 +69,7 @@ fun CarSettingsScreen() {
|
|||||||
val moreLyrics by CarSettings.moreLyrics.collectAsState()
|
val moreLyrics by CarSettings.moreLyrics.collectAsState()
|
||||||
val theme by CarSettings.appTheme.collectAsState()
|
val theme by CarSettings.appTheme.collectAsState()
|
||||||
val uiTheme by CarSettings.uiTheme.collectAsState()
|
val uiTheme by CarSettings.uiTheme.collectAsState()
|
||||||
|
val darkStyle by CarSettings.darkStyle.collectAsState()
|
||||||
val experimentalFeatures by CarSettings.experimentalFeaturesEnabled.collectAsState()
|
val experimentalFeatures by CarSettings.experimentalFeaturesEnabled.collectAsState()
|
||||||
val carSensors by CarSettings.carSensorsEnabled.collectAsState()
|
val carSensors by CarSettings.carSensorsEnabled.collectAsState()
|
||||||
val adaptiveVideo by CarSettings.adaptiveVideoEnabled.collectAsState()
|
val adaptiveVideo by CarSettings.adaptiveVideoEnabled.collectAsState()
|
||||||
@@ -116,6 +118,19 @@ fun CarSettingsScreen() {
|
|||||||
) { CarSettings.setAppTheme(it) }
|
) { CarSettings.setAppTheme(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (theme != AppTheme.LIGHT) item {
|
||||||
|
ChoiceRow(
|
||||||
|
"Dark mode",
|
||||||
|
listOf(
|
||||||
|
DarkStyle.BLACK to "Black",
|
||||||
|
DarkStyle.BLACK_COLOUR to "Black and colour",
|
||||||
|
DarkStyle.DEEP_COLOUR to "Deep colour",
|
||||||
|
DarkStyle.GREY to "Grey",
|
||||||
|
),
|
||||||
|
darkStyle,
|
||||||
|
) { CarSettings.setDarkStyle(it) }
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
ChoiceRow(
|
ChoiceRow(
|
||||||
"Theme",
|
"Theme",
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ fun CarUi() {
|
|||||||
val phoneDark by CarEnvironment.phoneDark.collectAsState()
|
val phoneDark by CarEnvironment.phoneDark.collectAsState()
|
||||||
val appTheme by CarSettings.appTheme.collectAsState()
|
val appTheme by CarSettings.appTheme.collectAsState()
|
||||||
val uiThemeId by CarSettings.uiTheme.collectAsState()
|
val uiThemeId by CarSettings.uiTheme.collectAsState()
|
||||||
|
val darkStyle by CarSettings.darkStyle.collectAsState()
|
||||||
val mediaApp by CarServices.mediaApp.collectAsState()
|
val mediaApp by CarServices.mediaApp.collectAsState()
|
||||||
val darkOutside by CarEnvironment.darkOutside.collectAsState()
|
val darkOutside by CarEnvironment.darkOutside.collectAsState()
|
||||||
val appContext = LocalContext.current.applicationContext
|
val appContext = LocalContext.current.applicationContext
|
||||||
@@ -118,7 +119,7 @@ fun CarUi() {
|
|||||||
AppTheme.LIGHT -> false
|
AppTheme.LIGHT -> false
|
||||||
AppTheme.DARK -> true
|
AppTheme.DARK -> true
|
||||||
}
|
}
|
||||||
val colors = (if (dark) dynamicDarkColorScheme(appContext) else dynamicLightColorScheme(appContext)).withTintedSurfaces(dark)
|
val colors = (if (dark) dynamicDarkColorScheme(appContext) else dynamicLightColorScheme(appContext)).withTintedSurfaces(dark, darkStyle)
|
||||||
val base = LocalDensity.current
|
val base = LocalDensity.current
|
||||||
val insets by CarEnvironment.insets.collectAsState()
|
val insets by CarEnvironment.insets.collectAsState()
|
||||||
val navigator = remember { CarNavigator(CarSettings.openOnConnect.value) }
|
val navigator = remember { CarNavigator(CarSettings.openOnConnect.value) }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app.seb3thehacker.gearslip.car.theme
|
|||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
|
import androidx.compose.ui.graphics.lerp
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The phone's Material You colours, with its accent washed over the surfaces. Material You's own
|
* The phone's Material You colours, with its accent washed over the surfaces. Material You's own
|
||||||
@@ -11,7 +12,8 @@ import androidx.compose.ui.graphics.compositeOver
|
|||||||
* Material 3's surface tint does, and it changes with the wallpaper like everything else.
|
* Material 3's surface tint does, and it changes with the wallpaper like everything else.
|
||||||
* Higher containers take more of the tint, so cards still stand off the background.
|
* Higher containers take more of the tint, so cards still stand off the background.
|
||||||
*/
|
*/
|
||||||
fun ColorScheme.withTintedSurfaces(dark: Boolean): ColorScheme {
|
fun ColorScheme.withTintedSurfaces(dark: Boolean, style: DarkStyle = DarkStyle.GREY): ColorScheme {
|
||||||
|
if (dark && style != DarkStyle.GREY) return withDarkStyle(style)
|
||||||
val base = if (dark) 0.03f else 0.02f
|
val base = if (dark) 0.03f else 0.02f
|
||||||
fun tint(own: Color, step: Int) = primary.copy(alpha = base + step * (if (dark) 0.015f else 0.01f)).compositeOver(own)
|
fun tint(own: Color, step: Int) = primary.copy(alpha = base + step * (if (dark) 0.015f else 0.01f)).compositeOver(own)
|
||||||
return copy(
|
return copy(
|
||||||
@@ -27,3 +29,54 @@ fun ColorScheme.withTintedSurfaces(dark: Boolean): ColorScheme {
|
|||||||
surfaceVariant = tint(surfaceVariant, 3),
|
surfaceVariant = tint(surfaceVariant, 3),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How dark the car's dark mode goes. [GREY] is Material You's own dark, with the tint above;
|
||||||
|
* the others are darker, and the two colour ones take their hue from the phone's accent.
|
||||||
|
*/
|
||||||
|
enum class DarkStyle { GREY, BLACK, DEEP_COLOUR, BLACK_COLOUR }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The surfaces rebuilt at [style]'s brightness, from the background up to the highest container.
|
||||||
|
* The levels are 0-255 greys; each surface keeps Material You's own slight hue at that level.
|
||||||
|
*/
|
||||||
|
private fun ColorScheme.withDarkStyle(style: DarkStyle): ColorScheme {
|
||||||
|
val levels = when (style) {
|
||||||
|
DarkStyle.BLACK -> listOf(0f, 12f, 17f, 25f, 34f)
|
||||||
|
DarkStyle.DEEP_COLOUR -> listOf(11f, 19f, 24f, 32f, 41f)
|
||||||
|
else -> listOf(0f, 17f, 22f, 30f, 39f)
|
||||||
|
}
|
||||||
|
val colour = style != DarkStyle.BLACK
|
||||||
|
// The accent's saturated container colour, not the pastel accent itself: tinting with the
|
||||||
|
// pastel only makes a surface lighter, where this one gives it colour at the same brightness.
|
||||||
|
fun at(own: Color, step: Int): Color {
|
||||||
|
val level = levels[step] / 255f
|
||||||
|
if (level == 0f) return Color.Black
|
||||||
|
val neutral = own.scaledTo(level)
|
||||||
|
return if (colour) {
|
||||||
|
lerp(neutral, primaryContainer.scaledTo(level), (0.6f + 0.08f * step).coerceAtMost(0.95f))
|
||||||
|
} else {
|
||||||
|
primary.copy(alpha = 0.02f + 0.015f * step).compositeOver(neutral)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy(
|
||||||
|
background = at(background, 0),
|
||||||
|
surface = at(surface, 0),
|
||||||
|
surfaceDim = at(surfaceDim, 0),
|
||||||
|
surfaceContainerLowest = at(surfaceContainerLowest, 0),
|
||||||
|
surfaceContainerLow = at(surfaceContainerLow, 1),
|
||||||
|
surfaceContainer = at(surfaceContainer, 2),
|
||||||
|
surfaceContainerHigh = at(surfaceContainerHigh, 3),
|
||||||
|
surfaceContainerHighest = at(surfaceContainerHighest, 4),
|
||||||
|
surfaceBright = at(surfaceBright, 4),
|
||||||
|
surfaceVariant = at(surfaceVariant, 3),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This colour's hue at an average brightness of [level], 0 to 1. */
|
||||||
|
private fun Color.scaledTo(level: Float): Color {
|
||||||
|
val mean = (red + green + blue) / 3f
|
||||||
|
if (mean <= 0f) return Color(level, level, level)
|
||||||
|
val k = level / mean
|
||||||
|
return Color((red * k).coerceAtMost(1f), (green * k).coerceAtMost(1f), (blue * k).coerceAtMost(1f))
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.seb3thehacker.gearslip.ui
|
package app.seb3thehacker.gearslip.ui
|
||||||
|
|
||||||
|
import app.seb3thehacker.gearslip.car.theme.DarkStyle
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -46,6 +47,12 @@ enum class CarGroup(val title: String) {
|
|||||||
|
|
||||||
private val sizeChoices = listOf(1f to "Normal", 1.25f to "Large", 1.5f to "Extra large")
|
private val sizeChoices = listOf(1f to "Normal", 1.25f to "Large", 1.5f to "Extra large")
|
||||||
private val themeChoices = listOf(AppTheme.PHONE to "Follow phone", AppTheme.LIGHT to "Light", AppTheme.DARK to "Dark")
|
private val themeChoices = listOf(AppTheme.PHONE to "Follow phone", AppTheme.LIGHT to "Light", AppTheme.DARK to "Dark")
|
||||||
|
private val darkChoices = listOf(
|
||||||
|
DarkStyle.BLACK to "Black",
|
||||||
|
DarkStyle.BLACK_COLOUR to "Black and colour",
|
||||||
|
DarkStyle.DEEP_COLOUR to "Deep colour",
|
||||||
|
DarkStyle.GREY to "Grey",
|
||||||
|
)
|
||||||
private val nightChoices = listOf(NightMode.AUTO to "Auto", NightMode.DAY to "Day", NightMode.NIGHT to "Night")
|
private val nightChoices = listOf(NightMode.AUTO to "Auto", NightMode.DAY to "Day", NightMode.NIGHT to "Night")
|
||||||
private val unitChoices = listOf(Units.AUTO to "Auto", Units.IMPERIAL to "Imperial", Units.METRIC to "Metric")
|
private val unitChoices = listOf(Units.AUTO to "Auto", Units.IMPERIAL to "Imperial", Units.METRIC to "Metric")
|
||||||
private val navBarChoices = listOf(
|
private val navBarChoices = listOf(
|
||||||
@@ -155,6 +162,7 @@ private fun GroupRows(group: CarGroup) {
|
|||||||
val moreLyrics by CarSettings.moreLyrics.collectAsState()
|
val moreLyrics by CarSettings.moreLyrics.collectAsState()
|
||||||
val theme by CarSettings.appTheme.collectAsState()
|
val theme by CarSettings.appTheme.collectAsState()
|
||||||
val uiTheme by CarSettings.uiTheme.collectAsState()
|
val uiTheme by CarSettings.uiTheme.collectAsState()
|
||||||
|
val darkStyle by CarSettings.darkStyle.collectAsState()
|
||||||
|
|
||||||
when (group) {
|
when (group) {
|
||||||
CarGroup.SCREEN -> {
|
CarGroup.SCREEN -> {
|
||||||
@@ -164,6 +172,10 @@ private fun GroupRows(group: CarGroup) {
|
|||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
Choice("App theme", themeChoices, theme, CarSettings::setAppTheme)
|
Choice("App theme", themeChoices, theme, CarSettings::setAppTheme)
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
if (theme != AppTheme.LIGHT) {
|
||||||
|
Choice("Dark mode", darkChoices, darkStyle, CarSettings::setDarkStyle)
|
||||||
|
SettingsDivider()
|
||||||
|
}
|
||||||
Choice("Buttons", GsThemes.builtIn.map { it.id to it.name }, uiTheme, CarSettings::setUiTheme)
|
Choice("Buttons", GsThemes.builtIn.map { it.id to it.name }, uiTheme, CarSettings::setUiTheme)
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
Choice("Map day/night", nightChoices, night, CarSettings::setNightMode)
|
Choice("Map day/night", nightChoices, night, CarSettings::setNightMode)
|
||||||
|
|||||||
Reference in new issue
Block a user