Add ability to customize the navigation drawer menu (#60)

Adds support for customizing the items listed on the left side
navigation drawer menu. Each item can be toggled on/off in settings.

The hidden ones are still accessible under "More" on the nav drawer,
which only appears if some items are hidden.

Additionally, if any of menu items are hidden, their "latest" content
will not appear on the home page.

The setting is stored per user profile.
This commit is contained in:
damontecres 2025-10-23 15:57:26 -04:00 committed by GitHub
parent 27d0a17a00
commit fc4954b561
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 776 additions and 184 deletions

View file

@ -3,10 +3,13 @@ package com.github.damontecres.wholphin.ui.main
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.NavDrawerItemRepository
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.supportItemKinds
@ -19,7 +22,6 @@ import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
import org.jellyfin.sdk.api.client.extensions.userApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.UserDto
@ -36,6 +38,8 @@ class HomeViewModel
constructor(
val api: ApiClient,
val navigationManager: NavigationManager,
val serverRepository: ServerRepository,
val navDrawerItemRepository: NavDrawerItemRepository,
) : ViewModel() {
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
val homeRows = MutableLiveData<List<HomeRow>>()
@ -52,64 +56,44 @@ class HomeViewModel
),
) {
Timber.d("init HomeViewModel")
val user by api.userApi.getCurrentUser()
// val displayPrefs =
// api.displayPreferencesApi
// .getDisplayPreferences(
// displayPreferencesId = "usersettings",
// client = "emby",
// ).content
val homeSections =
listOf(
HomeSection.RESUME,
HomeSection.NEXT_UP,
HomeSection.LATEST_MEDIA,
)
// TODO use display preferences?
// displayPrefs.customPrefs.entries
// .filter { it.key.startsWith("homesection") && it.value.isNotNullOrBlank() }
// .sortedBy { it.key }
// .map { HomeSection.fromKey(it.value ?: "") }
// .filter {
// it in
// setOf(
// HomeSection.LATEST_MEDIA,
// HomeSection.NEXT_UP,
// HomeSection.RESUME,
// )
// }
serverRepository.currentUserDto?.let { userDto ->
val includedIds =
navDrawerItemRepository
.getFilteredNavDrawerItems(navDrawerItemRepository.getNavDrawerItems())
.filter { it is ServerNavDrawerItem }
.map { (it as ServerNavDrawerItem).itemId }
// TODO data is fetched all together which may be slow for large servers
val resume = getResume(userDto.id, limit)
val nextUp = getNextUp(userDto.id, limit, prefs.enableRewatchingNextUp)
val latest = getLatest(userDto, limit, includedIds)
// TODO data is fetched all together which may be slow for large servers
val resume = getResume(user.id, limit)
val nextUp = getNextUp(user.id, limit, prefs.enableRewatchingNextUp)
val latest = getLatest(user, limit)
val homeRows =
if (prefs.combineContinueNext) {
listOf(
HomeRow(
section = HomeSection.NEXT_UP,
items = resume + nextUp,
),
*latest.toTypedArray(),
)
} else {
listOf(
HomeRow(
section = HomeSection.RESUME,
items = resume,
),
HomeRow(
section = HomeSection.NEXT_UP,
items = nextUp,
),
*latest.toTypedArray(),
)
val homeRows =
if (prefs.combineContinueNext) {
listOf(
HomeRow(
section = HomeSection.NEXT_UP,
items = resume + nextUp,
),
*latest.toTypedArray(),
)
} else {
listOf(
HomeRow(
section = HomeSection.RESUME,
items = resume,
),
HomeRow(
section = HomeSection.NEXT_UP,
items = nextUp,
),
*latest.toTypedArray(),
)
}
withContext(Dispatchers.Main) {
this@HomeViewModel.homeRows.value = homeRows
loadingState.value = LoadingState.Success
}
withContext(Dispatchers.Main) {
this@HomeViewModel.homeRows.value = homeRows
loadingState.value = LoadingState.Success
}
}
}
@ -162,11 +146,16 @@ class HomeViewModel
private suspend fun getLatest(
user: UserDto,
limit: Int,
includedIds: List<UUID>,
): List<HomeRow> {
val latestMediaIncludes =
user.configuration?.orderedViews.orEmpty().toMutableList().apply {
removeAll(user.configuration?.latestItemsExcludes.orEmpty())
}
user.configuration
?.orderedViews
.orEmpty()
.toMutableList()
.apply {
removeAll(user.configuration?.latestItemsExcludes.orEmpty())
}.filter { includedIds.contains(it) }
val views by api.userViewsApi.getUserViews()
val rows =

View file

@ -2,7 +2,6 @@ package com.github.damontecres.wholphin.ui.nav
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.interaction.MutableInteractionSource
@ -17,10 +16,13 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
@ -49,6 +51,7 @@ import androidx.tv.material3.LocalContentColor
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.NavigationDrawer
import androidx.tv.material3.NavigationDrawerItem
import androidx.tv.material3.NavigationDrawerItemDefaults
import androidx.tv.material3.NavigationDrawerScope
import androidx.tv.material3.ProvideTextStyle
import androidx.tv.material3.Text
@ -57,26 +60,20 @@ import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.JellyfinServer
import com.github.damontecres.wholphin.data.JellyfinUser
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.NavDrawerItemRepository
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.wholphin.ui.spacedByWithFooter
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.supportedCollectionTypes
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.DeviceProfile
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
@ -84,45 +81,25 @@ import javax.inject.Inject
class NavDrawerViewModel
@Inject
constructor(
@param:ApplicationContext val context: Context,
val serverRepository: ServerRepository,
val api: ApiClient,
private val navDrawerItemRepository: NavDrawerItemRepository,
val navigationManager: NavigationManager,
) : ViewModel() {
private var all: List<NavDrawerItem>? = null
val moreLibraries = MutableLiveData<List<NavDrawerItem>>(null)
val libraries = MutableLiveData<List<NavDrawerItem>>(listOf())
val selectedIndex = MutableLiveData<Int>(-1)
val selectedIndex = MutableLiveData(-1)
val showMore = MutableLiveData(false)
fun init() {
viewModelScope.launchIO {
val all = all ?: navDrawerItemRepository.getNavDrawerItems()
this@NavDrawerViewModel.all = all
val libraries = navDrawerItemRepository.getFilteredNavDrawerItems(all)
init {
viewModelScope.launch(Dispatchers.IO + ExceptionHandler(true)) {
val userViews =
api.userViewsApi
.getUserViews()
.content.items
val libraries =
userViews
.filter { it.collectionType in supportedCollectionTypes }
.map {
NavDrawerItem(
id = it.id,
name = it.name ?: it.id.toString(),
destination = BaseItem.from(it, api).destination(),
type = it.collectionType ?: CollectionType.UNKNOWN,
iconStringRes = null,
)
}
val extra =
listOf(
NavDrawerItem(
id = UUID.randomUUID(),
name = context.getString(R.string.favorites),
destination = Destination.Favorites,
type = CollectionType.UNKNOWN,
iconStringRes = R.string.fa_heart,
),
)
Timber.d("Got ${userViews.size} user views filtered to ${libraries.size}")
withContext(Dispatchers.Main) {
this@NavDrawerViewModel.libraries.value = extra + libraries
this@NavDrawerViewModel.moreLibraries.value =
all.toMutableList().apply { removeAll(libraries) }
this@NavDrawerViewModel.libraries.value = libraries
}
}
}
@ -130,15 +107,42 @@ class NavDrawerViewModel
fun setIndex(index: Int) {
selectedIndex.value = index
}
fun setShowMore(value: Boolean) {
showMore.value = value
}
}
data class NavDrawerItem(
val id: UUID,
sealed interface NavDrawerItem {
val id: String
fun name(context: Context): String
object Favorites : NavDrawerItem {
override val id: String
get() = "a_favorites"
override fun name(context: Context): String = context.getString(R.string.favorites)
}
object More : NavDrawerItem {
override val id: String
get() = "a_more"
override fun name(context: Context): String = context.getString(R.string.more)
}
}
data class ServerNavDrawerItem(
val itemId: UUID,
val name: String,
val destination: Destination,
val type: CollectionType,
@param:StringRes val iconStringRes: Int?,
)
) : NavDrawerItem {
override val id: String = "s_" + itemId.toServerString()
override fun name(context: Context): String = name
}
/**
* Display the left side navigation drawer with [DestinationContent] on the right
@ -162,17 +166,49 @@ fun NavDrawer(
val scope = rememberCoroutineScope()
val context = LocalContext.current
val drawerFocusRequester = remember { FocusRequester() }
val focusRequester = remember { FocusRequester() }
// If the user presses back while on the home page, open the nav drawer, another back press will quit the app
BackHandler(enabled = (drawerState.currentValue == DrawerValue.Closed && destination is Destination.Home)) {
drawerState.setValue(DrawerValue.Open)
drawerFocusRequester.requestFocus()
}
val moreLibraries by viewModel.moreLibraries.observeAsState(listOf())
val libraries by viewModel.libraries.observeAsState(listOf())
LaunchedEffect(Unit) { viewModel.init() }
val showMore by viewModel.showMore.observeAsState(false)
// val libraries = if (showPinnedOnly) pinnedLibraries else allLibraries
// A negative index is a built in page, >=0 is a library
val selectedIndex by viewModel.selectedIndex.observeAsState(-1)
val focusRequester = remember { FocusRequester() }
fun setShowMore(value: Boolean) {
viewModel.setShowMore(value)
}
BackHandler(enabled = showMore && drawerState.currentValue == DrawerValue.Open) {
setShowMore(false)
}
val onClick = { index: Int, item: NavDrawerItem ->
when (item) {
NavDrawerItem.Favorites -> {
viewModel.setIndex(index)
viewModel.navigationManager.navigateToFromDrawer(
Destination.Favorites,
)
}
NavDrawerItem.More -> {
setShowMore(!showMore)
}
is ServerNavDrawerItem -> {
viewModel.setIndex(index)
viewModel.navigationManager.navigateToFromDrawer(item.destination)
}
}
}
NavigationDrawer(
modifier =
@ -205,6 +241,7 @@ fun NavDrawer(
onClick = {
viewModel.navigationManager.navigateTo(Destination.UserList)
},
modifier = Modifier.animateItem(),
)
}
item {
@ -217,10 +254,11 @@ fun NavDrawer(
viewModel.navigationManager.navigateToFromDrawer(Destination.Search)
},
modifier =
Modifier.ifElse(
selectedIndex == -2,
Modifier.focusRequester(focusRequester),
),
Modifier
.ifElse(
selectedIndex == -2,
Modifier.focusRequester(focusRequester),
).animateItem(),
)
}
item {
@ -237,27 +275,44 @@ fun NavDrawer(
}
},
modifier =
Modifier.ifElse(
selectedIndex == -1,
Modifier.focusRequester(focusRequester),
),
Modifier
.ifElse(
selectedIndex == -1,
Modifier.focusRequester(focusRequester),
).animateItem(),
)
}
itemsIndexed(libraries) { index, it ->
NavItem(
library = it,
selected = selectedIndex == index,
onClick = {
viewModel.setIndex(index)
viewModel.navigationManager.navigateToFromDrawer(it.destination)
},
moreExpanded = showMore,
onClick = { onClick.invoke(index, it) },
modifier =
Modifier.ifElse(
selectedIndex == index,
Modifier.focusRequester(focusRequester),
),
Modifier
.ifElse(
selectedIndex == index,
Modifier.focusRequester(focusRequester),
).animateItem(),
)
}
if (showMore) {
itemsIndexed(moreLibraries) { index, it ->
NavItem(
library = it,
selected = selectedIndex == (index + libraries.size),
moreExpanded = showMore,
onClick = { onClick.invoke(index + libraries.size, it) },
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
modifier =
Modifier
.ifElse(
selectedIndex == (index + libraries.size),
Modifier.focusRequester(focusRequester),
).animateItem(),
)
}
}
item {
IconNavItem(
text = "Settings",
@ -270,7 +325,7 @@ fun NavDrawer(
),
)
},
modifier = Modifier,
modifier = Modifier.animateItem(),
)
}
}
@ -339,20 +394,29 @@ fun NavigationDrawerScope.NavItem(
library: NavDrawerItem,
onClick: () -> Unit,
selected: Boolean,
moreExpanded: Boolean,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
containerColor: Color = Color.Unspecified,
) {
val context = LocalContext.current
val icon =
when (library.type) {
CollectionType.MOVIES -> R.string.fa_film
CollectionType.TVSHOWS -> R.string.fa_tv
CollectionType.HOMEVIDEOS -> R.string.fa_video
CollectionType.LIVETV -> R.string.fa_tv
CollectionType.MUSIC -> R.string.fa_music
CollectionType.BOXSETS -> R.string.fa_open_folder
CollectionType.PLAYLISTS -> R.string.fa_list_ul
else -> library.iconStringRes
} ?: R.string.fa_film
when (library) {
NavDrawerItem.Favorites -> R.string.fa_heart
NavDrawerItem.More -> R.string.fa_ellipsis
is ServerNavDrawerItem ->
when (library.type) {
CollectionType.MOVIES -> R.string.fa_film
CollectionType.TVSHOWS -> R.string.fa_tv
CollectionType.HOMEVIDEOS -> R.string.fa_video
CollectionType.LIVETV -> R.string.fa_tv
CollectionType.MUSIC -> R.string.fa_music
CollectionType.BOXSETS -> R.string.fa_open_folder
CollectionType.PLAYLISTS -> R.string.fa_list_ul
else -> R.string.fa_film
}
}
val isFocused = interactionSource.collectIsFocusedAsState().value
val color =
when {
@ -364,6 +428,10 @@ fun NavigationDrawerScope.NavItem(
modifier = modifier,
selected = false,
onClick = onClick,
colors =
NavigationDrawerItemDefaults.colors(
containerColor = containerColor,
),
leadingContent = {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
@ -375,11 +443,19 @@ fun NavigationDrawerScope.NavItem(
)
}
},
trailingContent = {
if (library is NavDrawerItem.More) {
Icon(
imageVector = if (moreExpanded) Icons.Default.ArrowDropDown else Icons.Default.KeyboardArrowLeft,
contentDescription = null,
)
}
},
interactionSource = null,
) {
Text(
modifier = Modifier,
text = library.name ?: library.id.toString(),
text = library.name(context),
maxLines = 1,
)
}

View file

@ -34,7 +34,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.tv.material3.Button
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Switch
import androidx.tv.material3.Text
import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.wholphin.R
@ -53,6 +52,7 @@ import com.github.damontecres.wholphin.ui.components.EditTextBox
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch
import java.util.SortedSet
@Suppress("UNCHECKED_CAST")
@Composable
@ -197,7 +197,7 @@ fun <T> ComposablePreference(
}
is AppMultiChoicePreference<*> -> {
val values = stringArrayResource(preference.displayValues).toList()
val values = stringArrayResource(preference.displayValues).toSortedSet()
val summary =
preference.summary?.let { stringResource(it) }
?: preference.summary(context, value)
@ -207,46 +207,14 @@ fun <T> ComposablePreference(
list.addAll(value as List<Any>)
list
}
val onClick = { item: Any ->
if (selectedValues.contains(item)) {
selectedValues.remove(item)
} else {
selectedValues.add(item)
}
onValueChange.invoke(selectedValues.toList() as T)
}
ClickPreference(
MultiChoicePreference(
possibleValues = values as SortedSet<Any>,
selectedValues = selectedValues,
title = title,
summary = summary,
onClick = {
dialogParams =
DialogParams(
title = title,
fromLongClick = false,
items =
values.mapIndexed { index, it ->
val item = preference.allValues[index]!!
DialogItem(
headlineContent = { Text(it) },
trailingContent = {
Switch(
checked = selectedValues.contains(item),
onCheckedChange = {
onClick.invoke(item)
},
)
},
onClick = {
onClick.invoke(item)
},
)
},
)
onValueChange = {
onValueChange.invoke(selectedValues.toList() as T)
},
interactionSource = interactionSource,
modifier = modifier,
)
}

View file

@ -0,0 +1,93 @@
package com.github.damontecres.wholphin.ui.preferences
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableStateSetOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.tv.material3.Switch
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.ui.components.DialogItem
import com.github.damontecres.wholphin.ui.components.DialogParams
import com.github.damontecres.wholphin.ui.components.DialogPopup
@Composable
fun <T> MultiChoicePreference(
title: String,
summary: String?,
possibleValues: Set<T>,
selectedValues: Set<T>,
onValueChange: (List<T>) -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
valueDisplay: @Composable (item: T) -> Unit = { Text(it.toString()) },
) {
// val values = stringArrayResource(preference.displayValues).toList()
// val summary =
// preference.summary?.let { stringResource(it) }
// ?: preference.summary(context, value)
val selectedValues =
remember {
val list = mutableStateSetOf<T>()
list.addAll(selectedValues)
list
}
val onClick = { item: T ->
if (selectedValues.contains(item)) {
selectedValues.remove(item)
} else {
selectedValues.add(item)
}
onValueChange.invoke(selectedValues.toList())
}
var dialogParams by remember { mutableStateOf<DialogParams?>(null) }
ClickPreference(
title = title,
summary = summary,
onClick = {
dialogParams =
DialogParams(
title = title,
fromLongClick = false,
items =
possibleValues.mapIndexed { index, item ->
DialogItem(
headlineContent = { valueDisplay.invoke(item) },
trailingContent = {
Switch(
checked = selectedValues.contains(item),
onCheckedChange = {
onClick.invoke(item)
},
)
},
onClick = {
onClick.invoke(item)
},
)
},
)
},
interactionSource = interactionSource,
modifier = modifier,
)
AnimatedVisibility(dialogParams != null) {
dialogParams?.let {
DialogPopup(
showDialog = true,
title = it.title,
dialogItems = it.items,
onDismissRequest = { dialogParams = null },
waitToLoad = false,
dismissOnClick = false,
)
}
}
}

View file

@ -69,6 +69,9 @@ fun PreferencesContent(
var focusedIndex by rememberSaveable { mutableStateOf(Pair(0, 0)) }
val state = rememberLazyListState()
var preferences by remember { mutableStateOf(initialPreferences) }
val navDrawerPins by viewModel.navDrawerPins.observeAsState(mapOf())
LaunchedEffect(Unit) {
viewModel.preferenceDataStore.data.collect {
preferences = it
@ -262,6 +265,24 @@ fun PreferencesContent(
)
}
AppPreference.UserPinnedNavDrawerItems -> {
val selectedItems =
navDrawerPins.keys.mapNotNull {
if (navDrawerPins[it] ?: false) it else null
}
MultiChoicePreference(
title = stringResource(pref.title),
summary = pref.summary(context, null),
possibleValues = navDrawerPins.keys,
selectedValues = selectedItems.toSet(),
onValueChange = { newSelectedItems ->
viewModel.updatePins(newSelectedItems)
},
) {
Text(it.name(context))
}
}
else -> {
val value = pref.getter.invoke(preferences)
ComposablePreference(

View file

@ -1,9 +1,21 @@
package com.github.damontecres.wholphin.ui.preferences
import androidx.datastore.core.DataStore
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.NavDrawerItemRepository
import com.github.damontecres.wholphin.data.ServerPreferencesDao
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.isPinned
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
import com.github.damontecres.wholphin.data.model.NavPinType
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.RememberTabManager
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@ -15,5 +27,54 @@ class PreferencesViewModel
val preferenceDataStore: DataStore<AppPreferences>,
val navigationManager: NavigationManager,
val rememberTabManager: RememberTabManager,
val serverRepository: ServerRepository,
val navDrawerItemRepository: NavDrawerItemRepository,
val serverPreferencesDao: ServerPreferencesDao,
) : ViewModel(),
RememberTabManager by rememberTabManager
RememberTabManager by rememberTabManager {
private lateinit var allNavDrawerItems: List<NavDrawerItem>
val navDrawerPins = MutableLiveData<Map<NavDrawerItem, Boolean>>(mapOf())
init {
viewModelScope.launchIO {
serverRepository.currentUser?.let { user ->
allNavDrawerItems = navDrawerItemRepository.getNavDrawerItems()
val pins = serverPreferencesDao.getNavDrawerPinnedItems(user)
val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) }
this@PreferencesViewModel.navDrawerPins.setValueOnMain(navDrawerPins)
}
}
}
fun updatePins(newSelectedItems: List<NavDrawerItem>) {
viewModelScope.launchIO(ExceptionHandler(true)) {
serverRepository.currentUser?.let { user ->
val disabledItems =
mutableListOf<NavDrawerItem>().apply {
addAll(allNavDrawerItems)
removeAll(newSelectedItems)
}
val enabledItems = newSelectedItems.toSet()
val toSave =
disabledItems.map {
NavDrawerPinnedItem(
user.rowId,
it.id,
NavPinType.UNPINNED,
)
} +
enabledItems.map {
NavDrawerPinnedItem(
user.rowId,
it.id,
NavPinType.PINNED,
)
}
serverPreferencesDao.saveNavDrawerPinnedItems(*toSave.toTypedArray())
val pins = serverPreferencesDao.getNavDrawerPinnedItems(user)
val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) }
this@PreferencesViewModel.navDrawerPins.setValueOnMain(navDrawerPins)
}
}
}
}