add preference to use system colors or not

This commit is contained in:
Owen LeJeune
2022-09-02 21:26:17 -04:00
parent 00e728b48c
commit 28e8680f6f
11 changed files with 66 additions and 369 deletions

View File

@@ -352,6 +352,7 @@ class MainActivity : MonetCompatActivity() {
}
MainNavGraph(
activity = this@MainActivity,
appNavController = appNavController,
navController = navController,
appBarTitle = appBarTitle,

View File

@@ -26,10 +26,12 @@ class AppPreferences(context: Context) {
private val SELECTED_COLOR = "selected_color"
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 preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
/******** Search Preferences ********/
var persistentSearch: Boolean
get() = preferences.getBoolean(PERSISTENT_SEARCH, true)
set(value) { preferences.put(PERSISTENT_SEARCH, value) }
@@ -38,6 +40,25 @@ class AppPreferences(context: Context) {
get() = preferences.getBoolean(HIDE_TITLE, false)
set(value) { preferences.put(HIDE_TITLE, value) }
var useWallpaperColors: Boolean
get() = preferences.getBoolean(USE_WALLPAPER_COLORS, true)
set(value) { preferences.put(USE_WALLPAPER_COLORS, value) }
/******* Design Preferences ********/
var useSystemColors: Boolean
get() = preferences.getBoolean(USE_SYSTEM_COLORS, true)
set(value) { preferences.put(USE_SYSTEM_COLORS, value) }
var chromaMultiplier: Double
get() = preferences.getFloat(CHROMA_MULTIPLIER, MonetCompat.chromaMultiplier.toFloat()).toDouble()
set(value) { preferences.put(CHROMA_MULTIPLIER, value) }
var selectedColor: Int
get() = preferences.getInt(SELECTED_COLOR, Int.MAX_VALUE)
set(value) { preferences.put(SELECTED_COLOR, value) }
/******* Session Tokens ********/
var guestSessionId: String
get() = preferences.getString(GUEST_SESSION, "") ?: ""
set(value) { preferences.put(GUEST_SESSION, value) }
@@ -51,11 +72,8 @@ class AppPreferences(context: Context) {
var authorizedSessionId: String
get() = preferences.getString(AUTHORIZED_SESSION, "") ?: ""
set(value) { preferences.put(AUTHORIZED_SESSION, value) }
// val usePreferences: MutableState<Boolean>
// var usePreferences: Boolean
// get() = preferences.getBoolean(USE_PREFERENCES, false)
// set(value) { preferences.put(USE_PREFERENCES, value) }
/******** Dev Preferences ********/
var firstLaunchTesting: Boolean
get() = preferences.getBoolean(FIRST_LAUNCH_TESTING, false)
set(value) { preferences.put(FIRST_LAUNCH_TESTING, value) }
@@ -64,18 +82,6 @@ class AppPreferences(context: Context) {
get() = if (BuildConfig.DEBUG) firstLaunchTesting else preferences.getBoolean(FIRST_LAUNCH, true)
set(value) { preferences.put(FIRST_LAUNCH, value) }
var useSystemColors: Boolean
get() = preferences.getBoolean(USE_SYSTEM_COLORS, true)
set(value) { preferences.put(USE_SYSTEM_COLORS, value) }
var chromaMultiplier: Double
get() = preferences.getFloat(CHROMA_MULTIPLIER, MonetCompat.chromaMultiplier.toFloat()).toDouble()
set(value) { preferences.put(CHROMA_MULTIPLIER, value) }
var selectedColor: Int
get() = preferences.getInt(SELECTED_COLOR, Int.MAX_VALUE)
set(value) { preferences.put(SELECTED_COLOR, value) }
var useV4Api: Boolean
get() = preferences.getBoolean(USE_V4_API, true)
set(value) { preferences.put(USE_V4_API, value) }

View File

@@ -71,12 +71,6 @@ fun SwitchPreference(
}
}
@Preview
@Composable
private fun SwitchPreferencePreview() {
SwitchPreference("Title", true, subtitleText = "Subtitle")
}
@Composable
fun SliderPreference(
modifier: Modifier = Modifier,

View File

@@ -196,16 +196,6 @@ fun CustomSwitch(
}
}
@Preview(name = "TopLevelSwitch", showBackground = true)
@Preview(name = "Dark TopLevelSwitch", showBackground = true, uiMode = UI_MODE_NIGHT_YES)
@Composable
fun TopLevelSwitchPreview() {
val context = LocalContext.current
TopLevelSwitch("This is a switch") { isChecked ->
Toast.makeText(context, "Switch changed to $isChecked", Toast.LENGTH_SHORT).show()
}
}
@Composable
fun SearchFab(
focusSearchBar: MutableState<Boolean> = mutableStateOf(false),
@@ -221,12 +211,6 @@ fun SearchFab(
}
}
@Preview
@Composable
fun SearchFabPreview() {
SearchFab()
}
@Composable
fun MinLinesText(
text: String,
@@ -438,12 +422,6 @@ fun ChipGroup(
}
}
@Preview
@Composable
fun ChipPreview() {
BoxyChip("Test Chip")
}
/**
* @param progress The progress of the ring as a value between 0 and 1
*/
@@ -566,28 +544,6 @@ fun RoundedTextField(
}
}
@Preview(widthDp = 300, heightDp = 40)
@Composable
private fun RoundedEditTextPreview() {
RoundedTextField(
value = "this is my value",
onValueChange = {},
placeHolder = "this is my placeholder",
trailingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = ""
)
},
leadingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = ""
)
}
)
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun FullScreenThumbnailVideoPlayer(
@@ -689,18 +645,6 @@ fun CircleBackgroundColorImage(
}
}
@Composable
@Preview
private fun CircleBackgroundColorImagePreview() {
CircleBackgroundColorImage(
size = 100.dp,
backgroundColor = MaterialTheme.colorScheme.inverseSurface,
painter = painterResource(id = R.drawable.ic_rating_star),
colorFilter = ColorFilter.tint(color = RatingSelected),
imageSize = DpSize(width = 70.dp, height = 70.dp)
)
}
@Composable
fun CircleBackgroundColorImage(
size: Dp,

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.ui.navigation
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
@@ -18,42 +19,9 @@ object NavConstants {
const val TYPE_KEY = "type_key"
}
@Composable
fun MainNavigationRoutes(
navController: NavHostController,
startDestination: String = MainNavItem.MainView.route
) {
NavHost(navController = navController, startDestination = startDestination) {
composable(MainNavItem.MainView.route) {
MainAppView(appNavController = navController)
}
composable(
MainNavItem.DetailView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"),
arguments = listOf(
navArgument(NavConstants.ID_KEY) { type = NavType.IntType },
navArgument(NavConstants.TYPE_KEY) { type = NavType.EnumType(MediaViewType::class.java) }
)
) { navBackStackEntry ->
val args = navBackStackEntry.arguments
val mediaType = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType
if (mediaType != MediaViewType.PERSON) {
MediaDetailView(
appNavController = navController,
itemId = args.getInt(NavConstants.ID_KEY),
type = mediaType
)
} else {
PersonDetailView(
appNavController = navController,
personId = args.getInt(NavConstants.ID_KEY)
)
}
}
}
}
@Composable
fun MainNavGraph(
activity: AppCompatActivity,
appNavController: NavHostController,
navController: NavHostController,
appBarTitle: MutableState<String>,
@@ -82,7 +50,7 @@ fun MainNavGraph(
// }
composable(BottomNavItem.Settings.route) {
appBarActions.value = {}
SettingsTab(appBarTitle = appBarTitle)
SettingsTab(appBarTitle = appBarTitle, activity = activity)
}
}
}

View File

@@ -1,213 +0,0 @@
package com.owenlejeune.tvtime.ui.screens.main
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Scaffold
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.pager.ExperimentalPagerApi
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.RoundedTextField
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.utils.KeyboardManager
import org.koin.java.KoinJavaComponent.get
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class)
@Composable
fun MainAppView(appNavController: NavHostController, preferences: AppPreferences = get(AppPreferences::class.java)) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val appBarTitle = rememberSaveable { mutableStateOf(BottomNavItem.getByRoute(currentRoute)?.name ?: BottomNavItem.Items[0].name) }
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
}
val focusRequester = remember { FocusRequester() }
val focusSearchBar = rememberSaveable { mutableStateOf(false) }
val searchableScreens = listOf(BottomNavItem.Movies.route, BottomNavItem.TV.route, BottomNavItem.People.route)
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>( {} ) }
// todo - scroll state not remember when returing from detail screen
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
backgroundColor = MaterialTheme.colorScheme.background,
bottomBar = {
BottomNavBar(
navController = navController,
appBarTitle = appBarTitle
)
},
topBar = {
if (currentRoute in searchableScreens) {
SearchTopBar(
title = appBarTitle,
scrollBehavior = scrollBehavior,
requestSearchFocus = focusSearchBar,
focusRequester = focusRequester
)
} else {
TopBar(
title = appBarTitle,
scrollBehavior = scrollBehavior,
appBarActions = appBarActions
)
}
},
floatingActionButton = {
if (currentRoute in searchableScreens && !preferences.persistentSearch && !focusSearchBar.value) {
SearchFab(
focusSearchBar = focusSearchBar,
focusRequester = focusRequester
)
}
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
MainNavGraph(appNavController = appNavController, navController = navController, appBarTitle = appBarTitle, appBarActions = appBarActions)
}
}
}
@Composable
private fun TopBar(
title: MutableState<String>,
scrollBehavior: TopAppBarScrollBehavior,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
) {
LargeTopAppBar(
title = { Text(text = title.value) },
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults
.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.primary
),
actions = appBarActions.value
)
}
@Composable
private fun SearchTopBar(
title: MutableState<String>,
scrollBehavior: TopAppBarScrollBehavior,
requestSearchFocus: MutableState<Boolean> = remember { mutableStateOf(false) },
focusRequester: FocusRequester = remember { FocusRequester() },
preferences: AppPreferences = get(AppPreferences::class.java)
) {
SmallTopAppBar(
title = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
val hasSearchFocus = rememberSaveable { mutableStateOf(requestSearchFocus.value) }
if (!requestSearchFocus.value && !hasSearchFocus.value && !(preferences.persistentSearch && preferences.hideTitle)) {
Text(text = title.value)
}
if (requestSearchFocus.value || preferences.persistentSearch) {
var textState by remember { mutableStateOf("") }
val basePadding = 8.dp
RoundedTextField(
modifier = Modifier
.padding(
end = if (hasSearchFocus.value || preferences.hideTitle) (basePadding * 3) else basePadding,
start = if (hasSearchFocus.value || preferences.hideTitle) 0.dp else basePadding
)
.height(35.dp)
.onFocusChanged { focusState ->
hasSearchFocus.value = focusState.isFocused
},
requestFocus = requestSearchFocus.value,
focusRequester = focusRequester,
value = textState,
onValueChange = { textState = it },
placeHolder = stringResource(id = R.string.search_placeholder, title.value),
trailingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = stringResource(R.string.search_icon_content_descriptor),
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary)
)
}
)
}
}
},
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults
.largeTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.primary
)
)
val context = LocalContext.current
val keyboardManager = KeyboardManager.getInstance(context)
keyboardManager.attachKeyboardDismissListener {
requestSearchFocus.value = false
}
}
@Composable
private fun BottomNavBar(navController: NavController, appBarTitle: MutableState<String>) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
NavigationBar {
BottomNavItem.Items.forEach { item ->
NavigationBarItem(
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
label = { Text(item.name) },
selected = currentRoute == item.route,
onClick = {
onBottomAppBarItemClicked(
navController = navController,
appBarTitle = appBarTitle,
item = item
)
}
)
}
}
}
private fun onBottomAppBarItemClicked(
navController: NavController,
appBarTitle: MutableState<String>,
item: BottomNavItem
) {
appBarTitle.value = item.name
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { screenRoute ->
popUpTo(screenRoute) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}

View File

@@ -88,13 +88,4 @@ fun MediaTabs(
HorizontalPager(count = tabs.size, state = pagerState) { page ->
tabs[page].screen(appNavController, mediaViewType, tabs[page])
}
}
@OptIn(ExperimentalPagerApi::class)
@Preview(showBackground = true)
@Composable
fun MediaTabsPreview() {
val tabs = MediaTabNavItem.MovieItems
val pagerState = rememberPagerState()
MediaTabs(tabs = tabs, pagerState = pagerState, MediaViewType.MOVIE)
}

View File

@@ -2,6 +2,7 @@ package com.owenlejeune.tvtime.ui.screens.main
import android.os.Build
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -42,6 +43,7 @@ import org.koin.java.KoinJavaComponent.get
@Composable
fun SettingsTab(
appBarTitle: MutableState<String>,
activity: AppCompatActivity,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
appBarTitle.value = stringResource(id = R.string.nav_settings_title)
@@ -54,23 +56,7 @@ fun SettingsTab(
.padding(start = 8.dp, top = 8.dp, bottom = 8.dp, end = 24.dp)
.verticalScroll(scrollState)
) {
// val usePreferences = remember { mutableStateOf(preferences.usePreferences) }
// TopLevelSwitch(
// text = "Enable Preferences",
// checkedState = usePreferences,
// onCheckChanged = { isChecked ->
// usePreferences.value = isChecked
// preferences.usePreferences = isChecked
// }
// )
//
// Column(
// modifier = Modifier
// .fillMaxWidth()
// .wrapContentHeight()
// .padding(start = 8.dp, top = 8.dp, bottom = 8.dp, end = 24.dp)
// .verticalScroll()
// ) {
/******* Search Preferences ********/
PreferenceHeading(text = stringResource(R.string.preference_heading_search))
val persistentSearch = remember { mutableStateOf(preferences.persistentSearch) }
@@ -96,20 +82,35 @@ fun SettingsTab(
enabled = persistentSearch.value
)
/******* 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()
}
)
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
)
}
@@ -123,11 +124,11 @@ fun SettingsTab(
}
MonetCompat.getInstance().updateMonetColors()
},
enabled = if (isSystemColorsSupported) !preferences.useSystemColors else true
enabled = (if (isSystemColorsSupported) !preferences.useSystemColors else true).and(useWallpaperColors.value)
)
val showWallpaperPicker = remember { mutableStateOf(false) }
val wallpaperPickerModifier = if (isSystemColorsSupported && preferences.useSystemColors) {
val wallpaperPickerModifier = if (isSystemColorsSupported && preferences.useSystemColors && useWallpaperColors.value) {
Modifier
} else {
Modifier.clickable { showWallpaperPicker.value = true }
@@ -148,8 +149,9 @@ fun SettingsTab(
WallpaperPicker(showPopup = showWallpaperPicker)
}
/******* Dev Preferences ********/
if (BuildConfig.DEBUG) {
Divider(modifier = Modifier.padding(start = 8.dp, top = 20.dp))
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),

View File

@@ -137,12 +137,3 @@ private fun SmallTabIndicator(
)
}
@OptIn(ExperimentalPagerApi::class)
@Preview(showBackground = true)
@Composable
fun TabsPreview() {
val tabs = MediaTabNavItem.MovieItems
val pagerState = rememberPagerState()
Tabs(tabs = tabs, pagerState = pagerState)
}

View File

@@ -8,6 +8,8 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.preferences.AppPreferences
import org.koin.java.KoinJavaComponent.get
private val DarkColorPalette = darkColorScheme(
primary = A1_200,
@@ -97,14 +99,22 @@ private val LightColorPalette = lightColorScheme(
@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 colors = if(isDarkTheme) {
// monetCompat.darkMonetCompatScheme()
// } else {
// monetCompat.lightMonetCompatScheme()
// }
val colors = when {
isDarkTheme && preferences.useWallpaperColors -> monetCompat.darkMonetCompatScheme()
isDarkTheme && !preferences.useWallpaperColors -> DarkColorPalette
!isDarkTheme && preferences.useWallpaperColors -> monetCompat.lightMonetCompatScheme()
!isDarkTheme && !preferences.useWallpaperColors -> LightColorPalette
else -> throw Exception("Error getting theme colors, should never happen")
}
androidx.compose.material3.MaterialTheme(

View File

@@ -114,4 +114,7 @@
<!-- onboarding -->
<string name="get_started">Get started</string>
<string name="example_page_desc">This is an example</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="preference_system_colors_subtitle">Use wallpaper colors chosen by your device</string>
</resources>