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

@ -8,20 +8,26 @@ import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
import org.jellyfin.sdk.model.serializer.toUUID
import java.util.UUID
@Database(
entities = [JellyfinServer::class, JellyfinUser::class, ItemPlayback::class],
version = 4,
entities = [JellyfinServer::class, JellyfinUser::class, ItemPlayback::class, NavDrawerPinnedItem::class],
version = 5,
exportSchema = true,
autoMigrations = [AutoMigration(3, 4)],
autoMigrations = [
AutoMigration(3, 4),
AutoMigration(4, 5),
],
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun serverDao(): JellyfinServerDao
abstract fun itemPlaybackDao(): ItemPlaybackDao
abstract fun serverPreferencesDao(): ServerPreferencesDao
}
class Converters {

View file

@ -81,7 +81,7 @@ interface JellyfinServerDao {
fun updateUser(user: JellyfinUser): Int
@Transaction
fun addOrUpdateUser(user: JellyfinUser) {
fun addOrUpdateUser(user: JellyfinUser): JellyfinUser {
val result = addUser(user)
if (result == -1L) {
val toSave =
@ -92,7 +92,9 @@ interface JellyfinServerDao {
user
}
updateUser(toSave)
return toSave
}
return user.copy(rowId = result.toInt())
}
@Query("SELECT * FROM users WHERE serverId = :serverId AND id = :userId")

View file

@ -0,0 +1,66 @@
package com.github.damontecres.wholphin.data
import android.content.Context
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
import com.github.damontecres.wholphin.data.model.NavPinType
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 org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.CollectionType
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NavDrawerItemRepository
@Inject
constructor(
@param:ApplicationContext private val context: Context,
private val api: ApiClient,
private val serverRepository: ServerRepository,
private val serverPreferencesDao: ServerPreferencesDao,
) {
suspend fun getNavDrawerItems(): List<NavDrawerItem> {
val user = serverRepository.currentUser
val userViews =
api.userViewsApi
.getUserViews(userId = user?.id)
.content.items
val builtins = listOf(NavDrawerItem.Favorites)
val libraries =
userViews
.filter { it.collectionType in supportedCollectionTypes }
.map {
ServerNavDrawerItem(
itemId = it.id,
name = it.name ?: it.id.toString(),
destination = BaseItem.from(it, api).destination(),
type = it.collectionType ?: CollectionType.UNKNOWN,
)
}
return builtins + libraries
}
suspend fun getFilteredNavDrawerItems(items: List<NavDrawerItem>): List<NavDrawerItem> {
val user = serverRepository.currentUser
val navDrawerPins =
user
?.let {
serverPreferencesDao.getNavDrawerPinnedItems(it)
}.orEmpty()
val filtered = items.filter { navDrawerPins.isPinned(it.id) }
if (items.size != filtered.size) {
// Some were filtered out, check if should include More
if (navDrawerPins.isPinned(NavDrawerItem.More.id)) {
return filtered + listOf(NavDrawerItem.More)
}
}
return filtered
}
}
fun List<NavDrawerPinnedItem>.isPinned(id: String) = (firstOrNull { it.itemId == id }?.type ?: NavPinType.PINNED) == NavPinType.PINNED

View file

@ -0,0 +1,18 @@
package com.github.damontecres.wholphin.data
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
@Dao
interface ServerPreferencesDao {
fun getNavDrawerPinnedItems(user: JellyfinUser): List<NavDrawerPinnedItem> = getNavDrawerPinnedItems(user.rowId)
@Query("SELECT * from NavDrawerPinnedItem WHERE userId=:userId")
fun getNavDrawerPinnedItems(userId: Int): List<NavDrawerPinnedItem>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveNavDrawerPinnedItems(vararg items: NavDrawerPinnedItem)
}

View file

@ -82,15 +82,13 @@ class ServerRepository
val sysInfo by apiClient.systemApi.getSystemInfo()
val updatedServer = server.copy(name = sysInfo.serverName)
val updatedUser =
var updatedUser =
user.copy(
id = userDto.id,
name = userDto.name,
)
if (updatedUser.rowId <= 0) {
}
serverDao.addOrUpdateServer(updatedServer)
serverDao.addOrUpdateUser(updatedUser)
updatedUser = serverDao.addOrUpdateUser(updatedUser)
userPreferencesDataStore.updateData {
it
.toBuilder()

View file

@ -0,0 +1,28 @@
package com.github.damontecres.wholphin.data.model
import androidx.room.Entity
import androidx.room.ForeignKey
import com.github.damontecres.wholphin.data.JellyfinUser
enum class NavPinType {
PINNED,
UNPINNED,
}
@Entity(
foreignKeys = [
ForeignKey(
entity = JellyfinUser::class,
parentColumns = arrayOf("rowId"),
childColumns = arrayOf("userId"),
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
),
],
primaryKeys = ["userId", "itemId"],
)
data class NavDrawerPinnedItem(
val userId: Int,
val itemId: String,
val type: NavPinType,
)