mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Add ability to order navigation drawer items (#886)
## Description Allows for reordering the items in the navigation drawer This does not change the home page row order since #803 decouples the nav drawer and home page rows. The initial order will be the "Library Order" settings on the web under Profile->Home. Making any changes locally in Wholphin will set the order. If any new libraries are added or if Seerr integration is enabled, these will added to the end of the "pinned" list. ### Related issues Closes #822 Related to #399 & #803 ### Testing Tested on emulator - Re-ordering - Granting/Revoking user permission to a new library ## Screenshots N/A, nav drawer is the same, just sorted differently ## AI or LLM usage None
This commit is contained in:
parent
fcba1c7444
commit
17b0eef5c5
9 changed files with 1214 additions and 163 deletions
|
|
@ -27,8 +27,8 @@ 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.ReadOnlyComposable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -54,7 +54,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.findViewTreeViewModelStoreOwner
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.tv.material3.DrawerState
|
||||
import androidx.tv.material3.DrawerValue
|
||||
import androidx.tv.material3.Icon
|
||||
|
|
@ -72,6 +71,7 @@ import com.github.damontecres.wholphin.data.model.JellyfinUser
|
|||
import com.github.damontecres.wholphin.preferences.AppThemeColors
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.NavDrawerService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||
import com.github.damontecres.wholphin.services.SetupDestination
|
||||
|
|
@ -79,23 +79,16 @@ import com.github.damontecres.wholphin.services.SetupNavigationManager
|
|||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.components.TimeDisplay
|
||||
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.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.setup.UserIconCardImage
|
||||
import com.github.damontecres.wholphin.ui.spacedByWithFooter
|
||||
import com.github.damontecres.wholphin.ui.theme.LocalTheme
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.imageApi
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -104,80 +97,17 @@ class NavDrawerViewModel
|
|||
@Inject
|
||||
constructor(
|
||||
private val api: ApiClient,
|
||||
private val navDrawerService: NavDrawerService,
|
||||
private val navDrawerItemRepository: NavDrawerItemRepository,
|
||||
val navigationManager: NavigationManager,
|
||||
val setupNavigationManager: SetupNavigationManager,
|
||||
val backdropService: BackdropService,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
) : ViewModel() {
|
||||
val moreLibraries = MutableLiveData<List<NavDrawerItem>>(null)
|
||||
val libraries = MutableLiveData<List<NavDrawerItem>>(listOf())
|
||||
val state = navDrawerService.state
|
||||
|
||||
val selectedIndex = MutableLiveData(-1)
|
||||
val showMore = MutableLiveData(false)
|
||||
|
||||
init {
|
||||
seerrServerRepository.active
|
||||
.onEach {
|
||||
init()
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun init() {
|
||||
viewModelScope.launchIO {
|
||||
val all = navDrawerItemRepository.getNavDrawerItems()
|
||||
val libraries = navDrawerItemRepository.getFilteredNavDrawerItems(all)
|
||||
val moreLibraries = all.toMutableList().apply { removeAll(libraries) }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
this@NavDrawerViewModel.moreLibraries.value = moreLibraries
|
||||
this@NavDrawerViewModel.libraries.value = libraries
|
||||
}
|
||||
val asDestinations =
|
||||
(
|
||||
libraries +
|
||||
listOf(
|
||||
NavDrawerItem.More,
|
||||
NavDrawerItem.Discover,
|
||||
) + moreLibraries
|
||||
).map {
|
||||
if (it is ServerNavDrawerItem) {
|
||||
it.destination
|
||||
} else if (it is NavDrawerItem.Favorites) {
|
||||
Destination.Favorites
|
||||
} else if (it is NavDrawerItem.Discover) {
|
||||
Destination.Discover
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val backstack = navigationManager.backStack.toList().reversed()
|
||||
for (i in 0..<backstack.size) {
|
||||
val key = backstack[i]
|
||||
if (key is Destination) {
|
||||
val index =
|
||||
if (key is Destination.Home) {
|
||||
-1
|
||||
} else if (key is Destination.Search) {
|
||||
-2
|
||||
} else {
|
||||
val idx = asDestinations.indexOf(key)
|
||||
if (idx >= 0) {
|
||||
idx
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
Timber.v("Found $index => $key")
|
||||
if (index != null) {
|
||||
selectedIndex.setValueOnMain(index)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val moreExpanded = MutableLiveData(false)
|
||||
|
||||
fun onClickDrawerItem(
|
||||
index: Int,
|
||||
|
|
@ -193,7 +123,7 @@ class NavDrawerViewModel
|
|||
}
|
||||
|
||||
NavDrawerItem.More -> {
|
||||
setShowMore(!showMore.value!!)
|
||||
setShowMore(!moreExpanded.value!!)
|
||||
}
|
||||
|
||||
NavDrawerItem.Discover -> {
|
||||
|
|
@ -215,7 +145,7 @@ class NavDrawerViewModel
|
|||
}
|
||||
|
||||
fun setShowMore(value: Boolean) {
|
||||
showMore.value = value
|
||||
moreExpanded.value = value
|
||||
}
|
||||
|
||||
fun getUserImage(user: JellyfinUser): String = api.imageApi.getUserImageUrl(user.id)
|
||||
|
|
@ -289,15 +219,12 @@ fun NavDrawer(
|
|||
drawerState.setValue(DrawerValue.Open)
|
||||
focusRequester.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)
|
||||
// A negative index is a built in page, >=0 is a library
|
||||
val state by viewModel.state.collectAsState()
|
||||
val moreExpanded by viewModel.moreExpanded.observeAsState(false)
|
||||
// A negative index is a built-in page, >=0 is a library
|
||||
val selectedIndex by viewModel.selectedIndex.observeAsState(-1)
|
||||
|
||||
BackHandler(enabled = showMore && drawerState.currentValue == DrawerValue.Open) {
|
||||
BackHandler(enabled = moreExpanded && drawerState.currentValue == DrawerValue.Open) {
|
||||
viewModel.setShowMore(false)
|
||||
}
|
||||
|
||||
|
|
@ -412,51 +339,83 @@ fun NavDrawer(
|
|||
),
|
||||
)
|
||||
}
|
||||
itemsIndexed(libraries) { index, it ->
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
NavItem(
|
||||
library = it,
|
||||
selected = selectedIndex == index,
|
||||
moreExpanded = showMore,
|
||||
drawerOpen = isOpen,
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
viewModel.onClickDrawerItem(index, it)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
selectedIndex == index,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (showMore) {
|
||||
itemsIndexed(moreLibraries) { index, it ->
|
||||
val adjustedIndex = (index + libraries.size + 1)
|
||||
itemsIndexed(state.items) { index, it ->
|
||||
if (it !is NavDrawerItem.Discover || state.discoverEnabled) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
NavItem(
|
||||
library = it,
|
||||
selected = selectedIndex == adjustedIndex,
|
||||
moreExpanded = showMore,
|
||||
selected = selectedIndex == index,
|
||||
moreExpanded = moreExpanded,
|
||||
drawerOpen = isOpen,
|
||||
onClick = { viewModel.onClickDrawerItem(adjustedIndex, it) },
|
||||
containerColor =
|
||||
if (isOpen) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
viewModel.onClickDrawerItem(index, it)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
selectedIndex == adjustedIndex,
|
||||
selectedIndex == index,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (state.moreItems.isNotEmpty()) {
|
||||
item {
|
||||
val index = state.items.size
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
NavItem(
|
||||
library = NavDrawerItem.More,
|
||||
selected = selectedIndex == index,
|
||||
moreExpanded = moreExpanded,
|
||||
drawerOpen = isOpen,
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
viewModel.onClickDrawerItem(index, NavDrawerItem.More)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
selectedIndex == index,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (moreExpanded) {
|
||||
itemsIndexed(state.moreItems) { index, it ->
|
||||
val adjustedIndex =
|
||||
remember(state) { (index + state.items.size + 1) }
|
||||
if (it !is NavDrawerItem.Discover || state.discoverEnabled) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
NavItem(
|
||||
library = it,
|
||||
selected = selectedIndex == adjustedIndex,
|
||||
moreExpanded = moreExpanded,
|
||||
drawerOpen = isOpen,
|
||||
onClick = {
|
||||
viewModel.onClickDrawerItem(
|
||||
adjustedIndex,
|
||||
it,
|
||||
)
|
||||
},
|
||||
containerColor =
|
||||
if (isOpen) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
selectedIndex == adjustedIndex,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
IconNavItem(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
package com.github.damontecres.wholphin.ui.preferences
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.ListItem
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Switch
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.Button
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
|
||||
data class NavDrawerPin(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val pinned: Boolean,
|
||||
val item: NavDrawerItem,
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
context: Context,
|
||||
items: Map<NavDrawerItem, Boolean>,
|
||||
) {
|
||||
items.map { (item, pinned) ->
|
||||
NavDrawerPin(item.id, item.name(context), pinned, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class MoveDirection {
|
||||
UP,
|
||||
DOWN,
|
||||
}
|
||||
|
||||
private fun <T> List<T>.move(
|
||||
direction: MoveDirection,
|
||||
index: Int,
|
||||
): List<T> =
|
||||
toMutableList().apply {
|
||||
if (direction == MoveDirection.DOWN) {
|
||||
val down = this[index]
|
||||
val up = this[index + 1]
|
||||
set(index, up)
|
||||
set(index + 1, down)
|
||||
} else {
|
||||
val up = this[index]
|
||||
val down = this[index - 1]
|
||||
set(index - 1, up)
|
||||
set(index, down)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavDrawerPreference(
|
||||
title: String,
|
||||
summary: String?,
|
||||
items: List<NavDrawerPin>,
|
||||
onSave: (List<NavDrawerPin>) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
ClickPreference(
|
||||
title = title,
|
||||
summary = summary,
|
||||
onClick = { showDialog = true },
|
||||
interactionSource = interactionSource,
|
||||
modifier = modifier,
|
||||
)
|
||||
if (showDialog) {
|
||||
NavDrawerPreferenceDialog(
|
||||
items = items,
|
||||
onDismissRequest = { showDialog = false },
|
||||
onClick = { index ->
|
||||
val newItems =
|
||||
items.toMutableList().apply {
|
||||
set(index, items[index].let { it.copy(pinned = !it.pinned) })
|
||||
}
|
||||
onSave.invoke(newItems)
|
||||
},
|
||||
onMoveUp = { index ->
|
||||
onSave(items.move(MoveDirection.UP, index))
|
||||
},
|
||||
onMoveDown = { index ->
|
||||
onSave(items.move(MoveDirection.DOWN, index))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavDrawerPreferenceDialog(
|
||||
items: List<NavDrawerPin>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onClick: (Int) -> Unit,
|
||||
onMoveUp: (Int) -> Unit,
|
||||
onMoveDown: (Int) -> Unit,
|
||||
) {
|
||||
BasicDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.nav_drawer_pins),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||
) {
|
||||
itemsIndexed(items, key = { _, item -> item.id }) { index, item ->
|
||||
NavDrawerPreferenceListItem(
|
||||
title = item.title,
|
||||
pinned = item.pinned,
|
||||
moveUpAllowed = index > 0,
|
||||
moveDownAllowed = index < items.lastIndex,
|
||||
onClick = { onClick.invoke(index) },
|
||||
onMoveUp = { onMoveUp.invoke(index) },
|
||||
onMoveDown = { onMoveDown.invoke(index) },
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavDrawerPreferenceListItem(
|
||||
title: String,
|
||||
pinned: Boolean,
|
||||
moveUpAllowed: Boolean,
|
||||
moveDownAllowed: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onMoveUp: () -> Unit,
|
||||
onMoveDown: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 40.dp, max = 88.dp),
|
||||
) {
|
||||
ListItem(
|
||||
selected = false,
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = title,
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = pinned,
|
||||
onCheckedChange = {
|
||||
onClick.invoke()
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.wrapContentWidth(),
|
||||
) {
|
||||
MoveButton(R.string.fa_caret_up, moveUpAllowed, onMoveUp)
|
||||
MoveButton(R.string.fa_caret_down, moveDownAllowed, onMoveDown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MoveButton(
|
||||
@StringRes icon: Int,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) = Button(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = Modifier.size(32.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(icon),
|
||||
fontSize = 16.sp,
|
||||
fontFamily = FontAwesome,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
@Composable
|
||||
fun NavDrawerPreferenceListItemPreview() {
|
||||
WholphinTheme {
|
||||
NavDrawerPreferenceListItem(
|
||||
title = "Movies",
|
||||
pinned = true,
|
||||
moveUpAllowed = true,
|
||||
moveDownAllowed = true,
|
||||
onClick = {},
|
||||
onMoveUp = {},
|
||||
onMoveDown = { },
|
||||
modifier = Modifier.width(360.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ fun PreferencesContent(
|
|||
val currentServer by seerrVm.currentSeerrServer.collectAsState(null)
|
||||
var showPinFlow by remember { mutableStateOf(false) }
|
||||
|
||||
val navDrawerPins by viewModel.navDrawerPins.observeAsState(mapOf())
|
||||
val navDrawerPins by viewModel.navDrawerPins.collectAsState(emptyList())
|
||||
var cacheUsage by remember { mutableStateOf(CacheUsage(0, 0, 0)) }
|
||||
val seerrIntegrationEnabled by viewModel.seerrEnabled.collectAsState(false)
|
||||
var seerrDialogMode by remember { mutableStateOf<SeerrDialogMode>(SeerrDialogMode.None) }
|
||||
|
|
@ -335,21 +335,16 @@ fun PreferencesContent(
|
|||
}
|
||||
|
||||
AppPreference.UserPinnedNavDrawerItems -> {
|
||||
val selectedItems =
|
||||
navDrawerPins.keys.mapNotNull {
|
||||
if (navDrawerPins[it] ?: false) it else null
|
||||
}
|
||||
MultiChoicePreference(
|
||||
NavDrawerPreference(
|
||||
title = stringResource(pref.title),
|
||||
summary = pref.summary(context, null),
|
||||
possibleValues = navDrawerPins.keys,
|
||||
selectedValues = selectedItems.toSet(),
|
||||
onValueChange = { newSelectedItems ->
|
||||
viewModel.updatePins(newSelectedItems)
|
||||
items = navDrawerPins,
|
||||
onSave = {
|
||||
viewModel.updatePins(it)
|
||||
},
|
||||
) {
|
||||
Text(it.name(context))
|
||||
}
|
||||
modifier = Modifier,
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
|
||||
AppPreference.SendAppLogs -> {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@ package com.github.damontecres.wholphin.ui.preferences
|
|||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asFlow
|
||||
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.JellyfinUser
|
||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||
import com.github.damontecres.wholphin.data.model.NavPinType
|
||||
|
|
@ -17,23 +15,28 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.resetSubtitles
|
||||
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.NavDrawerService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||
import com.github.damontecres.wholphin.ui.detail.DebugViewModel.Companion.sendAppLogs
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.model.ClientInfo
|
||||
import org.jellyfin.sdk.model.DeviceInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -48,6 +51,7 @@ class PreferencesViewModel
|
|||
private val rememberTabManager: RememberTabManager,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val navDrawerItemRepository: NavDrawerItemRepository,
|
||||
private val navDrawerService: NavDrawerService,
|
||||
private val serverPreferencesDao: ServerPreferencesDao,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
private val deviceInfo: DeviceInfo,
|
||||
|
|
@ -55,7 +59,46 @@ class PreferencesViewModel
|
|||
) : ViewModel(),
|
||||
RememberTabManager by rememberTabManager {
|
||||
private lateinit var allNavDrawerItems: List<NavDrawerItem>
|
||||
val navDrawerPins = MutableLiveData<Map<NavDrawerItem, Boolean>>(mapOf())
|
||||
// val navDrawerPins = MutableLiveData<List<NavDrawerPin>>(emptyList())
|
||||
|
||||
val navDrawerPins =
|
||||
navDrawerService.state
|
||||
.combine(
|
||||
serverRepository.currentUser.asFlow(),
|
||||
) { state, user ->
|
||||
Pair(state, user)
|
||||
}.combine(seerrServerRepository.active) { (state, user), seerr ->
|
||||
Triple(state, user, seerr)
|
||||
}.map { (state, user, seerr) ->
|
||||
withContext(Dispatchers.IO) {
|
||||
val navDrawerPins =
|
||||
serverPreferencesDao
|
||||
.getNavDrawerPinnedItems(user!!)
|
||||
.associateBy { it.itemId }
|
||||
|
||||
val allItems = state.let { it.items + it.moreItems }
|
||||
val pins =
|
||||
allItems
|
||||
.sortedBy {
|
||||
navDrawerPins[it.id]?.order?.takeIf { it >= 0 } ?: Int.MAX_VALUE
|
||||
}.mapNotNull {
|
||||
if (!seerr && it is NavDrawerItem.Discover) {
|
||||
null
|
||||
} else {
|
||||
// Assume pinned if unknown
|
||||
val pinned = navDrawerPins[it.id]?.type ?: NavPinType.PINNED
|
||||
NavDrawerPin(
|
||||
it.id,
|
||||
it.name(context),
|
||||
pinned == NavPinType.PINNED,
|
||||
it,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pins
|
||||
}
|
||||
}
|
||||
|
||||
val currentUser get() = serverRepository.currentUser
|
||||
|
||||
|
|
@ -70,42 +113,50 @@ class PreferencesViewModel
|
|||
init {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
allNavDrawerItems = navDrawerItemRepository.getNavDrawerItems()
|
||||
val pins = serverPreferencesDao.getNavDrawerPinnedItems(user)
|
||||
val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) }
|
||||
this@PreferencesViewModel.navDrawerPins.setValueOnMain(navDrawerPins)
|
||||
// fetchNavDrawerPins(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePins(newSelectedItems: List<NavDrawerItem>) {
|
||||
private suspend fun fetchNavDrawerPins(user: JellyfinUser) {
|
||||
navDrawerService.state.map {
|
||||
val navDrawerPins =
|
||||
serverPreferencesDao.getNavDrawerPinnedItems(user).associateBy { it.itemId }
|
||||
|
||||
val allItems = navDrawerService.state.first().let { it.items + it.moreItems }
|
||||
val pins =
|
||||
allItems
|
||||
.sortedBy { navDrawerPins[it.id]?.order?.takeIf { it >= 0 } ?: Int.MAX_VALUE }
|
||||
.map {
|
||||
// Assume pinned if unknown
|
||||
val pinned = navDrawerPins[it.id]?.type ?: NavPinType.PINNED
|
||||
NavDrawerPin(it.id, it.name(context), pinned == NavPinType.PINNED, it)
|
||||
}
|
||||
pins
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePins(items: List<NavDrawerPin>) {
|
||||
viewModelScope.launchIO(ExceptionHandler(true)) {
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
val disabledItems =
|
||||
mutableListOf<NavDrawerItem>().apply {
|
||||
addAll(allNavDrawerItems)
|
||||
removeAll(newSelectedItems)
|
||||
serverRepository.currentUserDto.value?.let { userDto ->
|
||||
if (user.id == userDto.id) {
|
||||
Timber.v("Updating pins")
|
||||
val toSave =
|
||||
items.mapIndexed { index, item ->
|
||||
NavDrawerPinnedItem(
|
||||
user.rowId,
|
||||
item.id,
|
||||
if (item.pinned) NavPinType.PINNED else NavPinType.UNPINNED,
|
||||
index,
|
||||
)
|
||||
}
|
||||
serverPreferencesDao.saveNavDrawerPinnedItems(*toSave.toTypedArray())
|
||||
navDrawerService.updateNavDrawer(user, userDto)
|
||||
} else {
|
||||
throw IllegalStateException("User IDs do not match")
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue