redesign settings pages

This commit is contained in:
Owen LeJeune
2022-09-03 20:53:48 -04:00
parent 28e8680f6f
commit dcaa6ed361
11 changed files with 570 additions and 226 deletions

View File

@@ -4,10 +4,11 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Scaffold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -30,7 +31,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.kieronquinn.monetcompat.app.MonetCompatActivity
import com.owenlejeune.tvtime.extensions.WindowSizeClass
import com.owenlejeune.tvtime.extensions.rememberWindowSizeClass
@@ -40,9 +40,7 @@ import com.owenlejeune.tvtime.ui.components.SearchFab
import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
import com.owenlejeune.tvtime.ui.navigation.MainNavGraph
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.screens.main.MediaDetailView
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.PersonDetailView
import com.owenlejeune.tvtime.ui.screens.main.*
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
import com.owenlejeune.tvtime.utils.KeyboardManager
import com.owenlejeune.tvtime.utils.SessionManager
@@ -115,6 +113,7 @@ class MainActivity : MonetCompatActivity() {
topBar = {
if (windowSize != WindowSizeClass.Expanded) {
TopBar(
appNavController = appNavController,
title = appBarTitle,
scrollBehavior = scrollBehavior,
appBarActions = appBarActions
@@ -151,19 +150,31 @@ class MainActivity : MonetCompatActivity() {
@Composable
private fun TopBar(
appNavController: NavHostController,
title: MutableState<String>,
scrollBehavior: TopAppBarScrollBehavior,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
) {
val defaultAppBarActions: @Composable RowScope.() -> Unit = {
IconButton(
onClick = {
appNavController.navigate(MainNavItem.SettingsView.route)
}
) {
Icon(imageVector = Icons.Filled.Settings, contentDescription = stringResource(id = R.string.nav_settings_title))
}
}
LargeTopAppBar(
title = { Text(text = title.value) },
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults
.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.primary
scrolledContainerColor = MaterialTheme.colorScheme.background
),
actions = appBarActions.value
actions = {
defaultAppBarActions()
appBarActions.value(this)
}
)
}
@@ -316,6 +327,7 @@ class MainActivity : MonetCompatActivity() {
}
Column {
TopBar(
appNavController = appNavController,
title = appBarTitle,
scrollBehavior = topBarScrollBehaviour,
appBarActions = appBarActions
@@ -365,12 +377,13 @@ class MainActivity : MonetCompatActivity() {
private object NavConstants {
const val ID_KEY = "id_key"
const val TYPE_KEY = "type_key"
const val SETTINGS_KEY = "settings_key"
}
@Composable
private fun MainNavigationRoutes(
startDestination: String = MainNavItem.MainView.route,
mainNavStartRoute: String = BottomNavItem.Items[0].route,
mainNavStartRoute: String = MainNavItem.Items[0].route,
appNavController: NavHostController,
) {
NavHost(navController = appNavController, startDestination = startDestination) {
@@ -399,6 +412,22 @@ class MainActivity : MonetCompatActivity() {
)
}
}
composable(
MainNavItem.SettingsView.route.plus("/{${NavConstants.SETTINGS_KEY}}"),
arguments = listOf(
navArgument(NavConstants.SETTINGS_KEY) { type = NavType.StringType }
)
) {
val route = it.arguments?.getString(NavConstants.SETTINGS_KEY)
SettingsTab(
appNavController = appNavController,
activity = this@MainActivity,
route = route
)
}
composable(MainNavItem.SettingsView.route) {
SettingsTab(appNavController = appNavController, activity = this@MainActivity)
}
}
}

View File

@@ -27,6 +27,7 @@ class AppPreferences(context: Context) {
private val USE_V4_API = "use_v4_api"
private val SHOW_BACKDROP_GALLERY = "show_backdrop_gallery"
private val USE_WALLPAPER_COLORS = "use_wallpaper_colors"
private val DARK_THEME = "dark_theme"
}
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
@@ -46,6 +47,10 @@ class AppPreferences(context: Context) {
/******* Design Preferences ********/
var darkTheme: Int
get() = preferences.getInt(DARK_THEME, 0)
set(value) { preferences.put(DARK_THEME, value) }
var useSystemColors: Boolean
get() = preferences.getBoolean(USE_SYSTEM_COLORS, true)
set(value) { preferences.put(USE_SYSTEM_COLORS, value) }

View File

@@ -1,14 +1,18 @@
package com.owenlejeune.tvtime.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -92,7 +96,9 @@ fun SliderPreference(
var sliderValue by remember { mutableStateOf(value) }
Slider(
modifier = Modifier.fillMaxWidth().padding(start = 8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp),
value = sliderValue,
onValueChange = { sliderValue = it },
enabled = enabled,
@@ -106,4 +112,60 @@ fun SliderPreference(
)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RadioButtonPreference(
modifier: Modifier = Modifier,
enabled: Boolean = true,
selected: Boolean,
onClick: () -> Unit,
title: String,
icon: ImageVector,
titleTextColor: Color = MaterialTheme.colorScheme.onBackground,
disabledTextColor: Color = MaterialTheme.colorScheme.outline
) {
Row(
// modifier = modifier
// .clickable(
// enabled = enabled,
// onClick = onClick
// )
// .semantics { role = Role.RadioButton }
modifier = modifier
.padding(all = 8.dp)
.selectable(
selected = selected,
onClick = onClick,
role = Role.RadioButton
)
) {
Icon(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(end = 16.dp),
imageVector = icon,
contentDescription = title,
tint = MaterialTheme.colorScheme.primary
)
val titleColor = if (enabled) titleTextColor else disabledTextColor
Text(
modifier = Modifier
.align(Alignment.CenterVertically),
text = title,
style = MaterialTheme.typography.titleLarge,
color = titleColor,
fontSize = 20.sp
)
Spacer(modifier = Modifier.weight(1f))
RadioButton(
modifier = Modifier.align(Alignment.CenterVertically),
selected = selected,
onClick = null
)
}
}

View File

@@ -21,6 +21,7 @@ import androidx.compose.material.OutlinedTextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -75,6 +76,7 @@ import kotlinx.coroutines.launch
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser
import kotlin.math.exp
@Composable
fun TopLevelSwitch(
@@ -959,4 +961,52 @@ fun CenteredIconCircle(
}
}
}
}
@Composable
fun <T> Spinner(
modifier: Modifier = Modifier,
list: List<Pair<String, T>>,
preselected: Pair<String, T>,
onSelectionChanged: (Pair<String, T>) -> Unit
) {
var selected by remember { mutableStateOf(preselected) }
var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) {
Column {
OutlinedTextField(
value = selected.first,
onValueChange = { },
trailingIcon = { Icon(Icons.Outlined.ArrowDropDown, null) },
readOnly = true,
modifier = Modifier.clickable(
onClick = {
expanded = true
}
)
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
list.forEach { entry ->
DropdownMenuItem(
text = { Text(text = entry.first) },
onClick = {
selected = entry
expanded = false
onSelectionChanged(selected)
}
)
}
}
}
}
}
@Composable
@Preview
fun SpinnerPreview() {
Spinner(list = listOf("T" to "T", "Q" to "Q", "F" to "F"), modifier = Modifier.width(300.dp), preselected = "T" to "T", onSelectionChanged = {})
}

View File

@@ -12,7 +12,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko
val name = resourceUtils.getString(stringRes)
companion object {
val Items by lazy { listOf(Movies, TV, People, Account, Settings) }
val Items by lazy { listOf(Movies, TV, People, Account) }
val SearchableRoutes by lazy { listOf(Movies.route, TV.route, People.route) }
fun getByRoute(route: String?): BottomNavItem? {
@@ -20,8 +20,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko
Movies.route -> Movies
TV.route -> TV
Account.route -> Account
// Favourites.route -> Favourites
Settings.route -> Settings
Favourites.route -> Favourites
else -> null
}
}
@@ -30,8 +29,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko
object Movies: BottomNavItem(R.string.nav_movies_title, R.drawable.ic_movie, "movies_route")
object TV: BottomNavItem(R.string.nav_tv_title, R.drawable.ic_tv, "tv_route")
object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route")
// object Favourites: BottomNavItem(R.string.nav_favourites_title, R.drawable.ic_favorite, "favourites_route")
object Settings: BottomNavItem(R.string.nav_settings_title, R.drawable.ic_settings, "settings_route")
object Favourites: BottomNavItem(R.string.nav_favourites_title, R.drawable.ic_favorite, "favourites_route")
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route")
}

View File

@@ -2,7 +2,12 @@ package com.owenlejeune.tvtime.ui.navigation
sealed class MainNavItem(val route: String) {
companion object {
val Items = listOf(MainView, DetailView, SettingsView)
}
object MainView: MainNavItem("main_route")
object DetailView: MainNavItem("detail_route")
object SettingsView: MainNavItem("settings_route")
}

View File

@@ -44,13 +44,9 @@ fun MainNavGraph(
appBarActions.value = {}
PeopleTab(appBarTitle = appBarTitle, appNavController = appNavController)
}
// composable(BottomNavItem.Favourites.route) {
// appBarActions.value = {}
// FavouritesTab()
// }
composable(BottomNavItem.Settings.route) {
composable(BottomNavItem.Favourites.route) {
appBarActions.value = {}
SettingsTab(appBarTitle = appBarTitle, activity = activity)
FavouritesTab()
}
}
}

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
@@ -289,7 +290,8 @@ private fun AccountDropdownMenu(
IconButton(
onClick = { expanded.value = true }
) {
Icon(imageVector = Icons.Filled.MoreVert, contentDescription = null)
// Icon(imageVector = Icons.Filled.MoreVert, contentDescription = null)
Icon(imageVector = Icons.Filled.AccountCircle, contentDescription = stringResource(id = R.string.nav_account_title))
}
DropdownMenu(

View File

@@ -3,6 +3,7 @@ package com.owenlejeune.tvtime.ui.screens.main
import android.os.Build
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -12,9 +13,9 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.ResetTv
import androidx.compose.material.icons.filled.RestartAlt
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.DarkMode
import androidx.compose.material.icons.outlined.LightMode
import androidx.compose.material3.*
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider
@@ -22,144 +23,417 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.Icon
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.BuildConfig
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.*
import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.SessionManager
import dev.kdrag0n.monet.factory.ColorSchemeFactory
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.java.KoinJavaComponent.get
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsTab(
appBarTitle: MutableState<String>,
appNavController: NavController,
activity: AppCompatActivity,
route: String? = null,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
appBarTitle.value = stringResource(id = R.string.nav_settings_title)
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
}
val scrollState = rememberScrollState()
val appBarTitle = remember { mutableStateOf("") }
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults
.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background
),
title = { Text(text = appBarTitle.value) },
navigationIcon = {
IconButton(
onClick = { appNavController.popBackStack() }
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.content_description_back_button)
)
}
}
)
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
if (route != null) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(all = 24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
SettingsPage.getByRoute(route).apply {
appBarTitle.value = name
SettingsPageRenderer(appNavController, activity, preferences)
}
}
} else {
SettingsTabView(
appNavController = appNavController,
appBarTitle = appBarTitle
)
}
}
}
}
@Composable
private fun SettingsTabView(
appNavController: NavController,
appBarTitle: MutableState<String>
) {
appBarTitle.value = stringResource(id = R.string.nav_settings_title)
Column(
modifier = Modifier
.fillMaxSize()
.padding(start = 8.dp, top = 8.dp, bottom = 8.dp, end = 24.dp)
.verticalScroll(scrollState)
.padding(all = 24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
/******* Search Preferences ********/
PreferenceHeading(text = stringResource(R.string.preference_heading_search))
val persistentSearch = remember { mutableStateOf(preferences.persistentSearch) }
SwitchPreference(
titleText = stringResource(R.string.preferences_persistent_search_title),
subtitleText = stringResource(R.string.preferences_persistent_search_subtitle),
checkState = persistentSearch.value,
onCheckedChange = { isChecked ->
persistentSearch.value = isChecked
preferences.persistentSearch = isChecked
}
TopLevelSettingsCard(
title = stringResource(id = R.string.preference_heading_search),
subtitle = stringResource(R.string.preference_subtitle_search),
icon = Icons.Filled.Search,
settingsView = SettingsPage.SearchSettings,
appNavController = appNavController
)
val hideTitle = remember { mutableStateOf(preferences.hideTitle) }
SwitchPreference(
titleText = stringResource(R.string.preferences_hide_heading_title),
subtitleText = stringResource(R.string.preferences_hide_heading_subtitle),
checkState = hideTitle.value,
onCheckedChange = { isChecked ->
hideTitle.value = isChecked
preferences.hideTitle = isChecked
},
enabled = persistentSearch.value
TopLevelSettingsCard(
title = stringResource(id = R.string.preference_heading_design),
subtitle = stringResource(R.string.preference_subtitle_design),
icon = Icons.Filled.Palette,
settingsView = SettingsPage.DesignSettings,
appNavController = appNavController
)
/******* Design Preferences ********/
PreferenceHeading(text = stringResource(id = R.string.preference_heading_design))
val useWallpaperColors = remember { mutableStateOf(preferences.useWallpaperColors) }
SwitchPreference(
titleText = stringResource(R.string.preferences_use_wallpaper_colors_title),
subtitleText = stringResource(R.string.preferences_use_wallpaper_colors_subtitle),
checkState = useWallpaperColors.value,
onCheckedChange = { isChecked ->
useWallpaperColors.value = isChecked
preferences.useWallpaperColors = isChecked
activity.recreate()
}
TopLevelSettingsCard(
title = stringResource(id = R.string.preferences_debug_title),
subtitle = stringResource(R.string.preference_subtitle_debug),
icon = Icons.Filled.DeveloperBoard,
settingsView = SettingsPage.DeveloperSettings,
appNavController = appNavController
)
}
}
val isSystemColorsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
if (isSystemColorsSupported) {
val useSystemColors = remember { mutableStateOf(preferences.useSystemColors) }
SwitchPreference(
titleText = stringResource(id = R.string.preference_system_colors_heading),
subtitleText = stringResource(R.string.preference_system_colors_subtitle),
checkState = useSystemColors.value,
onCheckedChange = { isChecked ->
useSystemColors.value = isChecked
preferences.useSystemColors = isChecked
MonetCompat.useSystemColorsOnAndroid12 = isChecked
MonetCompat.getInstance().updateMonetColors()
},
enabled = useWallpaperColors.value
)
}
SliderPreference(
titleText = stringResource(id = R.string.preference_chroma_factor_heading),
value = preferences.chromaMultiplier.toFloat() * 50f,
onValueChangeFinished = { value ->
with((value / 50f).toDouble()) {
preferences.chromaMultiplier = this
MonetCompat.chromaMultiplier = this
@Composable
private fun TopLevelSettingsCard(
title: String,
subtitle: String? = null,
icon: ImageVector,
settingsView: SettingsPage,
appNavController: NavController
) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.clickable(
onClick = {
appNavController.navigate("${MainNavItem.SettingsView.route}/${settingsView.route}")
}
)
) {
Icon(
imageVector = icon,
contentDescription = subtitle,
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(end = 16.dp),
tint = MaterialTheme.colorScheme.primary
)
Column(
modifier = Modifier
.align(Alignment.CenterVertically)
.weight(1f)
) {
val titleColor = MaterialTheme.colorScheme.onBackground
val subtitleColor = MaterialTheme.colorScheme.onSurfaceVariant
Text(text = title, style = MaterialTheme.typography.titleLarge, color = titleColor, fontSize = 20.sp)
subtitle?.let {
Text(text = subtitle, style = MaterialTheme.typography.bodyMedium, color = subtitleColor)
}
}
}
}
@Composable
private fun SearchPreferences(
preferences: AppPreferences = get(AppPreferences::class.java)
) {
val persistentSearch = remember { mutableStateOf(preferences.persistentSearch) }
SwitchPreference(
titleText = stringResource(R.string.preferences_persistent_search_title),
subtitleText = stringResource(R.string.preferences_persistent_search_subtitle),
checkState = persistentSearch.value,
onCheckedChange = { isChecked ->
persistentSearch.value = isChecked
preferences.persistentSearch = isChecked
}
)
val hideTitle = remember { mutableStateOf(preferences.hideTitle) }
SwitchPreference(
titleText = stringResource(R.string.preferences_hide_heading_title),
subtitleText = stringResource(R.string.preferences_hide_heading_subtitle),
checkState = hideTitle.value,
onCheckedChange = { isChecked ->
hideTitle.value = isChecked
preferences.hideTitle = isChecked
},
enabled = persistentSearch.value
)
}
@Composable
private fun DesignPreferences(
appNavController: NavController,
activity: AppCompatActivity,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
TopLevelSettingsCard(
title = stringResource(id = R.string.preference_heading_dark_mode),
subtitle = stringResource(R.string.preference_subtitle_dark_mode),
icon = Icons.Outlined.DarkMode,
settingsView = SettingsPage.DarkModeSettings,
appNavController = appNavController
)
val useWallpaperColors = remember { mutableStateOf(preferences.useWallpaperColors) }
SwitchPreference(
titleText = stringResource(R.string.preferences_use_wallpaper_colors_title),
subtitleText = stringResource(R.string.preferences_use_wallpaper_colors_subtitle),
checkState = useWallpaperColors.value,
onCheckedChange = { isChecked ->
useWallpaperColors.value = isChecked
preferences.useWallpaperColors = isChecked
activity.recreate()
}
)
val isSystemColorsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
if (isSystemColorsSupported) {
val useSystemColors = remember { mutableStateOf(preferences.useSystemColors) }
SwitchPreference(
titleText = stringResource(id = R.string.preference_system_colors_heading),
subtitleText = stringResource(R.string.preference_system_colors_subtitle),
checkState = useSystemColors.value,
onCheckedChange = { isChecked ->
useSystemColors.value = isChecked
preferences.useSystemColors = isChecked
MonetCompat.useSystemColorsOnAndroid12 = isChecked
MonetCompat.getInstance().updateMonetColors()
},
enabled = (if (isSystemColorsSupported) !preferences.useSystemColors else true).and(useWallpaperColors.value)
enabled = useWallpaperColors.value
)
}
val showWallpaperPicker = remember { mutableStateOf(false) }
val wallpaperPickerModifier = if (isSystemColorsSupported && preferences.useSystemColors && useWallpaperColors.value) {
SliderPreference(
titleText = stringResource(id = R.string.preference_chroma_factor_heading),
value = preferences.chromaMultiplier.toFloat() * 50f,
onValueChangeFinished = { value ->
with((value / 50f).toDouble()) {
preferences.chromaMultiplier = this
MonetCompat.chromaMultiplier = this
}
MonetCompat.getInstance().updateMonetColors()
},
enabled = (if (isSystemColorsSupported) !preferences.useSystemColors else true).and(
useWallpaperColors.value
)
)
val showWallpaperPicker = remember { mutableStateOf(false) }
val wallpaperPickerModifier =
if (isSystemColorsSupported && preferences.useSystemColors && useWallpaperColors.value) {
Modifier
} else {
Modifier.clickable { showWallpaperPicker.value = true }
}
Text(
text = stringResource(id = R.string.preference_wallpaper_color_heading),
style = MaterialTheme.typography.titleLarge,
fontSize = 20.sp,
color = if (isSystemColorsSupported && preferences.useSystemColors) {
MaterialTheme.colorScheme.outline
} else {
MaterialTheme.colorScheme.onBackground
},
modifier = wallpaperPickerModifier
.padding(start = 8.dp)
Text(
text = stringResource(id = R.string.preference_wallpaper_color_heading),
style = MaterialTheme.typography.titleLarge,
fontSize = 20.sp,
color = if (isSystemColorsSupported && preferences.useSystemColors) {
MaterialTheme.colorScheme.outline
} else {
MaterialTheme.colorScheme.onBackground
},
modifier = wallpaperPickerModifier
.padding(start = 8.dp)
)
if (showWallpaperPicker.value) {
WallpaperPicker(showPopup = showWallpaperPicker)
}
}
enum class DarkMode {
Automatic,
Dark,
Light
}
@Composable
private fun DarkModePreferences(
activity: AppCompatActivity,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
val selectedValue = remember { mutableStateOf(preferences.darkTheme) }
val isSelected: (Int) -> Boolean = { selectedValue.value == it }
val onChangeState: (Int) -> Unit = {
selectedValue.value = it
preferences.darkTheme = it
activity.recreate()
}
PreferenceHeading(text = "Automatic")
RadioButtonPreference(
selected = isSelected(DarkMode.Automatic.ordinal),
title = "Follow system",
icon = Icons.Filled.Brightness6,
onClick = {
onChangeState(DarkMode.Automatic.ordinal)
}
)
PreferenceHeading(text = "Manual")
RadioButtonPreference(
selected = isSelected(DarkMode.Light.ordinal),
title = "Light mode",
icon = Icons.Outlined.LightMode,
onClick = {
onChangeState(DarkMode.Light.ordinal)
}
)
RadioButtonPreference(
selected = isSelected(DarkMode.Dark.ordinal),
title = "Dark mode",
icon = Icons.Outlined.DarkMode,
onClick = {
onChangeState(DarkMode.Dark.ordinal)
}
)
}
@Composable
private fun DevPreferences(
preferences: AppPreferences = get(AppPreferences::class.java)
) {
if (BuildConfig.DEBUG) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val firstLaunchTesting = remember { mutableStateOf(preferences.firstLaunchTesting) }
SwitchPreference(
titleText = "Always show onboarding flow",
subtitleText = "Show onboarding flow on each launch",
checkState = firstLaunchTesting.value,
onCheckedChange = { isChecked ->
firstLaunchTesting.value = isChecked
preferences.firstLaunchTesting = isChecked
}
)
if (showWallpaperPicker.value) {
WallpaperPicker(showPopup = showWallpaperPicker)
val shouldShowPalette = remember { mutableStateOf(false) }
Text(
text = "Show material palette",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.clickable(
onClick = {
shouldShowPalette.value = true
}
)
)
if (shouldShowPalette.value) {
AlertDialog(
modifier = Modifier.padding(12.dp),
title = { Text(text = "Palette") },
text = { PaletteView() },
dismissButton = {
TextButton(onClick = { shouldShowPalette.value = false }) {
Text("Dismiss")
}
},
confirmButton = {},
onDismissRequest = { shouldShowPalette.value = false }
)
}
/******* Dev Preferences ********/
if (BuildConfig.DEBUG) {
Divider(modifier = Modifier.padding(start = 8.dp, top = 20.dp), color = MaterialTheme.colorScheme.primaryContainer)
Text(
modifier = Modifier.padding(start = 8.dp, top = 20.dp),
text = stringResource(R.string.preferences_debug_title),
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary
)
DebugOptions()
}
Text(
text = "Clear session",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.clickable(
onClick = {
preferences.guestSessionId = ""
coroutineScope.launch {
SessionManager.clearSession {
Toast
.makeText(context, "Cleared session: $it", Toast.LENGTH_SHORT)
.show()
}
SessionManager.clearSessionV4 {
Toast
.makeText(
context,
"Cleared session v4: $it",
Toast.LENGTH_SHORT
)
.show()
}
}
}
)
)
val useV4Api = remember { mutableStateOf(preferences.useV4Api) }
SwitchPreference(
titleText = "Use v4 API",
checkState = useV4Api.value,
onCheckedChange = { isChecked ->
useV4Api.value = isChecked
preferences.useV4Api = isChecked
}
)
}
}
@@ -242,80 +516,24 @@ private fun WallpaperPicker(
}
}
@Composable
private fun DebugOptions(preferences: AppPreferences = get(AppPreferences::class.java)) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
private sealed class SettingsPage(stringRes: Int, val route: String, val SettingsPageRenderer: @Composable (NavController, AppCompatActivity, AppPreferences) -> Unit): KoinComponent {
private val resources: ResourceUtils by inject()
val firstLaunchTesting = remember { mutableStateOf(preferences.firstLaunchTesting) }
SwitchPreference(
titleText = "Always show onboarding flow",
subtitleText = "Show onboarding flow on each launch",
checkState = firstLaunchTesting.value,
onCheckedChange = { isChecked ->
firstLaunchTesting.value = isChecked
preferences.firstLaunchTesting = isChecked
val name = resources.getString(stringRes)
companion object {
val Pages by lazy {listOf(SearchSettings, DesignSettings, DeveloperSettings, DarkModeSettings) }
fun getByRoute(route: String): SettingsPage {
return Pages.map { it.route to it }
.find { it.first == route }
?.second
?: throw IllegalArgumentException("Invalid settings page route: $route")
}
)
val shouldShowPalette = remember { mutableStateOf(false) }
Text(
text = "Show material palette",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.clickable(
onClick = {
shouldShowPalette.value = true
}
)
)
if (shouldShowPalette.value) {
AlertDialog(
modifier = Modifier.padding(12.dp),
title = { Text(text = "Palette") },
text = { PaletteView() },
dismissButton = {
TextButton(onClick = { shouldShowPalette.value = false }) {
Text("Dismiss")
}
},
confirmButton = {},
onDismissRequest = { shouldShowPalette.value = false }
)
}
Text(
text = "Clear session",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 12.dp)
.clickable(
onClick = {
preferences.guestSessionId = ""
coroutineScope.launch {
SessionManager.clearSession {
Toast
.makeText(context, "Cleared session: $it", Toast.LENGTH_SHORT)
.show()
}
SessionManager.clearSessionV4 {
Toast
.makeText(context, "Cleared session v4: $it", Toast.LENGTH_SHORT)
.show()
}
}
}
)
)
val useV4Api = remember { mutableStateOf(preferences.useV4Api) }
SwitchPreference(
titleText = "Use v4 API",
checkState = useV4Api.value,
onCheckedChange = { isChecked ->
useV4Api.value = isChecked
preferences.useV4Api = isChecked
}
)
object SearchSettings: SettingsPage(R.string.preference_heading_search, "search", @Composable { n, a, p -> SearchPreferences(p) } )
object DesignSettings: SettingsPage(R.string.preference_heading_design, "design", @Composable { n, a, p -> DesignPreferences(n, a, p) } )
object DeveloperSettings: SettingsPage(R.string.preferences_debug_title,"dev", @Composable { n, a, p -> DevPreferences(p) } )
object DarkModeSettings: SettingsPage(R.string.preference_heading_dark_mode, "darkmode", @Composable { n, a, p -> DarkModePreferences(a, p) } )
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.screens.main.DarkMode
import org.koin.java.KoinJavaComponent.get
private val DarkColorPalette = darkColorScheme(
@@ -69,46 +70,19 @@ private val LightColorPalette = lightColorScheme(
inverseOnSurface = N1_50
)
//@Composable
//fun TVTimeTheme(
// isDarkTheme: Boolean = isSystemInDarkTheme(),
// isDynamicColor: Boolean = true,
// content: @Composable () -> Unit
//) {
// val dynamicColor = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
// val colorScheme = when {
// dynamicColor && isDarkTheme -> {
// dynamicDarkColorScheme(LocalContext.current)
// }
// dynamicColor && !isDarkTheme -> {
// dynamicLightColorScheme(LocalContext.current)
// }
// isDarkTheme -> DarkColorPalette
// else -> LightColorPalette
// }
//
// val systemUiController = rememberSystemUiController()
// systemUiController.setSystemBarsColor(colorScheme.background, !isDarkTheme)
//
// MaterialTheme(
// colorScheme = colorScheme,
// typography = Typography,
// content = content
// )
//}
@Composable
fun TVTimeTheme(
preferences: AppPreferences = get(AppPreferences::class.java),
isDarkTheme: Boolean = isSystemInDarkTheme(),
monetCompat: MonetCompat,
content: @Composable () -> Unit
) {
// val colors = if(isDarkTheme) {
// monetCompat.darkMonetCompatScheme()
// } else {
// monetCompat.lightMonetCompatScheme()
// }
val isDarkTheme = when(preferences.darkTheme) {
DarkMode.Automatic.ordinal -> isSystemInDarkTheme()
DarkMode.Dark.ordinal -> true
DarkMode.Light.ordinal -> false
else -> throw IllegalArgumentException("Illegal theme value ${preferences.darkTheme}")
}
val colors = when {
isDarkTheme && preferences.useWallpaperColors -> monetCompat.darkMonetCompatScheme()
isDarkTheme && !preferences.useWallpaperColors -> DarkColorPalette

View File

@@ -117,4 +117,9 @@
<string name="preferences_use_wallpaper_colors_title">Use wallpaper colors</string>
<string name="preferences_use_wallpaper_colors_subtitle">Use theme colors pulled from your device\'s wallpaper</string>
<string name="preference_system_colors_subtitle">Use wallpaper colors chosen by your device</string>
<string name="preference_subtitle_search">Search bar view settings</string>
<string name="preference_subtitle_design">Design and theming options</string>
<string name="preference_subtitle_debug">Secret developer options</string>
<string name="preference_heading_dark_mode">Dark mode</string>
<string name="preference_subtitle_dark_mode">Light, dark, or auto</string>
</resources>