mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11: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
|
|
@ -0,0 +1,147 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.asFlow
|
||||
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.data.model.NavPinType
|
||||
import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||
import com.github.damontecres.wholphin.util.supportedCollectionTypes
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.UserDto
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NavDrawerService
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
@param:DefaultCoroutineScope private val coroutineScope: CoroutineScope,
|
||||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val serverPreferencesDao: ServerPreferencesDao,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
) {
|
||||
private val _state = MutableStateFlow(NavDrawerItemState.EMPTY)
|
||||
val state: StateFlow<NavDrawerItemState> = _state
|
||||
|
||||
init {
|
||||
serverRepository.currentUser
|
||||
.asFlow()
|
||||
.combine(serverRepository.currentUserDto.asFlow()) { user, userDto ->
|
||||
Pair(user, userDto)
|
||||
}.onEach { (user, userDto) ->
|
||||
Timber.d("User updated: user=%s, userDto=%s", user?.id, userDto?.id)
|
||||
_state.update {
|
||||
it.copy(
|
||||
items = emptyList(),
|
||||
moreItems = emptyList(),
|
||||
)
|
||||
}
|
||||
if (user != null && userDto != null && user.id == userDto.id) {
|
||||
updateNavDrawer(user, userDto)
|
||||
}
|
||||
}.launchIn(coroutineScope)
|
||||
seerrServerRepository.active
|
||||
.onEach { discoverActive ->
|
||||
_state.update { it.copy(discoverEnabled = discoverActive) }
|
||||
}.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
suspend fun updateNavDrawer(
|
||||
user: JellyfinUser,
|
||||
userDto: UserDto,
|
||||
) {
|
||||
val tvAccess = userDto.policy?.enableLiveTvAccess ?: false
|
||||
val userViews =
|
||||
api.userViewsApi
|
||||
.getUserViews(userId = user.id)
|
||||
.content.items
|
||||
val recordingFolders =
|
||||
if (tvAccess) {
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(userId = user.id)
|
||||
.content.items
|
||||
.map { it.id }
|
||||
.toSet()
|
||||
} else {
|
||||
setOf()
|
||||
}
|
||||
|
||||
val builtins = listOf(NavDrawerItem.Favorites, NavDrawerItem.Discover)
|
||||
|
||||
val libraries =
|
||||
userViews
|
||||
.filter { it.collectionType in supportedCollectionTypes || it.id in recordingFolders }
|
||||
.map {
|
||||
val destination =
|
||||
if (it.id in recordingFolders) {
|
||||
Destination.Recordings(it.id)
|
||||
} else {
|
||||
BaseItem.from(it, api).destination()
|
||||
}
|
||||
ServerNavDrawerItem(
|
||||
itemId = it.id,
|
||||
name = it.name ?: it.id.toString(),
|
||||
destination = destination,
|
||||
type = it.collectionType ?: CollectionType.UNKNOWN,
|
||||
)
|
||||
}
|
||||
val allItems = builtins + libraries
|
||||
|
||||
val navDrawerPins =
|
||||
serverPreferencesDao.getNavDrawerPinnedItems(user).associateBy { it.itemId }
|
||||
|
||||
val items = mutableListOf<NavDrawerItem>()
|
||||
val moreItems = mutableListOf<NavDrawerItem>()
|
||||
allItems
|
||||
// Sort by order if non-default, existing items before customize will have -1 value
|
||||
// New items from the server will get Int.MAX_VALUE
|
||||
// Items the user doesn't have access to anymore will be skipped
|
||||
.sortedBy { navDrawerPins[it.id]?.order?.takeIf { it >= 0 } ?: Int.MAX_VALUE }
|
||||
.forEach {
|
||||
// Assume pinned if unknown
|
||||
val pinned = navDrawerPins[it.id]?.type ?: NavPinType.PINNED
|
||||
if (pinned == NavPinType.PINNED) {
|
||||
items.add(it)
|
||||
} else {
|
||||
moreItems.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
items = items,
|
||||
moreItems = moreItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class NavDrawerItemState(
|
||||
val items: List<NavDrawerItem>,
|
||||
val moreItems: List<NavDrawerItem>,
|
||||
val discoverEnabled: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = NavDrawerItemState(emptyList(), emptyList(), false)
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,10 @@ annotation class StandardOkHttpClient
|
|||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class IoCoroutineScope
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class DefaultCoroutineScope
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppModule {
|
||||
|
|
@ -177,6 +181,11 @@ object AppModule {
|
|||
@IoCoroutineScope
|
||||
fun ioCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@DefaultCoroutineScope
|
||||
fun defaultCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun workManager(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue