Add theme mode selector in SettingsScreen
This commit is contained in:
@@ -35,11 +35,13 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.valentinilk.shimmer.LocalShimmerTheme
|
import com.valentinilk.shimmer.LocalShimmerTheme
|
||||||
import com.valentinilk.shimmer.defaultShimmerTheme
|
import com.valentinilk.shimmer.defaultShimmerTheme
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ColorPaletteMode
|
||||||
import it.vfsfitvnm.vimusic.services.PlayerService
|
import it.vfsfitvnm.vimusic.services.PlayerService
|
||||||
import it.vfsfitvnm.vimusic.ui.components.BottomSheetMenu
|
import it.vfsfitvnm.vimusic.ui.components.BottomSheetMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.rememberMenuState
|
import it.vfsfitvnm.vimusic.ui.components.rememberMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.HomeScreen
|
import it.vfsfitvnm.vimusic.ui.screens.HomeScreen
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsScreen
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.rememberColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.rememberColorPalette
|
||||||
@@ -64,7 +66,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
setContent {
|
setContent {
|
||||||
val preferences = rememberPreferences()
|
val preferences = rememberPreferences()
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
val isDarkTheme = isSystemInDarkTheme()
|
|
||||||
|
val isDarkTheme = when (preferences.colorPaletteMode) {
|
||||||
|
ColorPaletteMode.Light -> false
|
||||||
|
ColorPaletteMode.Dark -> true
|
||||||
|
ColorPaletteMode.System -> isSystemInDarkTheme()
|
||||||
|
}
|
||||||
|
|
||||||
val colorPalette = rememberColorPalette(isDarkTheme)
|
val colorPalette = rememberColorPalette(isDarkTheme)
|
||||||
|
|
||||||
@@ -126,6 +133,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(LocalColorPalette.current.background)
|
.background(LocalColorPalette.current.background)
|
||||||
) {
|
) {
|
||||||
|
// SettingsScreen()
|
||||||
HomeScreen(intentVideoId = intentVideoId)
|
HomeScreen(intentVideoId = intentVideoId)
|
||||||
|
|
||||||
BottomSheetMenu(
|
BottomSheetMenu(
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.enums
|
||||||
|
|
||||||
|
enum class ColorPaletteMode {
|
||||||
|
Light,
|
||||||
|
Dark,
|
||||||
|
System
|
||||||
|
}
|
||||||
@@ -3,17 +3,21 @@ package it.vfsfitvnm.vimusic.ui.components.themed
|
|||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
@@ -26,10 +30,7 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import it.vfsfitvnm.vimusic.ui.components.ChunkyButton
|
import it.vfsfitvnm.vimusic.ui.components.ChunkyButton
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.*
|
||||||
import it.vfsfitvnm.vimusic.utils.color
|
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -194,16 +195,15 @@ fun ConfirmationDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private inline fun DefaultDialog(
|
inline fun DefaultDialog(
|
||||||
noinline onDismiss: () -> Unit,
|
noinline onDismiss: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
|
||||||
crossinline content: @Composable ColumnScope.() -> Unit
|
crossinline content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
Dialog(
|
Dialog(onDismissRequest = onDismiss) {
|
||||||
onDismissRequest = onDismiss
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = horizontalAlignment,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(all = 48.dp)
|
.padding(all = 48.dp)
|
||||||
.background(
|
.background(
|
||||||
@@ -215,3 +215,111 @@ private inline fun DefaultDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <reified T : Enum<T>> EnumValueSelectorDialog(
|
||||||
|
noinline onDismiss: () -> Unit,
|
||||||
|
title: String,
|
||||||
|
selectedValue: T,
|
||||||
|
crossinline onValueSelected: (T) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
crossinline valueText: (T) -> String = Enum<T>::name
|
||||||
|
) {
|
||||||
|
val typography = LocalTypography.current
|
||||||
|
val colorPalette = LocalColorPalette.current
|
||||||
|
|
||||||
|
Dialog(onDismissRequest = onDismiss) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(all = 48.dp)
|
||||||
|
.background(
|
||||||
|
color = LocalColorPalette.current.elevatedBackground,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(vertical = 16.dp),
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = title,
|
||||||
|
style = typography.s.semiBold,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp, horizontal = 24.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
enumValues<T>().forEach { value ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = {
|
||||||
|
onDismiss()
|
||||||
|
onValueSelected(value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 24.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
if (selectedValue == value) {
|
||||||
|
Box(contentAlignment = Alignment.Center) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(18.dp)
|
||||||
|
.background(
|
||||||
|
color = colorPalette.primaryContainer,
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(6.dp)
|
||||||
|
.background(
|
||||||
|
color = colorPalette.onPrimaryContainer,
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(18.dp)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = colorPalette.textDisabled,
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = valueText(value),
|
||||||
|
style = typography.xs.medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = "Cancel",
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.clip(RoundedCornerShape(36.dp))
|
||||||
|
.clickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = onDismiss
|
||||||
|
)
|
||||||
|
.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
|
.align(Alignment.End)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,18 +2,26 @@ package it.vfsfitvnm.vimusic.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
import it.vfsfitvnm.route.RouteHandler
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ColorPaletteMode
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.EnumValueSelectorDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
|
||||||
|
import it.vfsfitvnm.vimusic.utils.LocalPreferences
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@@ -40,6 +48,7 @@ fun SettingsScreen() {
|
|||||||
host {
|
host {
|
||||||
val colorPalette = LocalColorPalette.current
|
val colorPalette = LocalColorPalette.current
|
||||||
val typography = LocalTypography.current
|
val typography = LocalTypography.current
|
||||||
|
val preferences = LocalPreferences.current
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -73,7 +82,97 @@ fun SettingsScreen() {
|
|||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.background(
|
||||||
|
color = colorPalette.lightBackground,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.contrast),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.size(20.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = "Appearance",
|
||||||
|
style = typography.m.semiBold,
|
||||||
|
modifier = Modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumValueSelectorEntry(
|
||||||
|
title = "Theme mode",
|
||||||
|
selectedValue = preferences.colorPaletteMode,
|
||||||
|
onValueSelected = {
|
||||||
|
preferences.colorPaletteMode = it
|
||||||
|
},
|
||||||
|
valueText = {
|
||||||
|
when (it) {
|
||||||
|
ColorPaletteMode.Light -> "Light"
|
||||||
|
ColorPaletteMode.Dark -> "Dark"
|
||||||
|
ColorPaletteMode.System -> "System"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private inline fun <reified T: Enum<T>>EnumValueSelectorEntry(
|
||||||
|
title: String,
|
||||||
|
selectedValue: T,
|
||||||
|
crossinline onValueSelected: (T) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
crossinline valueText: (T) -> String = Enum<T>::name
|
||||||
|
) {
|
||||||
|
val typography = LocalTypography.current
|
||||||
|
|
||||||
|
var isShowingDialog by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowingDialog) {
|
||||||
|
EnumValueSelectorDialog(
|
||||||
|
onDismiss = {
|
||||||
|
isShowingDialog = false
|
||||||
|
},
|
||||||
|
title = title,
|
||||||
|
selectedValue = selectedValue,
|
||||||
|
onValueSelected = onValueSelected,
|
||||||
|
valueText = valueText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.clickable(
|
||||||
|
indication = rememberRipple(bounded = true),
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = { isShowingDialog = true }
|
||||||
|
)
|
||||||
|
.padding(horizontal = 32.dp, vertical = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = title,
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = valueText(selectedValue),
|
||||||
|
style = typography.xs.semiBold.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,13 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ColorPaletteMode
|
||||||
import it.vfsfitvnm.vimusic.enums.SongCollection
|
import it.vfsfitvnm.vimusic.enums.SongCollection
|
||||||
import it.vfsfitvnm.youtubemusic.YouTube
|
import it.vfsfitvnm.youtubemusic.YouTube
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class Preferences(holder: SharedPreferences) : SharedPreferences by holder {
|
class Preferences(holder: SharedPreferences) : SharedPreferences by holder {
|
||||||
|
var colorPaletteMode by preference("colorPaletteMode", ColorPaletteMode.System)
|
||||||
var searchFilter by preference("searchFilter", YouTube.Item.Song.Filter.value)
|
var searchFilter by preference("searchFilter", YouTube.Item.Song.Filter.value)
|
||||||
var repeatMode by preference("repeatMode", Player.REPEAT_MODE_OFF)
|
var repeatMode by preference("repeatMode", Player.REPEAT_MODE_OFF)
|
||||||
var homePageSongCollection by preference("homePageSongCollection", SongCollection.MostPlayed)
|
var homePageSongCollection by preference("homePageSongCollection", SongCollection.MostPlayed)
|
||||||
|
|||||||
9
app/src/main/res/drawable/contrast.xml
Normal file
9
app/src/main/res/drawable/contrast.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="512dp"
|
||||||
|
android:height="512dp"
|
||||||
|
android:viewportWidth="512"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M256,32A224,224 0,0 0,97.61 414.39,224 224,0 1,0 414.39,97.61 222.53,222.53 0,0 0,256 32ZM64,256C64,150.13 150.13,64 256,64V448C150.13,448 64,361.87 64,256Z"/>
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user