add option to reorder home tabs

This commit is contained in:
Owen LeJeune
2022-09-09 15:51:44 -04:00
parent dd0a395bf9
commit 266ade86a1
12 changed files with 492 additions and 106 deletions

View File

@@ -58,7 +58,7 @@ class MainActivity : MonetCompatActivity() {
SessionManager.initialize()
}
var mainNavStartRoute = BottomNavItem.Items[0].route
var mainNavStartRoute = BottomNavItem.SortedItems[0].route
intent.data?.let {
when (it.host) {
getString(R.string.intent_route_auth_return) -> mainNavStartRoute = BottomNavItem.Account.route
@@ -82,7 +82,7 @@ class MainActivity : MonetCompatActivity() {
@Composable
private fun AppScaffold(
appNavController: NavHostController,
mainNavStartRoute: String = BottomNavItem.Items[0].route,
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
val windowSize = rememberWindowSizeClass()
@@ -91,7 +91,7 @@ class MainActivity : MonetCompatActivity() {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val appBarTitle = rememberSaveable { mutableStateOf(BottomNavItem.getByRoute(currentRoute)?.name ?: BottomNavItem.Items[0].name) }
val appBarTitle = rememberSaveable { mutableStateOf(BottomNavItem.getByRoute(currentRoute)?.name ?: BottomNavItem.SortedItems[0].name) }
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) {
@@ -101,8 +101,6 @@ class MainActivity : MonetCompatActivity() {
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) }
val fab = remember { mutableStateOf<@Composable () -> Unit>({}) }
// todo - scroll state not remember when returing from detail screen
Scaffold (
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
@@ -170,18 +168,25 @@ class MainActivity : MonetCompatActivity() {
}
@Composable
private fun BottomNavBar(navController: NavController, appBarTitle: MutableState<String>) {
private fun BottomNavBar(
navController: NavController,
appBarTitle: MutableState<String>,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
NavigationBar {
BottomNavItem.Items.forEach { item ->
BottomNavItem.SortedItems.forEach { item ->
NavigationBarItem(
modifier = Modifier
.padding(4.dp)
.clip(RoundedCornerShape(24.dp)),
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
label = { Text(item.name) },
label = {
val name = if (preferences.showBottomTabLabels) item.name else " "
Text(text = name)
},
selected = currentRoute == item.route,
onClick = {
onBottomAppBarItemClicked(
@@ -225,7 +230,7 @@ class MainActivity : MonetCompatActivity() {
topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) {
if (windowSize == WindowSizeClass.Expanded) {
DualColumnMainContent(
@@ -256,7 +261,7 @@ class MainActivity : MonetCompatActivity() {
fab: MutableState<@Composable () -> Unit>,
appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) {
MainMediaView(
appNavController = appNavController,
@@ -276,7 +281,8 @@ class MainActivity : MonetCompatActivity() {
topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
@@ -284,10 +290,10 @@ class MainActivity : MonetCompatActivity() {
Row(modifier = Modifier.fillMaxSize()) {
NavigationRail {
Spacer(modifier = Modifier.weight(1f))
BottomNavItem.Items.forEach { item ->
BottomNavItem.SortedItems.forEach { item ->
NavigationRailItem(
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
label = { Text(item.name) },
label = { if (preferences.showBottomTabLabels) Text(item.name) },
selected = currentRoute == item.route,
onClick = {
onBottomAppBarItemClicked(
@@ -326,7 +332,7 @@ class MainActivity : MonetCompatActivity() {
fab: MutableState<@Composable () -> Unit>,
appBarTitle: MutableState<String>,
appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) {
Column {
val navBackStackEntry by navController.currentBackStackEntryAsState()
@@ -413,13 +419,6 @@ class MainActivity : MonetCompatActivity() {
)
) {
it.arguments?.let { arguments ->
// val title = arguments.getString(NavConstants.SEARCH_TITLE_KEY) ?: ""
// val type = if (preferences.multiSearch) {
// MediaViewType.MIXED
// } else {
// MediaViewType[arguments.getInt(NavConstants.SEARCH_ID_KEY)]
// }
val (type, title) = if (preferences.multiSearch) {
Pair(MediaViewType.MIXED, "")
} else {

View File

@@ -36,6 +36,16 @@ fun <T: Any> LazyListScope.listItems(
}
}
fun <T: Any?> LazyListScope.listItems(
items: List<T?>,
key: (T?) -> Any,
itemContent: @Composable (value: T?) -> Unit
) {
items(items.size, key = { key(items[it]) }) { index ->
itemContent(items[index])
}
}
fun <T: Any> LazyListScope.lazyPagingItems(
lazyPagingItems: LazyPagingItems<T>,
itemContent: @Composable LazyItemScope.(value: T?) -> Unit

View File

@@ -9,10 +9,15 @@ import com.owenlejeune.tvtime.utils.SessionManager
class AppPreferences(context: Context) {
enum class DarkMode {
Automatic,
Dark,
Light
}
companion object {
private val PREF_FILE = "tvtime_shared_preferences"
// private val USE_PREFERENCES = "use_android_12_colors"
private val PERSISTENT_SEARCH = "persistent_search"
private val GUEST_SESSION = "guest_session_id"
private val AUTHORIZED_SESSION = "authorized_session_id"
@@ -27,38 +32,50 @@ class AppPreferences(context: Context) {
private val USE_WALLPAPER_COLORS = "use_wallpaper_colors"
private val DARK_THEME = "dark_theme"
private val MULTI_SEARCH = "multi_search"
private val MOVIES_TAB_POSITION = "movies_tab_position"
private val TV_TAB_POSITION = "tv_tab_position"
private val PEOPLE_TAB_POSITION = "people_tab_position"
private val ACCOUNT_TAB_POSITION = "account_tab_position"
private val SHOW_BTAB_LABELS = "show_btab_labels"
}
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
/******** Search Preferences ********/
val showSearchBarDefault: Boolean = false
var showSearchBar: Boolean
get() = preferences.getBoolean(PERSISTENT_SEARCH, true)
get() = preferences.getBoolean(PERSISTENT_SEARCH, showSearchBarDefault)
set(value) { preferences.put(PERSISTENT_SEARCH, value) }
val multiSearchDefault: Boolean = true
var multiSearch: Boolean
get() = preferences.getBoolean(MULTI_SEARCH, false)
get() = preferences.getBoolean(MULTI_SEARCH, multiSearchDefault)
set(value) { preferences.put(MULTI_SEARCH, value) }
/******* Design Preferences ********/
val useWallpaperColorsDefault: Boolean = true
var useWallpaperColors: Boolean
get() = preferences.getBoolean(USE_WALLPAPER_COLORS, true)
get() = preferences.getBoolean(USE_WALLPAPER_COLORS, useWallpaperColorsDefault)
set(value) { preferences.put(USE_WALLPAPER_COLORS, value) }
val darkThemeDefault: Int = DarkMode.Automatic.ordinal
var darkTheme: Int
get() = preferences.getInt(DARK_THEME, 0)
get() = preferences.getInt(DARK_THEME, darkThemeDefault)
set(value) { preferences.put(DARK_THEME, value) }
val useSystemColorsDefault: Boolean = true
var useSystemColors: Boolean
get() = preferences.getBoolean(USE_SYSTEM_COLORS, true)
get() = preferences.getBoolean(USE_SYSTEM_COLORS, useSystemColorsDefault)
set(value) { preferences.put(USE_SYSTEM_COLORS, value) }
val chromeMultiplyerDefault: Double = MonetCompat.chromaMultiplier.toFloat().toDouble()
var chromaMultiplier: Double
get() = preferences.getFloat(CHROMA_MULTIPLIER, MonetCompat.chromaMultiplier.toFloat()).toDouble()
get() = preferences.getFloat(CHROMA_MULTIPLIER, chromeMultiplyerDefault.toFloat()).toDouble()
set(value) { preferences.put(CHROMA_MULTIPLIER, value) }
val selectedColorDefault: Int = Int.MAX_VALUE
var selectedColor: Int
get() = preferences.getInt(SELECTED_COLOR, Int.MAX_VALUE)
get() = preferences.getInt(SELECTED_COLOR, selectedColorDefault)
set(value) { preferences.put(SELECTED_COLOR, value) }
/******* Session Tokens ********/
@@ -76,21 +93,50 @@ class AppPreferences(context: Context) {
get() = preferences.getString(AUTHORIZED_SESSION, "") ?: ""
set(value) { preferences.put(AUTHORIZED_SESSION, value) }
/******** Home Screen Preferences ********/
val moviesTabPositionDefault: Int = 0
var moviesTabPosition: Int
get() = preferences.getInt(MOVIES_TAB_POSITION, moviesTabPositionDefault)
set(value) { preferences.put(MOVIES_TAB_POSITION, value) }
val tvTabPositionDefault: Int = 1
var tvTabPosition: Int
get() = preferences.getInt(TV_TAB_POSITION, tvTabPositionDefault)
set(value) { preferences.put(TV_TAB_POSITION, value) }
val peopleTabPositionDefault: Int = 2
var peopleTabPosition: Int
get() = preferences.getInt(PEOPLE_TAB_POSITION, peopleTabPositionDefault)
set(value) { preferences.put(PEOPLE_TAB_POSITION, value) }
val accountTabPositionDefault: Int = 3
var accountTabPosition: Int
get() = preferences.getInt(ACCOUNT_TAB_POSITION, accountTabPositionDefault)
set(value) { preferences.put(ACCOUNT_TAB_POSITION, value) }
val showBottomTabLabelsDefault: Boolean = true
var showBottomTabLabels: Boolean
get() = preferences.getBoolean(SHOW_BTAB_LABELS, showBottomTabLabelsDefault)
set(value) { preferences.put(SHOW_BTAB_LABELS, value) }
/******** Dev Preferences ********/
val firstLaunchTestingDefault: Boolean = false
var firstLaunchTesting: Boolean
get() = preferences.getBoolean(FIRST_LAUNCH_TESTING, false)
get() = preferences.getBoolean(FIRST_LAUNCH_TESTING, firstLaunchTestingDefault)
set(value) { preferences.put(FIRST_LAUNCH_TESTING, value) }
var firstLaunch: Boolean
get() = if (BuildConfig.DEBUG) firstLaunchTesting else preferences.getBoolean(FIRST_LAUNCH, true)
set(value) { preferences.put(FIRST_LAUNCH, value) }
val useV4ApiDefault: Boolean = false
var useV4Api: Boolean
get() = preferences.getBoolean(USE_V4_API, true)
get() = preferences.getBoolean(USE_V4_API, useV4ApiDefault)
set(value) { preferences.put(USE_V4_API, value) }
var showBackdropGallery: Boolean// = true
get() = preferences.getBoolean(SHOW_BACKDROP_GALLERY, true)
val showBackdropGalleryDefault: Boolean = true
var showBackdropGallery: Boolean
get() = preferences.getBoolean(SHOW_BACKDROP_GALLERY, showBackdropGalleryDefault)
set(value) { preferences.put(SHOW_BACKDROP_GALLERY, value) }
/********* Helpers ********/

View File

@@ -1,35 +1,48 @@
package com.owenlejeune.tvtime.ui.navigation
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.utils.ResourceUtils
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): KoinComponent {
sealed class BottomNavItem(
stringRes: Int,
val icon: Int,
val route: String,
private val orderGetter: (AppPreferences) -> Int,
private val orderSetter: (AppPreferences, Int) -> Unit
): KoinComponent {
private val appPreferences: AppPreferences by inject()
private val resourceUtils: ResourceUtils by inject()
val name = resourceUtils.getString(stringRes)
var order: Int
get() = orderGetter.invoke(appPreferences)
set(value) { orderSetter.invoke(appPreferences, value) }
companion object {
val Items by lazy { listOf(Movies, TV, People, Account) }
val SearchableRoutes by lazy { listOf(Movies.route, TV.route, People.route) }
val SortedItems
get() = Items.filter { it.order > -1 }.sortedBy { it.order }.ifEmpty { Items }
val Items by lazy {
listOf(Movies, TV, People, Account)
}
fun getByRoute(route: String?): BottomNavItem? {
return when (route) {
Movies.route -> Movies
TV.route -> TV
Account.route -> Account
Favourites.route -> Favourites
else -> null
}
}
}
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 People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route")
object Movies: BottomNavItem(R.string.nav_movies_title, R.drawable.ic_movie, "movies_route", { it.moviesTabPosition }, { p, i -> p.moviesTabPosition = i } )
object TV: BottomNavItem(R.string.nav_tv_title, R.drawable.ic_tv, "tv_route", { it.tvTabPosition }, { p, i -> p.tvTabPosition = i } )
object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route", { it.accountTabPosition }, { p, i -> p.accountTabPosition = i } )
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route", { it.peopleTabPosition }, { p, i -> p.peopleTabPosition = i } )
}

View File

@@ -6,11 +6,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.owenlejeune.tvtime.ui.screens.main.MediaDetailView
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.*
@@ -27,7 +24,7 @@ fun MainNavGraph(
fab: MutableState<@Composable () -> Unit>,
appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
startDestination: String = BottomNavItem.Items[0].route
startDestination: String = BottomNavItem.SortedItems[0].route
) {
NavHost(navController = navController, startDestination = startDestination) {
composable(BottomNavItem.Movies.route) {
@@ -59,9 +56,5 @@ fun MainNavGraph(
fab = fab
)
}
composable(BottomNavItem.Favourites.route) {
appBarActions.value = {}
FavouritesTab()
}
}
}

View File

@@ -328,7 +328,7 @@ private fun TvSearchResultView(
backdropModel = { TmdbUtils.getFullBackdropPath(result.backdropPath) },
additionalDetails = {
listOf(
"${TmdbUtils.releaseYearFromData(result.releaseDate)} ${context.getString(R.string.search_result_tv_services)}",
"${TmdbUtils.releaseYearFromData(result.releaseDate)} ${context.getString(R.string.search_result_tv_series)}",
cast.value?.joinToString(separator = ", ") { it.name } ?: ""
)
}

View File

@@ -1,24 +0,0 @@
package com.owenlejeune.tvtime.ui.screens.main
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun FavouritesTab() {
Column(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Favourites Tab",
color = MaterialTheme.colorScheme.onBackground
)
}
}

View File

@@ -4,8 +4,10 @@ import android.os.Build
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.IconButton
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
@@ -23,13 +25,19 @@ 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.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.MainNavItem
import com.owenlejeune.tvtime.ui.views.HomeTabRecyclerAdapter
import com.owenlejeune.tvtime.ui.views.ItemMoveCallback
import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.SessionManager
import kotlinx.coroutines.launch
@@ -52,6 +60,8 @@ fun SettingsTab(
}
val appBarTitle = remember { mutableStateOf("") }
val defaultRestoreAction = ::resetAllPreferences
val restoreAction = remember { mutableStateOf<(AppPreferences) -> Unit>(defaultRestoreAction) }
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -72,6 +82,16 @@ fun SettingsTab(
contentDescription = stringResource(id = R.string.content_description_back_button)
)
}
},
actions = {
IconButton(onClick = {
restoreAction.value(preferences)
}) {
Icon(
imageVector = Icons.Filled.SettingsBackupRestore,
contentDescription = stringResource(R.string.preferences_restore_content_description)
)
}
}
)
}
@@ -82,14 +102,16 @@ fun SettingsTab(
modifier = Modifier
.fillMaxSize()
.padding(all = 24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
verticalArrangement = Arrangement.spacedBy(18.dp)
) {
SettingsPage.getByRoute(route).apply {
appBarTitle.value = name
restoreAction.value = resetPreferencesHandler
SettingsPageRenderer(appNavController, activity, preferences)
}
}
} else {
restoreAction.value = ::resetAllPreferences
SettingsTabView(
appNavController = appNavController,
appBarTitle = appBarTitle
@@ -128,6 +150,14 @@ private fun SettingsTabView(
appNavController = appNavController
)
TopLevelSettingsCard(
title = stringResource(id = R.string.preference_heading_home_screen),
subtitle = stringResource(R.string.preference_subtitle_home_screen),
icon = Icons.Filled.Home,
settingsView = SettingsPage.HomeScreenSettings,
appNavController = appNavController
)
TopLevelSettingsCard(
title = stringResource(id = R.string.preferences_debug_title),
subtitle = stringResource(R.string.preference_subtitle_debug),
@@ -196,8 +226,8 @@ private fun SearchPreferences(
val multiSearch = remember { mutableStateOf(preferences.multiSearch) }
SwitchPreference(
titleText = "Multi Search",
subtitleText = "Search across movies, TV, and people at the same time",
titleText = stringResource(R.string.preference_multi_search_title),
subtitleText = stringResource(R.string.preference_multi_search_subtitle),
checkState = multiSearch.value,
onCheckedChange = { isChecked ->
multiSearch.value = isChecked
@@ -288,12 +318,6 @@ private fun DesignPreferences(
}
}
enum class DarkMode {
Automatic,
Dark,
Light
}
@Composable
private fun DarkModePreferences(
activity: AppCompatActivity,
@@ -308,35 +332,79 @@ private fun DarkModePreferences(
activity.recreate()
}
PreferenceHeading(text = "Automatic")
PreferenceHeading(text = stringResource(R.string.preference_dark_mode_automatic_heading))
RadioButtonPreference(
selected = isSelected(DarkMode.Automatic.ordinal),
title = "Follow system",
selected = isSelected(AppPreferences.DarkMode.Automatic.ordinal),
title = stringResource(R.string.preference_dark_mode_follow_system_label),
icon = Icons.Filled.Brightness6,
onClick = {
onChangeState(DarkMode.Automatic.ordinal)
onChangeState(AppPreferences.DarkMode.Automatic.ordinal)
}
)
PreferenceHeading(text = "Manual")
PreferenceHeading(text = stringResource(R.string.preference_dark_mode_maual_heading))
RadioButtonPreference(
selected = isSelected(DarkMode.Light.ordinal),
title = "Light mode",
selected = isSelected(AppPreferences.DarkMode.Light.ordinal),
title = stringResource(R.string.preference_dark_mode_light_mode_label),
icon = Icons.Outlined.LightMode,
onClick = {
onChangeState(DarkMode.Light.ordinal)
onChangeState(AppPreferences.DarkMode.Light.ordinal)
}
)
RadioButtonPreference(
selected = isSelected(DarkMode.Dark.ordinal),
title = "Dark mode",
selected = isSelected(AppPreferences.DarkMode.Dark.ordinal),
title = stringResource(R.string.preference_dark_mode_dark_mode_label),
icon = Icons.Outlined.DarkMode,
onClick = {
onChangeState(DarkMode.Dark.ordinal)
onChangeState(AppPreferences.DarkMode.Dark.ordinal)
}
)
}
@Composable
private fun HomeScreenPreferences(
preferences: AppPreferences = get(AppPreferences::class.java)
) {
PreferenceHeading(text = stringResource(R.string.preference_look_and_feel_heading))
val showTabLabels = remember { mutableStateOf(preferences.showBottomTabLabels) }
SwitchPreference(
titleText = stringResource(R.string.preference_show_text_labels_title),
subtitleText = stringResource(R.string.preference_show_text_labels_subtitle),
checkState = showTabLabels.value,
onCheckedChange = { isChecked ->
showTabLabels.value = isChecked
preferences.showBottomTabLabels = isChecked
}
)
PreferenceHeading(text = stringResource(R.string.preference_home_tab_order_heading))
Box(modifier = Modifier
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(10.dp)
)
.padding(horizontal = 12.dp)
) {
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
val mAdapter = HomeTabRecyclerAdapter()
val touchCallback = ItemMoveCallback(mAdapter)
val touchHelper = ItemTouchHelper(touchCallback)
touchHelper.attachToRecyclerView(this)
adapter = mAdapter
}
},
update = { }
)
}
}
@Composable
private fun DevPreferences(
preferences: AppPreferences = get(AppPreferences::class.java)
@@ -513,13 +581,64 @@ private fun WallpaperPicker(
}
}
private sealed class SettingsPage(stringRes: Int, val route: String, val SettingsPageRenderer: @Composable (NavController, AppCompatActivity, AppPreferences) -> Unit): KoinComponent {
private fun resetAllPreferences(preferences: AppPreferences) {
resetSearchPreferences(preferences = preferences)
resetDesignPreferences(preferences = preferences)
resetHomeScreenPreferences(preferences = preferences)
resetDevModePreference(preferences = preferences)
}
private fun resetSearchPreferences(preferences: AppPreferences) {
preferences.showSearchBar = preferences.showSearchBarDefault
preferences.multiSearch = preferences.multiSearchDefault
}
private fun resetDesignPreferences(preferences: AppPreferences) {
preferences.useWallpaperColors = preferences.useWallpaperColorsDefault
preferences.useSystemColors = preferences.useSystemColorsDefault
preferences.chromaMultiplier = preferences.chromeMultiplyerDefault
preferences.selectedColor = preferences.selectedColorDefault
resetDarkModePreferences(preferences = preferences)
}
private fun resetDarkModePreferences(preferences: AppPreferences) {
preferences.darkTheme = preferences.darkThemeDefault
}
private fun resetDevModePreference(preferences: AppPreferences) {
preferences.firstLaunchTesting = preferences.firstLaunchTestingDefault
preferences.useV4Api = preferences.useV4ApiDefault
preferences.showBackdropGallery = preferences.showBackdropGalleryDefault
}
private fun resetHomeScreenPreferences(preferences: AppPreferences) {
preferences.moviesTabPosition = preferences.moviesTabPositionDefault
preferences.tvTabPosition = preferences.tvTabPositionDefault
preferences.peopleTabPosition = preferences.peopleTabPositionDefault
preferences.accountTabPosition = preferences.accountTabPositionDefault
preferences.showBottomTabLabels = preferences.showBottomTabLabelsDefault
}
private sealed class SettingsPage(
stringRes: Int,
val route: String,
val SettingsPageRenderer: @Composable (NavController, AppCompatActivity, AppPreferences) -> Unit,
val resetPreferencesHandler: (AppPreferences) -> Unit
): KoinComponent {
private val resources: ResourceUtils by inject()
val name = resources.getString(stringRes)
companion object {
val Pages by lazy {listOf(SearchSettings, DesignSettings, DeveloperSettings, DarkModeSettings) }
val Pages by lazy {
listOf(
SearchSettings,
DesignSettings,
DeveloperSettings,
DarkModeSettings,
HomeScreenSettings
)
}
fun getByRoute(route: String): SettingsPage {
return Pages.map { it.route to it }
@@ -529,8 +648,34 @@ private sealed class SettingsPage(stringRes: Int, val route: String, val Setting
}
}
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) } )
object SearchSettings: SettingsPage(
R.string.preference_heading_search,
"search",
@Composable { _, _, p -> SearchPreferences(p) },
::resetSearchPreferences
)
object DesignSettings: SettingsPage(
R.string.preference_heading_design,
"design",
@Composable { n, a, p -> DesignPreferences(n, a, p) },
::resetDesignPreferences
)
object HomeScreenSettings: SettingsPage(
R.string.preference_heading_home_screen,
"home",
@Composable { _, _, p -> HomeScreenPreferences(p) },
::resetHomeScreenPreferences
)
object DeveloperSettings: SettingsPage(
R.string.preferences_debug_title,
"dev",
@Composable { _, _, p -> DevPreferences(p) },
::resetDevModePreference
)
object DarkModeSettings: SettingsPage(
R.string.preference_heading_dark_mode,
"darkmode",
@Composable { _, a, p -> DarkModePreferences(a, p) },
::resetDarkModePreferences
)
}

View File

@@ -9,7 +9,6 @@ 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(
@@ -77,9 +76,9 @@ fun TVTimeTheme(
content: @Composable () -> Unit
) {
val isDarkTheme = when(preferences.darkTheme) {
DarkMode.Automatic.ordinal -> isSystemInDarkTheme()
DarkMode.Dark.ordinal -> true
DarkMode.Light.ordinal -> false
AppPreferences.DarkMode.Automatic.ordinal -> isSystemInDarkTheme()
AppPreferences.DarkMode.Dark.ordinal -> true
AppPreferences.DarkMode.Light.ordinal -> false
else -> throw IllegalArgumentException("Illegal theme value ${preferences.darkTheme}")
}

View File

@@ -0,0 +1,137 @@
package com.owenlejeune.tvtime.ui.views
import android.view.ViewGroup
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DragIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.recyclerview.widget.RecyclerView
import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
class HomeTabRecyclerAdapter: RecyclerView.Adapter<HomeTabRecyclerAdapter.TabViewHolder>(),
ItemMoveCallback.ItemTouchHelperContract, KoinComponent
{
class TabViewHolder(itemView: ComposeView): RecyclerView.ViewHolder(itemView)
private val pages: MutableList<BottomNavItem?>
private val indexOfDivider
get() = pages.indexOf(null)
init {
val visiblePages = BottomNavItem.Items.filter { it.order > -1 }.sortedBy { it.order }
val hiddenPages = BottomNavItem.Items.filter { it.order < 0 }
pages = ArrayList<BottomNavItem?>().apply {
addAll(visiblePages)
add(null)
addAll(hiddenPages)
}
}
override fun getItemCount(): Int {
return pages.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabViewHolder {
val composeView = ComposeView(get()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
}
return TabViewHolder(composeView)
}
override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
val itemView = holder.itemView
if (itemView is ComposeView) {
val page = pages[position]
itemView.setContent {
if (page == null) {
ItemDivider()
} else {
ItemRow(page = page)
}
}
}
}
@Composable
private fun ItemRow(page: BottomNavItem) {
Row(
modifier = Modifier
.height(50.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(id = page.icon),
contentDescription = page.name
)
Text(
modifier = Modifier
.padding(start = 8.dp),
text = page.name,
color = MaterialTheme.colorScheme.onBackground,
fontSize = 16.sp
)
Spacer(modifier = Modifier.weight(1f))
Icon(
modifier = Modifier
.size(24.dp),
imageVector = Icons.Filled.DragIndicator,
contentDescription = null
)
}
}
@Composable
private fun ItemDivider() {
Row(modifier = Modifier.height(50.dp)) {
Text(
text = "Hidden",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(end = 8.dp)
)
Divider(
modifier = Modifier.align(Alignment.CenterVertically),
color = MaterialTheme.colorScheme.onBackground,
thickness = 2.dp
)
}
}
override fun onRowClear(myViewHolder: RecyclerView.ViewHolder) {
myViewHolder.itemView.alpha = 1f
}
override fun onRowMoved(fromPosition: Int, toPosition: Int) {
if (indexOfDivider == 1 && toPosition > 1) {
return
}
pages.add(
toPosition,
pages.removeAt(fromPosition)
)
pages.forEachIndexed { index, bottomNavItem ->
bottomNavItem?.order = if (index > indexOfDivider) -1 else index
}
notifyItemMoved(fromPosition, toPosition)
}
override fun onRowSelected(myViewHolder: RecyclerView.ViewHolder) {
myViewHolder.itemView.alpha = 0.6f
}
}

View File

@@ -0,0 +1,54 @@
package com.owenlejeune.tvtime.ui.views
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class ItemMoveCallback(private val mAdapter: ItemTouchHelperContract): ItemTouchHelper.Callback() {
override fun isLongPressDragEnabled(): Boolean {
return true
}
override fun isItemViewSwipeEnabled(): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// left blank
}
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val flags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
return makeMovementFlags(flags, 0)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
mAdapter.onRowMoved(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
return true
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder?.let { mAdapter.onRowSelected(viewHolder) }
}
super.onSelectedChanged(viewHolder, actionState)
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
mAdapter.onRowClear(viewHolder)
}
interface ItemTouchHelperContract {
fun onRowMoved(fromPosition: Int, toPosition: Int)
fun onRowSelected(myViewHolder: RecyclerView.ViewHolder)
fun onRowClear(myViewHolder: RecyclerView.ViewHolder)
}
}

View File

@@ -66,6 +66,20 @@
<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>
<string name="preference_heading_home_screen">Home Screen</string>
<string name="preference_subtitle_home_screen">Home screen layout options</string>
<string name="preference_dark_mode_automatic_heading">Automatic</string>
<string name="preference_dark_mode_maual_heading">Manual</string>
<string name="preference_dark_mode_follow_system_label">Follow system</string>
<string name="preference_dark_mode_light_mode_label">Light mode</string>
<string name="preference_dark_mode_dark_mode_label">Dark mode</string>
<string name="preference_look_and_feel_heading">Look &amp; Feel</string>
<string name="preferences_restore_content_description">Restore settings</string>
<string name="preference_multi_search_title">Multi Search</string>
<string name="preference_multi_search_subtitle">Search across movies, TV, and people at the same time</string>
<string name="preference_show_text_labels_title">Show tab text labels</string>
<string name="preference_show_text_labels_subtitle">Show text labels for tab items in the bottom tab bar or navigation rail</string>
<string name="preference_home_tab_order_heading">Home Tab Order</string>
<!-- video type -->
<string name="video_type_clip">Clips</string>
@@ -122,6 +136,6 @@
<string name="example_page_desc">This is an example</string>
<!-- search results -->
<string name="search_result_tv_services">TV Series</string>
<string name="search_result_tv_series">TV Series</string>
<string name="no_search_results">No search results found</string>
</resources>