Add theme mode selector in SettingsScreen

This commit is contained in:
vfsfitvnm
2022-06-05 14:52:03 +02:00
parent 116e8889ac
commit 5e18d7e22a
6 changed files with 245 additions and 12 deletions

View File

@@ -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(

View File

@@ -0,0 +1,7 @@
package it.vfsfitvnm.vimusic.enums
enum class ColorPaletteMode {
Light,
Dark,
System
}

View File

@@ -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)
)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -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)

View 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>