mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-20 02:30:53 -05:00
add option to reorder home tabs
This commit is contained in:
@@ -58,7 +58,7 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
SessionManager.initialize()
|
SessionManager.initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
var mainNavStartRoute = BottomNavItem.Items[0].route
|
var mainNavStartRoute = BottomNavItem.SortedItems[0].route
|
||||||
intent.data?.let {
|
intent.data?.let {
|
||||||
when (it.host) {
|
when (it.host) {
|
||||||
getString(R.string.intent_route_auth_return) -> mainNavStartRoute = BottomNavItem.Account.route
|
getString(R.string.intent_route_auth_return) -> mainNavStartRoute = BottomNavItem.Account.route
|
||||||
@@ -82,7 +82,7 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun AppScaffold(
|
private fun AppScaffold(
|
||||||
appNavController: NavHostController,
|
appNavController: NavHostController,
|
||||||
mainNavStartRoute: String = BottomNavItem.Items[0].route,
|
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
|
||||||
preferences: AppPreferences = get(AppPreferences::class.java)
|
preferences: AppPreferences = get(AppPreferences::class.java)
|
||||||
) {
|
) {
|
||||||
val windowSize = rememberWindowSizeClass()
|
val windowSize = rememberWindowSizeClass()
|
||||||
@@ -91,7 +91,7 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
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 decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||||
val scrollBehavior = remember(decayAnimationSpec) {
|
val scrollBehavior = remember(decayAnimationSpec) {
|
||||||
@@ -101,8 +101,6 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) }
|
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) }
|
||||||
val fab = remember { mutableStateOf<@Composable () -> Unit>({}) }
|
val fab = remember { mutableStateOf<@Composable () -> Unit>({}) }
|
||||||
|
|
||||||
// todo - scroll state not remember when returing from detail screen
|
|
||||||
|
|
||||||
Scaffold (
|
Scaffold (
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -170,18 +168,25 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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 navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
BottomNavItem.Items.forEach { item ->
|
BottomNavItem.SortedItems.forEach { item ->
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.clip(RoundedCornerShape(24.dp)),
|
.clip(RoundedCornerShape(24.dp)),
|
||||||
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
|
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,
|
selected = currentRoute == item.route,
|
||||||
onClick = {
|
onClick = {
|
||||||
onBottomAppBarItemClicked(
|
onBottomAppBarItemClicked(
|
||||||
@@ -225,7 +230,7 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
topBarScrollBehaviour: TopAppBarScrollBehavior,
|
topBarScrollBehaviour: TopAppBarScrollBehavior,
|
||||||
appBarTitle: MutableState<String>,
|
appBarTitle: MutableState<String>,
|
||||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
||||||
mainNavStartRoute: String = BottomNavItem.Items[0].route
|
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
|
||||||
) {
|
) {
|
||||||
if (windowSize == WindowSizeClass.Expanded) {
|
if (windowSize == WindowSizeClass.Expanded) {
|
||||||
DualColumnMainContent(
|
DualColumnMainContent(
|
||||||
@@ -256,7 +261,7 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
fab: MutableState<@Composable () -> Unit>,
|
fab: MutableState<@Composable () -> Unit>,
|
||||||
appBarTitle: MutableState<String>,
|
appBarTitle: MutableState<String>,
|
||||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
||||||
mainNavStartRoute: String = BottomNavItem.Items[0].route
|
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
|
||||||
) {
|
) {
|
||||||
MainMediaView(
|
MainMediaView(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
@@ -276,7 +281,8 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
topBarScrollBehaviour: TopAppBarScrollBehavior,
|
topBarScrollBehaviour: TopAppBarScrollBehavior,
|
||||||
appBarTitle: MutableState<String>,
|
appBarTitle: MutableState<String>,
|
||||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
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 navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
@@ -284,10 +290,10 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
NavigationRail {
|
NavigationRail {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
BottomNavItem.Items.forEach { item ->
|
BottomNavItem.SortedItems.forEach { item ->
|
||||||
NavigationRailItem(
|
NavigationRailItem(
|
||||||
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
|
icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) },
|
||||||
label = { Text(item.name) },
|
label = { if (preferences.showBottomTabLabels) Text(item.name) },
|
||||||
selected = currentRoute == item.route,
|
selected = currentRoute == item.route,
|
||||||
onClick = {
|
onClick = {
|
||||||
onBottomAppBarItemClicked(
|
onBottomAppBarItemClicked(
|
||||||
@@ -326,7 +332,7 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
fab: MutableState<@Composable () -> Unit>,
|
fab: MutableState<@Composable () -> Unit>,
|
||||||
appBarTitle: MutableState<String>,
|
appBarTitle: MutableState<String>,
|
||||||
appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}),
|
appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}),
|
||||||
mainNavStartRoute: String = BottomNavItem.Items[0].route
|
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
@@ -413,13 +419,6 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
it.arguments?.let { arguments ->
|
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) {
|
val (type, title) = if (preferences.multiSearch) {
|
||||||
Pair(MediaViewType.MIXED, "")
|
Pair(MediaViewType.MIXED, "")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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(
|
fun <T: Any> LazyListScope.lazyPagingItems(
|
||||||
lazyPagingItems: LazyPagingItems<T>,
|
lazyPagingItems: LazyPagingItems<T>,
|
||||||
itemContent: @Composable LazyItemScope.(value: T?) -> Unit
|
itemContent: @Composable LazyItemScope.(value: T?) -> Unit
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ import com.owenlejeune.tvtime.utils.SessionManager
|
|||||||
|
|
||||||
class AppPreferences(context: Context) {
|
class AppPreferences(context: Context) {
|
||||||
|
|
||||||
|
enum class DarkMode {
|
||||||
|
Automatic,
|
||||||
|
Dark,
|
||||||
|
Light
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val PREF_FILE = "tvtime_shared_preferences"
|
private val PREF_FILE = "tvtime_shared_preferences"
|
||||||
|
|
||||||
// private val USE_PREFERENCES = "use_android_12_colors"
|
|
||||||
private val PERSISTENT_SEARCH = "persistent_search"
|
private val PERSISTENT_SEARCH = "persistent_search"
|
||||||
private val GUEST_SESSION = "guest_session_id"
|
private val GUEST_SESSION = "guest_session_id"
|
||||||
private val AUTHORIZED_SESSION = "authorized_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 USE_WALLPAPER_COLORS = "use_wallpaper_colors"
|
||||||
private val DARK_THEME = "dark_theme"
|
private val DARK_THEME = "dark_theme"
|
||||||
private val MULTI_SEARCH = "multi_search"
|
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)
|
private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
/******** Search Preferences ********/
|
/******** Search Preferences ********/
|
||||||
|
val showSearchBarDefault: Boolean = false
|
||||||
var showSearchBar: Boolean
|
var showSearchBar: Boolean
|
||||||
get() = preferences.getBoolean(PERSISTENT_SEARCH, true)
|
get() = preferences.getBoolean(PERSISTENT_SEARCH, showSearchBarDefault)
|
||||||
set(value) { preferences.put(PERSISTENT_SEARCH, value) }
|
set(value) { preferences.put(PERSISTENT_SEARCH, value) }
|
||||||
|
|
||||||
|
val multiSearchDefault: Boolean = true
|
||||||
var multiSearch: Boolean
|
var multiSearch: Boolean
|
||||||
get() = preferences.getBoolean(MULTI_SEARCH, false)
|
get() = preferences.getBoolean(MULTI_SEARCH, multiSearchDefault)
|
||||||
set(value) { preferences.put(MULTI_SEARCH, value) }
|
set(value) { preferences.put(MULTI_SEARCH, value) }
|
||||||
|
|
||||||
/******* Design Preferences ********/
|
/******* Design Preferences ********/
|
||||||
|
val useWallpaperColorsDefault: Boolean = true
|
||||||
var useWallpaperColors: Boolean
|
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) }
|
set(value) { preferences.put(USE_WALLPAPER_COLORS, value) }
|
||||||
|
|
||||||
|
val darkThemeDefault: Int = DarkMode.Automatic.ordinal
|
||||||
var darkTheme: Int
|
var darkTheme: Int
|
||||||
get() = preferences.getInt(DARK_THEME, 0)
|
get() = preferences.getInt(DARK_THEME, darkThemeDefault)
|
||||||
set(value) { preferences.put(DARK_THEME, value) }
|
set(value) { preferences.put(DARK_THEME, value) }
|
||||||
|
|
||||||
|
val useSystemColorsDefault: Boolean = true
|
||||||
var useSystemColors: Boolean
|
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) }
|
set(value) { preferences.put(USE_SYSTEM_COLORS, value) }
|
||||||
|
|
||||||
|
val chromeMultiplyerDefault: Double = MonetCompat.chromaMultiplier.toFloat().toDouble()
|
||||||
var chromaMultiplier: Double
|
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) }
|
set(value) { preferences.put(CHROMA_MULTIPLIER, value) }
|
||||||
|
|
||||||
|
val selectedColorDefault: Int = Int.MAX_VALUE
|
||||||
var selectedColor: Int
|
var selectedColor: Int
|
||||||
get() = preferences.getInt(SELECTED_COLOR, Int.MAX_VALUE)
|
get() = preferences.getInt(SELECTED_COLOR, selectedColorDefault)
|
||||||
set(value) { preferences.put(SELECTED_COLOR, value) }
|
set(value) { preferences.put(SELECTED_COLOR, value) }
|
||||||
|
|
||||||
/******* Session Tokens ********/
|
/******* Session Tokens ********/
|
||||||
@@ -76,21 +93,50 @@ class AppPreferences(context: Context) {
|
|||||||
get() = preferences.getString(AUTHORIZED_SESSION, "") ?: ""
|
get() = preferences.getString(AUTHORIZED_SESSION, "") ?: ""
|
||||||
set(value) { preferences.put(AUTHORIZED_SESSION, value) }
|
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 ********/
|
/******** Dev Preferences ********/
|
||||||
|
val firstLaunchTestingDefault: Boolean = false
|
||||||
var firstLaunchTesting: Boolean
|
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) }
|
set(value) { preferences.put(FIRST_LAUNCH_TESTING, value) }
|
||||||
|
|
||||||
var firstLaunch: Boolean
|
var firstLaunch: Boolean
|
||||||
get() = if (BuildConfig.DEBUG) firstLaunchTesting else preferences.getBoolean(FIRST_LAUNCH, true)
|
get() = if (BuildConfig.DEBUG) firstLaunchTesting else preferences.getBoolean(FIRST_LAUNCH, true)
|
||||||
set(value) { preferences.put(FIRST_LAUNCH, value) }
|
set(value) { preferences.put(FIRST_LAUNCH, value) }
|
||||||
|
|
||||||
|
val useV4ApiDefault: Boolean = false
|
||||||
var useV4Api: Boolean
|
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) }
|
set(value) { preferences.put(USE_V4_API, value) }
|
||||||
|
|
||||||
var showBackdropGallery: Boolean// = true
|
val showBackdropGalleryDefault: Boolean = true
|
||||||
get() = preferences.getBoolean(SHOW_BACKDROP_GALLERY, true)
|
var showBackdropGallery: Boolean
|
||||||
|
get() = preferences.getBoolean(SHOW_BACKDROP_GALLERY, showBackdropGalleryDefault)
|
||||||
set(value) { preferences.put(SHOW_BACKDROP_GALLERY, value) }
|
set(value) { preferences.put(SHOW_BACKDROP_GALLERY, value) }
|
||||||
|
|
||||||
/********* Helpers ********/
|
/********* Helpers ********/
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
package com.owenlejeune.tvtime.ui.navigation
|
package com.owenlejeune.tvtime.ui.navigation
|
||||||
|
|
||||||
import com.owenlejeune.tvtime.R
|
import com.owenlejeune.tvtime.R
|
||||||
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
import com.owenlejeune.tvtime.utils.ResourceUtils
|
import com.owenlejeune.tvtime.utils.ResourceUtils
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
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()
|
private val resourceUtils: ResourceUtils by inject()
|
||||||
|
|
||||||
val name = resourceUtils.getString(stringRes)
|
val name = resourceUtils.getString(stringRes)
|
||||||
|
var order: Int
|
||||||
|
get() = orderGetter.invoke(appPreferences)
|
||||||
|
set(value) { orderSetter.invoke(appPreferences, value) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Items by lazy { listOf(Movies, TV, People, Account) }
|
val SortedItems
|
||||||
val SearchableRoutes by lazy { listOf(Movies.route, TV.route, People.route) }
|
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? {
|
fun getByRoute(route: String?): BottomNavItem? {
|
||||||
return when (route) {
|
return when (route) {
|
||||||
Movies.route -> Movies
|
Movies.route -> Movies
|
||||||
TV.route -> TV
|
TV.route -> TV
|
||||||
Account.route -> Account
|
Account.route -> Account
|
||||||
Favourites.route -> Favourites
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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", { it.moviesTabPosition }, { p, i -> p.moviesTabPosition = i } )
|
||||||
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", { it.tvTabPosition }, { p, i -> p.tvTabPosition = i } )
|
||||||
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", { it.accountTabPosition }, { p, i -> p.accountTabPosition = i } )
|
||||||
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", { it.peopleTabPosition }, { p, i -> p.peopleTabPosition = i } )
|
||||||
object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavType
|
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
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.MediaViewType
|
||||||
import com.owenlejeune.tvtime.ui.screens.main.*
|
import com.owenlejeune.tvtime.ui.screens.main.*
|
||||||
|
|
||||||
@@ -27,7 +24,7 @@ fun MainNavGraph(
|
|||||||
fab: MutableState<@Composable () -> Unit>,
|
fab: MutableState<@Composable () -> Unit>,
|
||||||
appBarTitle: MutableState<String>,
|
appBarTitle: MutableState<String>,
|
||||||
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
|
||||||
startDestination: String = BottomNavItem.Items[0].route
|
startDestination: String = BottomNavItem.SortedItems[0].route
|
||||||
) {
|
) {
|
||||||
NavHost(navController = navController, startDestination = startDestination) {
|
NavHost(navController = navController, startDestination = startDestination) {
|
||||||
composable(BottomNavItem.Movies.route) {
|
composable(BottomNavItem.Movies.route) {
|
||||||
@@ -59,9 +56,5 @@ fun MainNavGraph(
|
|||||||
fab = fab
|
fab = fab
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(BottomNavItem.Favourites.route) {
|
|
||||||
appBarActions.value = {}
|
|
||||||
FavouritesTab()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +328,7 @@ private fun TvSearchResultView(
|
|||||||
backdropModel = { TmdbUtils.getFullBackdropPath(result.backdropPath) },
|
backdropModel = { TmdbUtils.getFullBackdropPath(result.backdropPath) },
|
||||||
additionalDetails = {
|
additionalDetails = {
|
||||||
listOf(
|
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 } ?: ""
|
cast.value?.joinToString(separator = ", ") { it.name } ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,10 @@ 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.animation.rememberSplineBasedDecay
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.TextButton
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.material.icons.Icons
|
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.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.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.navigation.NavController
|
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.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.MainNavItem
|
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.ResourceUtils
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -52,6 +60,8 @@ fun SettingsTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val appBarTitle = remember { mutableStateOf("") }
|
val appBarTitle = remember { mutableStateOf("") }
|
||||||
|
val defaultRestoreAction = ::resetAllPreferences
|
||||||
|
val restoreAction = remember { mutableStateOf<(AppPreferences) -> Unit>(defaultRestoreAction) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
@@ -72,6 +82,16 @@ fun SettingsTab(
|
|||||||
contentDescription = stringResource(id = R.string.content_description_back_button)
|
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
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(all = 24.dp),
|
.padding(all = 24.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
verticalArrangement = Arrangement.spacedBy(18.dp)
|
||||||
) {
|
) {
|
||||||
SettingsPage.getByRoute(route).apply {
|
SettingsPage.getByRoute(route).apply {
|
||||||
appBarTitle.value = name
|
appBarTitle.value = name
|
||||||
|
restoreAction.value = resetPreferencesHandler
|
||||||
SettingsPageRenderer(appNavController, activity, preferences)
|
SettingsPageRenderer(appNavController, activity, preferences)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
restoreAction.value = ::resetAllPreferences
|
||||||
SettingsTabView(
|
SettingsTabView(
|
||||||
appNavController = appNavController,
|
appNavController = appNavController,
|
||||||
appBarTitle = appBarTitle
|
appBarTitle = appBarTitle
|
||||||
@@ -128,6 +150,14 @@ private fun SettingsTabView(
|
|||||||
appNavController = appNavController
|
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(
|
TopLevelSettingsCard(
|
||||||
title = stringResource(id = R.string.preferences_debug_title),
|
title = stringResource(id = R.string.preferences_debug_title),
|
||||||
subtitle = stringResource(R.string.preference_subtitle_debug),
|
subtitle = stringResource(R.string.preference_subtitle_debug),
|
||||||
@@ -196,8 +226,8 @@ private fun SearchPreferences(
|
|||||||
|
|
||||||
val multiSearch = remember { mutableStateOf(preferences.multiSearch) }
|
val multiSearch = remember { mutableStateOf(preferences.multiSearch) }
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
titleText = "Multi Search",
|
titleText = stringResource(R.string.preference_multi_search_title),
|
||||||
subtitleText = "Search across movies, TV, and people at the same time",
|
subtitleText = stringResource(R.string.preference_multi_search_subtitle),
|
||||||
checkState = multiSearch.value,
|
checkState = multiSearch.value,
|
||||||
onCheckedChange = { isChecked ->
|
onCheckedChange = { isChecked ->
|
||||||
multiSearch.value = isChecked
|
multiSearch.value = isChecked
|
||||||
@@ -288,12 +318,6 @@ private fun DesignPreferences(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DarkMode {
|
|
||||||
Automatic,
|
|
||||||
Dark,
|
|
||||||
Light
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DarkModePreferences(
|
private fun DarkModePreferences(
|
||||||
activity: AppCompatActivity,
|
activity: AppCompatActivity,
|
||||||
@@ -308,35 +332,79 @@ private fun DarkModePreferences(
|
|||||||
activity.recreate()
|
activity.recreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferenceHeading(text = "Automatic")
|
PreferenceHeading(text = stringResource(R.string.preference_dark_mode_automatic_heading))
|
||||||
RadioButtonPreference(
|
RadioButtonPreference(
|
||||||
selected = isSelected(DarkMode.Automatic.ordinal),
|
selected = isSelected(AppPreferences.DarkMode.Automatic.ordinal),
|
||||||
title = "Follow system",
|
title = stringResource(R.string.preference_dark_mode_follow_system_label),
|
||||||
icon = Icons.Filled.Brightness6,
|
icon = Icons.Filled.Brightness6,
|
||||||
onClick = {
|
onClick = {
|
||||||
onChangeState(DarkMode.Automatic.ordinal)
|
onChangeState(AppPreferences.DarkMode.Automatic.ordinal)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
PreferenceHeading(text = "Manual")
|
PreferenceHeading(text = stringResource(R.string.preference_dark_mode_maual_heading))
|
||||||
RadioButtonPreference(
|
RadioButtonPreference(
|
||||||
selected = isSelected(DarkMode.Light.ordinal),
|
selected = isSelected(AppPreferences.DarkMode.Light.ordinal),
|
||||||
title = "Light mode",
|
title = stringResource(R.string.preference_dark_mode_light_mode_label),
|
||||||
icon = Icons.Outlined.LightMode,
|
icon = Icons.Outlined.LightMode,
|
||||||
onClick = {
|
onClick = {
|
||||||
onChangeState(DarkMode.Light.ordinal)
|
onChangeState(AppPreferences.DarkMode.Light.ordinal)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
RadioButtonPreference(
|
RadioButtonPreference(
|
||||||
selected = isSelected(DarkMode.Dark.ordinal),
|
selected = isSelected(AppPreferences.DarkMode.Dark.ordinal),
|
||||||
title = "Dark mode",
|
title = stringResource(R.string.preference_dark_mode_dark_mode_label),
|
||||||
icon = Icons.Outlined.DarkMode,
|
icon = Icons.Outlined.DarkMode,
|
||||||
onClick = {
|
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
|
@Composable
|
||||||
private fun DevPreferences(
|
private fun DevPreferences(
|
||||||
preferences: AppPreferences = get(AppPreferences::class.java)
|
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()
|
private val resources: ResourceUtils by inject()
|
||||||
|
|
||||||
val name = resources.getString(stringRes)
|
val name = resources.getString(stringRes)
|
||||||
|
|
||||||
companion object {
|
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 {
|
fun getByRoute(route: String): SettingsPage {
|
||||||
return Pages.map { it.route to it }
|
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 SearchSettings: SettingsPage(
|
||||||
object DesignSettings: SettingsPage(R.string.preference_heading_design, "design", @Composable { n, a, p -> DesignPreferences(n, a, p) } )
|
R.string.preference_heading_search,
|
||||||
object DeveloperSettings: SettingsPage(R.string.preferences_debug_title,"dev", @Composable { n, a, p -> DevPreferences(p) } )
|
"search",
|
||||||
object DarkModeSettings: SettingsPage(R.string.preference_heading_dark_mode, "darkmode", @Composable { n, a, p -> DarkModePreferences(a, p) } )
|
@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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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(
|
||||||
@@ -77,9 +76,9 @@ fun TVTimeTheme(
|
|||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val isDarkTheme = when(preferences.darkTheme) {
|
val isDarkTheme = when(preferences.darkTheme) {
|
||||||
DarkMode.Automatic.ordinal -> isSystemInDarkTheme()
|
AppPreferences.DarkMode.Automatic.ordinal -> isSystemInDarkTheme()
|
||||||
DarkMode.Dark.ordinal -> true
|
AppPreferences.DarkMode.Dark.ordinal -> true
|
||||||
DarkMode.Light.ordinal -> false
|
AppPreferences.DarkMode.Light.ordinal -> false
|
||||||
else -> throw IllegalArgumentException("Illegal theme value ${preferences.darkTheme}")
|
else -> throw IllegalArgumentException("Illegal theme value ${preferences.darkTheme}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,20 @@
|
|||||||
<string name="preference_subtitle_debug">Secret developer options</string>
|
<string name="preference_subtitle_debug">Secret developer options</string>
|
||||||
<string name="preference_heading_dark_mode">Dark mode</string>
|
<string name="preference_heading_dark_mode">Dark mode</string>
|
||||||
<string name="preference_subtitle_dark_mode">Light, dark, or auto</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 & 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 -->
|
<!-- video type -->
|
||||||
<string name="video_type_clip">Clips</string>
|
<string name="video_type_clip">Clips</string>
|
||||||
@@ -122,6 +136,6 @@
|
|||||||
<string name="example_page_desc">This is an example</string>
|
<string name="example_page_desc">This is an example</string>
|
||||||
|
|
||||||
<!-- search results -->
|
<!-- 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>
|
<string name="no_search_results">No search results found</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user