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.activity.compose.setContent
import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Scaffold 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -30,7 +31,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.kieronquinn.monetcompat.app.MonetCompatActivity import com.kieronquinn.monetcompat.app.MonetCompatActivity
import com.owenlejeune.tvtime.extensions.WindowSizeClass import com.owenlejeune.tvtime.extensions.WindowSizeClass
import com.owenlejeune.tvtime.extensions.rememberWindowSizeClass 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.BottomNavItem
import com.owenlejeune.tvtime.ui.navigation.MainNavGraph import com.owenlejeune.tvtime.ui.navigation.MainNavGraph
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.screens.main.MediaDetailView import com.owenlejeune.tvtime.ui.screens.main.*
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.PersonDetailView
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
import com.owenlejeune.tvtime.utils.KeyboardManager import com.owenlejeune.tvtime.utils.KeyboardManager
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
@@ -115,6 +113,7 @@ class MainActivity : MonetCompatActivity() {
topBar = { topBar = {
if (windowSize != WindowSizeClass.Expanded) { if (windowSize != WindowSizeClass.Expanded) {
TopBar( TopBar(
appNavController = appNavController,
title = appBarTitle, title = appBarTitle,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
appBarActions = appBarActions appBarActions = appBarActions
@@ -151,19 +150,31 @@ class MainActivity : MonetCompatActivity() {
@Composable @Composable
private fun TopBar( private fun TopBar(
appNavController: NavHostController,
title: MutableState<String>, title: MutableState<String>,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) 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( LargeTopAppBar(
title = { Text(text = title.value) }, title = { Text(text = title.value) },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults colors = TopAppBarDefaults
.largeTopAppBarColors( .largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background, scrolledContainerColor = MaterialTheme.colorScheme.background
titleContentColor = MaterialTheme.colorScheme.primary
), ),
actions = appBarActions.value actions = {
defaultAppBarActions()
appBarActions.value(this)
}
) )
} }
@@ -316,6 +327,7 @@ class MainActivity : MonetCompatActivity() {
} }
Column { Column {
TopBar( TopBar(
appNavController = appNavController,
title = appBarTitle, title = appBarTitle,
scrollBehavior = topBarScrollBehaviour, scrollBehavior = topBarScrollBehaviour,
appBarActions = appBarActions appBarActions = appBarActions
@@ -365,12 +377,13 @@ class MainActivity : MonetCompatActivity() {
private object NavConstants { private object NavConstants {
const val ID_KEY = "id_key" const val ID_KEY = "id_key"
const val TYPE_KEY = "type_key" const val TYPE_KEY = "type_key"
const val SETTINGS_KEY = "settings_key"
} }
@Composable @Composable
private fun MainNavigationRoutes( private fun MainNavigationRoutes(
startDestination: String = MainNavItem.MainView.route, startDestination: String = MainNavItem.MainView.route,
mainNavStartRoute: String = BottomNavItem.Items[0].route, mainNavStartRoute: String = MainNavItem.Items[0].route,
appNavController: NavHostController, appNavController: NavHostController,
) { ) {
NavHost(navController = appNavController, startDestination = startDestination) { 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 USE_V4_API = "use_v4_api"
private val SHOW_BACKDROP_GALLERY = "show_backdrop_gallery" private val SHOW_BACKDROP_GALLERY = "show_backdrop_gallery"
private val USE_WALLPAPER_COLORS = "use_wallpaper_colors" 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) private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
@@ -46,6 +47,10 @@ class AppPreferences(context: Context) {
/******* Design Preferences ********/ /******* Design Preferences ********/
var darkTheme: Int
get() = preferences.getInt(DARK_THEME, 0)
set(value) { preferences.put(DARK_THEME, value) }
var useSystemColors: Boolean var useSystemColors: Boolean
get() = preferences.getBoolean(USE_SYSTEM_COLORS, true) get() = preferences.getBoolean(USE_SYSTEM_COLORS, true)
set(value) { preferences.put(USE_SYSTEM_COLORS, value) } set(value) { preferences.put(USE_SYSTEM_COLORS, value) }

View File

@@ -1,14 +1,18 @@
package com.owenlejeune.tvtime.ui.components package com.owenlejeune.tvtime.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.SliderDefaults import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.*
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -92,7 +96,9 @@ fun SliderPreference(
var sliderValue by remember { mutableStateOf(value) } var sliderValue by remember { mutableStateOf(value) }
Slider( Slider(
modifier = Modifier.fillMaxWidth().padding(start = 8.dp), modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp),
value = sliderValue, value = sliderValue,
onValueChange = { sliderValue = it }, onValueChange = { sliderValue = it },
enabled = enabled, enabled = enabled,
@@ -107,3 +113,59 @@ 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.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable 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.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.html.HtmlGenerator
import org.intellij.markdown.parser.MarkdownParser import org.intellij.markdown.parser.MarkdownParser
import kotlin.math.exp
@Composable @Composable
fun TopLevelSwitch( fun TopLevelSwitch(
@@ -960,3 +962,51 @@ 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) val name = resourceUtils.getString(stringRes)
companion object { 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) } val SearchableRoutes by lazy { listOf(Movies.route, TV.route, People.route) }
fun getByRoute(route: String?): BottomNavItem? { fun getByRoute(route: String?): BottomNavItem? {
@@ -20,8 +20,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko
Movies.route -> Movies Movies.route -> Movies
TV.route -> TV TV.route -> TV
Account.route -> Account Account.route -> Account
// Favourites.route -> Favourites Favourites.route -> Favourites
Settings.route -> Settings
else -> null 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 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 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 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 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 People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_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) { sealed class MainNavItem(val route: String) {
companion object {
val Items = listOf(MainView, DetailView, SettingsView)
}
object MainView: MainNavItem("main_route") object MainView: MainNavItem("main_route")
object DetailView: MainNavItem("detail_route") object DetailView: MainNavItem("detail_route")
object SettingsView: MainNavItem("settings_route")
} }

View File

@@ -44,13 +44,9 @@ fun MainNavGraph(
appBarActions.value = {} appBarActions.value = {}
PeopleTab(appBarTitle = appBarTitle, appNavController = appNavController) PeopleTab(appBarTitle = appBarTitle, appNavController = appNavController)
} }
// composable(BottomNavItem.Favourites.route) { composable(BottomNavItem.Favourites.route) {
// appBarActions.value = {}
// FavouritesTab()
// }
composable(BottomNavItem.Settings.route) {
appBarActions.value = {} 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.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -289,7 +290,8 @@ private fun AccountDropdownMenu(
IconButton( IconButton(
onClick = { expanded.value = true } 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( DropdownMenu(

View File

@@ -3,6 +3,7 @@ package com.owenlejeune.tvtime.ui.screens.main
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@@ -12,9 +13,9 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.TextButton import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.ResetTv import androidx.compose.material.icons.outlined.DarkMode
import androidx.compose.material.icons.filled.RestartAlt import androidx.compose.material.icons.outlined.LightMode
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
@@ -22,43 +23,179 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.runtime.* import androidx.compose.runtime.*
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.draw.clip
import androidx.compose.ui.graphics.Color 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.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.kieronquinn.monetcompat.core.MonetCompat import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.BuildConfig import com.owenlejeune.tvtime.BuildConfig
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.* 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 com.owenlejeune.tvtime.utils.SessionManager
import dev.kdrag0n.monet.factory.ColorSchemeFactory import dev.kdrag0n.monet.factory.ColorSchemeFactory
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsTab( fun SettingsTab(
appBarTitle: MutableState<String>, appNavController: NavController,
activity: AppCompatActivity, activity: AppCompatActivity,
route: String? = null,
preferences: AppPreferences = get(AppPreferences::class.java) 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( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(start = 8.dp, top = 8.dp, bottom = 8.dp, end = 24.dp) .padding(all = 24.dp),
.verticalScroll(scrollState) verticalArrangement = Arrangement.spacedBy(24.dp)
) { ) {
/******* Search Preferences ********/ TopLevelSettingsCard(
PreferenceHeading(text = stringResource(R.string.preference_heading_search)) title = stringResource(id = R.string.preference_heading_search),
subtitle = stringResource(R.string.preference_subtitle_search),
icon = Icons.Filled.Search,
settingsView = SettingsPage.SearchSettings,
appNavController = appNavController
)
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
)
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
)
}
}
@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) } val persistentSearch = remember { mutableStateOf(preferences.persistentSearch) }
SwitchPreference( SwitchPreference(
titleText = stringResource(R.string.preferences_persistent_search_title), titleText = stringResource(R.string.preferences_persistent_search_title),
@@ -81,9 +218,21 @@ fun SettingsTab(
}, },
enabled = persistentSearch.value enabled = persistentSearch.value
) )
}
/******* Design Preferences ********/ @Composable
PreferenceHeading(text = stringResource(id = R.string.preference_heading_design)) 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) } val useWallpaperColors = remember { mutableStateOf(preferences.useWallpaperColors) }
SwitchPreference( SwitchPreference(
@@ -124,11 +273,14 @@ fun SettingsTab(
} }
MonetCompat.getInstance().updateMonetColors() MonetCompat.getInstance().updateMonetColors()
}, },
enabled = (if (isSystemColorsSupported) !preferences.useSystemColors else true).and(useWallpaperColors.value) enabled = (if (isSystemColorsSupported) !preferences.useSystemColors else true).and(
useWallpaperColors.value
)
) )
val showWallpaperPicker = remember { mutableStateOf(false) } val showWallpaperPicker = remember { mutableStateOf(false) }
val wallpaperPickerModifier = if (isSystemColorsSupported && preferences.useSystemColors && useWallpaperColors.value) { val wallpaperPickerModifier =
if (isSystemColorsSupported && preferences.useSystemColors && useWallpaperColors.value) {
Modifier Modifier
} else { } else {
Modifier.clickable { showWallpaperPicker.value = true } Modifier.clickable { showWallpaperPicker.value = true }
@@ -148,18 +300,140 @@ fun SettingsTab(
if (showWallpaperPicker.value) { if (showWallpaperPicker.value) {
WallpaperPicker(showPopup = showWallpaperPicker) WallpaperPicker(showPopup = showWallpaperPicker)
} }
}
/******* Dev Preferences ********/ enum class DarkMode {
if (BuildConfig.DEBUG) { Automatic,
Divider(modifier = Modifier.padding(start = 8.dp, top = 20.dp), color = MaterialTheme.colorScheme.primaryContainer) Dark,
Text( Light
modifier = Modifier.padding(start = 8.dp, top = 20.dp), }
text = stringResource(R.string.preferences_debug_title),
style = MaterialTheme.typography.headlineMedium, @Composable
color = MaterialTheme.colorScheme.primary private fun DarkModePreferences(
) activity: AppCompatActivity,
DebugOptions() 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
}
)
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
}
)
} }
} }
@@ -242,80 +516,24 @@ private fun WallpaperPicker(
} }
} }
@Composable private sealed class SettingsPage(stringRes: Int, val route: String, val SettingsPageRenderer: @Composable (NavController, AppCompatActivity, AppPreferences) -> Unit): KoinComponent {
private fun DebugOptions(preferences: AppPreferences = get(AppPreferences::class.java)) { private val resources: ResourceUtils by inject()
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val firstLaunchTesting = remember { mutableStateOf(preferences.firstLaunchTesting) } val name = resources.getString(stringRes)
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 shouldShowPalette = remember { mutableStateOf(false) } companion object {
Text( val Pages by lazy {listOf(SearchSettings, DesignSettings, DeveloperSettings, DarkModeSettings) }
text = "Show material palette",
color = MaterialTheme.colorScheme.onBackground, fun getByRoute(route: String): SettingsPage {
modifier = Modifier return Pages.map { it.route to it }
.padding(horizontal = 8.dp, vertical = 12.dp) .find { it.first == route }
.clickable( ?.second
onClick = { ?: throw IllegalArgumentException("Invalid settings page route: $route")
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( object SearchSettings: SettingsPage(R.string.preference_heading_search, "search", @Composable { n, a, p -> SearchPreferences(p) } )
text = "Clear session", object DesignSettings: SettingsPage(R.string.preference_heading_design, "design", @Composable { n, a, p -> DesignPreferences(n, a, p) } )
color = MaterialTheme.colorScheme.onBackground, object DeveloperSettings: SettingsPage(R.string.preferences_debug_title,"dev", @Composable { n, a, p -> DevPreferences(p) } )
modifier = Modifier object DarkModeSettings: SettingsPage(R.string.preference_heading_dark_mode, "darkmode", @Composable { n, a, p -> DarkModePreferences(a, p) } )
.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
}
)
} }

View File

@@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.kieronquinn.monetcompat.core.MonetCompat import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.screens.main.DarkMode
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
private val DarkColorPalette = darkColorScheme( private val DarkColorPalette = darkColorScheme(
@@ -69,46 +70,19 @@ private val LightColorPalette = lightColorScheme(
inverseOnSurface = N1_50 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 @Composable
fun TVTimeTheme( fun TVTimeTheme(
preferences: AppPreferences = get(AppPreferences::class.java), preferences: AppPreferences = get(AppPreferences::class.java),
isDarkTheme: Boolean = isSystemInDarkTheme(),
monetCompat: MonetCompat, monetCompat: MonetCompat,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
// val colors = if(isDarkTheme) { val isDarkTheme = when(preferences.darkTheme) {
// monetCompat.darkMonetCompatScheme() DarkMode.Automatic.ordinal -> isSystemInDarkTheme()
// } else { DarkMode.Dark.ordinal -> true
// monetCompat.lightMonetCompatScheme() DarkMode.Light.ordinal -> false
// } else -> throw IllegalArgumentException("Illegal theme value ${preferences.darkTheme}")
}
val colors = when { val colors = when {
isDarkTheme && preferences.useWallpaperColors -> monetCompat.darkMonetCompatScheme() isDarkTheme && preferences.useWallpaperColors -> monetCompat.darkMonetCompatScheme()
isDarkTheme && !preferences.useWallpaperColors -> DarkColorPalette 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_title">Use wallpaper colors</string>
<string name="preferences_use_wallpaper_colors_subtitle">Use theme colors pulled from your device\'s wallpaper</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_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> </resources>