mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Remember library/collection sorting (#150)
Closes #148 Closes #83 Remember the chosen sort order & direction per user profile on device. Also adds a few more sort options. This is only saved on the device and not saved/loaded from the server.
This commit is contained in:
parent
08577b8a31
commit
40267be633
12 changed files with 486 additions and 51 deletions
|
|
@ -0,0 +1,303 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "24390d64259f19e0e67ecfb827b974fb",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "servers",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT, `url` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "users",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`rowId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` TEXT NOT NULL, `name` TEXT, `serverId` TEXT NOT NULL, `accessToken` TEXT, FOREIGN KEY(`serverId`) REFERENCES `servers`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "rowId",
|
||||||
|
"columnName": "rowId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "serverId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accessToken",
|
||||||
|
"columnName": "accessToken",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"rowId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_users_id_serverId",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id",
|
||||||
|
"serverId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_users_id_serverId` ON `${TABLE_NAME}` (`id`, `serverId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_users_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_users_id` ON `${TABLE_NAME}` (`id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_users_serverId",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"serverId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_users_serverId` ON `${TABLE_NAME}` (`serverId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "servers",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"serverId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "ItemPlayback",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`rowId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` INTEGER NOT NULL, `itemId` TEXT NOT NULL, `sourceId` TEXT, `audioIndex` INTEGER NOT NULL, `subtitleIndex` INTEGER NOT NULL, FOREIGN KEY(`userId`) REFERENCES `users`(`rowId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "rowId",
|
||||||
|
"columnName": "rowId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "userId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "itemId",
|
||||||
|
"columnName": "itemId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sourceId",
|
||||||
|
"columnName": "sourceId",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "audioIndex",
|
||||||
|
"columnName": "audioIndex",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "subtitleIndex",
|
||||||
|
"columnName": "subtitleIndex",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"rowId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_ItemPlayback_userId_itemId",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"userId",
|
||||||
|
"itemId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ItemPlayback_userId_itemId` ON `${TABLE_NAME}` (`userId`, `itemId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "users",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"rowId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "NavDrawerPinnedItem",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` INTEGER NOT NULL, `itemId` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`userId`, `itemId`), FOREIGN KEY(`userId`) REFERENCES `users`(`rowId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "userId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "itemId",
|
||||||
|
"columnName": "itemId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"userId",
|
||||||
|
"itemId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "users",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"rowId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "LibraryDisplayInfo",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` INTEGER NOT NULL, `itemId` TEXT NOT NULL, `sort` TEXT NOT NULL, `direction` TEXT NOT NULL, PRIMARY KEY(`userId`, `itemId`), FOREIGN KEY(`userId`) REFERENCES `users`(`rowId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "userId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "itemId",
|
||||||
|
"columnName": "itemId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sort",
|
||||||
|
"columnName": "sort",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "direction",
|
||||||
|
"columnName": "direction",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"userId",
|
||||||
|
"itemId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_LibraryDisplayInfo_userId_itemId",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"userId",
|
||||||
|
"itemId"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_LibraryDisplayInfo_userId_itemId` ON `${TABLE_NAME}` (`userId`, `itemId`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "users",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"rowId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '24390d64259f19e0e67ecfb827b974fb')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,17 +10,21 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||||
|
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
import org.jellyfin.sdk.model.serializer.toUUID
|
import org.jellyfin.sdk.model.serializer.toUUID
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [JellyfinServer::class, JellyfinUser::class, ItemPlayback::class, NavDrawerPinnedItem::class],
|
entities = [JellyfinServer::class, JellyfinUser::class, ItemPlayback::class, NavDrawerPinnedItem::class, LibraryDisplayInfo::class],
|
||||||
version = 5,
|
version = 6,
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(3, 4),
|
AutoMigration(3, 4),
|
||||||
AutoMigration(4, 5),
|
AutoMigration(4, 5),
|
||||||
|
AutoMigration(5, 6),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
|
|
@ -30,6 +34,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun itemPlaybackDao(): ItemPlaybackDao
|
abstract fun itemPlaybackDao(): ItemPlaybackDao
|
||||||
|
|
||||||
abstract fun serverPreferencesDao(): ServerPreferencesDao
|
abstract fun serverPreferencesDao(): ServerPreferencesDao
|
||||||
|
|
||||||
|
abstract fun libraryDisplayInfoDao(): LibraryDisplayInfoDao
|
||||||
}
|
}
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
|
|
@ -38,6 +44,18 @@ class Converters {
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun convertToUUID(str: String): UUID = str.toUUID()
|
fun convertToUUID(str: String): UUID = str.toUUID()
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun convertItemSortBy(sort: ItemSortBy): String = sort.serialName
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun convertItemSortBy(sort: String): ItemSortBy? = ItemSortBy.fromNameOrNull(sort)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun convertSortOrder(sort: SortOrder): String = sort.serialName
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun convertSortOrder(sort: String): SortOrder? = SortOrder.fromNameOrNull(sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Migrations {
|
object Migrations {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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.JellyfinUser
|
||||||
|
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LibraryDisplayInfoDao {
|
||||||
|
fun getItem(
|
||||||
|
user: JellyfinUser,
|
||||||
|
itemId: UUID,
|
||||||
|
): LibraryDisplayInfo? = getItem(user.rowId, itemId)
|
||||||
|
|
||||||
|
@Query("SELECT * from LibraryDisplayInfo WHERE userId=:userId AND itemId=:itemId")
|
||||||
|
fun getItem(
|
||||||
|
userId: Int,
|
||||||
|
itemId: UUID,
|
||||||
|
): LibraryDisplayInfo?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun saveItem(item: LibraryDisplayInfo): Long
|
||||||
|
|
||||||
|
@Query("SELECT * from LibraryDisplayInfo WHERE userId=:userId")
|
||||||
|
fun getItems(userId: Int): List<LibraryDisplayInfo>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
@file:UseSerializers(UUIDSerializer::class)
|
||||||
|
|
||||||
|
package com.github.damontecres.wholphin.data.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.ForeignKey
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.Index
|
||||||
|
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = JellyfinUser::class,
|
||||||
|
parentColumns = arrayOf("rowId"),
|
||||||
|
childColumns = arrayOf("userId"),
|
||||||
|
onDelete = ForeignKey.CASCADE,
|
||||||
|
onUpdate = ForeignKey.CASCADE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
primaryKeys = ["userId", "itemId"],
|
||||||
|
indices = [Index("userId", "itemId", unique = true)],
|
||||||
|
)
|
||||||
|
@Serializable
|
||||||
|
data class LibraryDisplayInfo(
|
||||||
|
val userId: Int,
|
||||||
|
val itemId: UUID,
|
||||||
|
val sort: ItemSortBy,
|
||||||
|
val direction: SortOrder,
|
||||||
|
) {
|
||||||
|
@Ignore @Transient
|
||||||
|
val sortAndDirection = SortAndDirection(sort, direction)
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.room.Room
|
||||||
import com.github.damontecres.wholphin.data.AppDatabase
|
import com.github.damontecres.wholphin.data.AppDatabase
|
||||||
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
||||||
import com.github.damontecres.wholphin.data.JellyfinServerDao
|
import com.github.damontecres.wholphin.data.JellyfinServerDao
|
||||||
|
import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
||||||
import com.github.damontecres.wholphin.data.Migrations
|
import com.github.damontecres.wholphin.data.Migrations
|
||||||
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
|
|
@ -51,6 +52,10 @@ object DatabaseModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun serverPreferencesDao(db: AppDatabase): ServerPreferencesDao = db.serverPreferencesDao()
|
fun serverPreferencesDao(db: AppDatabase): ServerPreferencesDao = db.serverPreferencesDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun libraryDisplayInfoDao(db: AppDatabase): LibraryDisplayInfoDao = db.libraryDisplayInfoDao()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun userPreferencesDataStore(
|
fun userPreferencesDataStore(
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
|
import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
||||||
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
|
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
|
|
@ -62,6 +65,8 @@ class CollectionFolderViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
|
private val serverRepository: ServerRepository,
|
||||||
|
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
||||||
) : ItemViewModel(api) {
|
) : ItemViewModel(api) {
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||||
val pager = MutableLiveData<List<BaseItem?>>(listOf())
|
val pager = MutableLiveData<List<BaseItem?>>(listOf())
|
||||||
|
|
@ -71,7 +76,7 @@ class CollectionFolderViewModel
|
||||||
fun init(
|
fun init(
|
||||||
itemId: UUID?,
|
itemId: UUID?,
|
||||||
potential: BaseItem?,
|
potential: BaseItem?,
|
||||||
sortAndDirection: SortAndDirection,
|
initialSortAndDirection: SortAndDirection?,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
): Job =
|
): Job =
|
||||||
|
|
@ -82,12 +87,41 @@ class CollectionFolderViewModel
|
||||||
) + Dispatchers.IO,
|
) + Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
if (itemId != null) {
|
if (itemId != null) {
|
||||||
fetchItem(itemId, potential)
|
fetchItem(itemId)
|
||||||
|
|
||||||
|
val sortAndDirection =
|
||||||
|
if (initialSortAndDirection == null) {
|
||||||
|
serverRepository.currentUser?.let { user ->
|
||||||
|
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
||||||
|
} ?: SortAndDirection.DEFAULT
|
||||||
|
} else {
|
||||||
|
SortAndDirection.DEFAULT
|
||||||
|
}
|
||||||
|
loadResults(sortAndDirection, recursive, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSortChange(
|
||||||
|
sortAndDirection: SortAndDirection,
|
||||||
|
recursive: Boolean,
|
||||||
|
filter: GetItemsFilter,
|
||||||
|
) {
|
||||||
|
serverRepository.currentUser?.let { user ->
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val libraryDisplayInfo =
|
||||||
|
LibraryDisplayInfo(
|
||||||
|
userId = user.rowId,
|
||||||
|
itemId = itemId,
|
||||||
|
sort = sortAndDirection.sort,
|
||||||
|
direction = sortAndDirection.direction,
|
||||||
|
)
|
||||||
|
libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadResults(sortAndDirection, recursive, filter)
|
loadResults(sortAndDirection, recursive, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadResults(
|
private fun loadResults(
|
||||||
sortAndDirection: SortAndDirection,
|
sortAndDirection: SortAndDirection,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
|
|
@ -126,17 +160,25 @@ class CollectionFolderViewModel
|
||||||
recursive = recursive,
|
recursive = recursive,
|
||||||
excludeItemIds = item?.let { listOf(item.id) },
|
excludeItemIds = item?.let { listOf(item.id) },
|
||||||
sortBy =
|
sortBy =
|
||||||
listOf(
|
buildList {
|
||||||
sortAndDirection.sort,
|
add(sortAndDirection.sort)
|
||||||
ItemSortBy.SORT_NAME,
|
if (sortAndDirection.sort != ItemSortBy.SORT_NAME) {
|
||||||
ItemSortBy.PRODUCTION_YEAR,
|
add(ItemSortBy.SORT_NAME)
|
||||||
),
|
}
|
||||||
|
if (item?.data?.collectionType == CollectionType.MOVIES) {
|
||||||
|
add(ItemSortBy.PRODUCTION_YEAR)
|
||||||
|
}
|
||||||
|
},
|
||||||
sortOrder =
|
sortOrder =
|
||||||
listOf(
|
buildList {
|
||||||
sortAndDirection.direction,
|
add(sortAndDirection.direction)
|
||||||
SortOrder.ASCENDING,
|
if (sortAndDirection.sort != ItemSortBy.SORT_NAME) {
|
||||||
SortOrder.ASCENDING,
|
add(SortOrder.ASCENDING)
|
||||||
),
|
}
|
||||||
|
if (item?.data?.collectionType == CollectionType.MOVIES) {
|
||||||
|
add(SortOrder.ASCENDING)
|
||||||
|
}
|
||||||
|
},
|
||||||
fields = SlimItemFields,
|
fields = SlimItemFields,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -198,18 +240,14 @@ fun CollectionFolderGrid(
|
||||||
onClickItem: (BaseItem) -> Unit,
|
onClickItem: (BaseItem) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: CollectionFolderViewModel = hiltViewModel(),
|
viewModel: CollectionFolderViewModel = hiltViewModel(),
|
||||||
initialSortAndDirection: SortAndDirection =
|
initialSortAndDirection: SortAndDirection? = null,
|
||||||
SortAndDirection(
|
|
||||||
ItemSortBy.SORT_NAME,
|
|
||||||
SortOrder.ASCENDING,
|
|
||||||
),
|
|
||||||
showTitle: Boolean = true,
|
showTitle: Boolean = true,
|
||||||
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
OneTimeLaunchedEffect {
|
OneTimeLaunchedEffect {
|
||||||
viewModel.init(itemId, item, initialSortAndDirection, recursive, initialFilter)
|
viewModel.init(itemId, item, initialSortAndDirection, recursive, initialFilter)
|
||||||
}
|
}
|
||||||
val sortAndDirection by viewModel.sortAndDirection.observeAsState(initialSortAndDirection)
|
val sortAndDirection by viewModel.sortAndDirection.observeAsState()
|
||||||
val filter by viewModel.filter.observeAsState(initialFilter)
|
val filter by viewModel.filter.observeAsState(initialFilter)
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||||
val item by viewModel.item.observeAsState()
|
val item by viewModel.item.observeAsState()
|
||||||
|
|
@ -226,11 +264,11 @@ fun CollectionFolderGrid(
|
||||||
preferences,
|
preferences,
|
||||||
item,
|
item,
|
||||||
pager,
|
pager,
|
||||||
sortAndDirection = sortAndDirection,
|
sortAndDirection = sortAndDirection!!,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onSortChange = {
|
onSortChange = {
|
||||||
viewModel.loadResults(it, recursive, filter)
|
viewModel.onSortChange(it, recursive, filter)
|
||||||
},
|
},
|
||||||
showTitle = showTitle,
|
showTitle = showTitle,
|
||||||
positionCallback = positionCallback,
|
positionCallback = positionCallback,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ data class SortAndDirection(
|
||||||
val direction: SortOrder,
|
val direction: SortOrder,
|
||||||
) {
|
) {
|
||||||
fun flip() = copy(direction = direction.flip())
|
fun flip() = copy(direction = direction.flip())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT =
|
||||||
|
SortAndDirection(
|
||||||
|
ItemSortBy.SORT_NAME,
|
||||||
|
SortOrder.ASCENDING,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SortOrder.flip() = if (this == SortOrder.ASCENDING) SortOrder.DESCENDING else SortOrder.ASCENDING
|
fun SortOrder.flip() = if (this == SortOrder.ASCENDING) SortOrder.DESCENDING else SortOrder.ASCENDING
|
||||||
|
|
@ -20,6 +28,9 @@ val MovieSortOptions =
|
||||||
ItemSortBy.PREMIERE_DATE,
|
ItemSortBy.PREMIERE_DATE,
|
||||||
ItemSortBy.DATE_CREATED,
|
ItemSortBy.DATE_CREATED,
|
||||||
ItemSortBy.DATE_PLAYED,
|
ItemSortBy.DATE_PLAYED,
|
||||||
|
ItemSortBy.COMMUNITY_RATING,
|
||||||
|
ItemSortBy.CRITIC_RATING,
|
||||||
|
ItemSortBy.PLAY_COUNT,
|
||||||
ItemSortBy.RANDOM,
|
ItemSortBy.RANDOM,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,6 +41,7 @@ val SeriesSortOptions =
|
||||||
ItemSortBy.DATE_CREATED,
|
ItemSortBy.DATE_CREATED,
|
||||||
ItemSortBy.DATE_LAST_CONTENT_ADDED,
|
ItemSortBy.DATE_LAST_CONTENT_ADDED,
|
||||||
ItemSortBy.DATE_PLAYED,
|
ItemSortBy.DATE_PLAYED,
|
||||||
|
ItemSortBy.COMMUNITY_RATING,
|
||||||
ItemSortBy.RANDOM,
|
ItemSortBy.RANDOM,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -50,5 +62,8 @@ fun getStringRes(sort: ItemSortBy): Int =
|
||||||
ItemSortBy.DATE_LAST_CONTENT_ADDED -> R.string.sort_by_date_episode_added
|
ItemSortBy.DATE_LAST_CONTENT_ADDED -> R.string.sort_by_date_episode_added
|
||||||
ItemSortBy.DATE_PLAYED -> R.string.sort_by_date_played
|
ItemSortBy.DATE_PLAYED -> R.string.sort_by_date_played
|
||||||
ItemSortBy.RANDOM -> R.string.sort_by_random
|
ItemSortBy.RANDOM -> R.string.sort_by_random
|
||||||
|
ItemSortBy.COMMUNITY_RATING -> R.string.community_rating
|
||||||
|
ItemSortBy.CRITIC_RATING -> R.string.critic_rating
|
||||||
|
ItemSortBy.PLAY_COUNT -> R.string.play_count
|
||||||
else -> throw IllegalArgumentException("Unsupported sort option: $sort")
|
else -> throw IllegalArgumentException("Unsupported sort option: $sort")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,21 +25,11 @@ abstract class ItemViewModel(
|
||||||
val api: ApiClient,
|
val api: ApiClient,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val item = MutableLiveData<BaseItem?>(null)
|
val item = MutableLiveData<BaseItem?>(null)
|
||||||
|
lateinit var itemId: UUID
|
||||||
|
|
||||||
suspend fun fetchItem(
|
suspend fun fetchItem(itemId: UUID): BaseItem =
|
||||||
itemId: UUID,
|
|
||||||
potential: BaseItem?,
|
|
||||||
): BaseItem =
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
// val fetchedItem =
|
this@ItemViewModel.itemId = itemId
|
||||||
// when {
|
|
||||||
// item.value == null && potential?.id == itemId -> potential
|
|
||||||
// item.value?.id == itemId -> item.value
|
|
||||||
// else -> {
|
|
||||||
// val it = api.userLibraryApi.getItem(itemId).content
|
|
||||||
// BaseItem.from(it, api)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
val it = api.userLibraryApi.getItem(itemId).content
|
val it = api.userLibraryApi.getItem(itemId).content
|
||||||
val fetchedItem = BaseItem.from(it, api)
|
val fetchedItem = BaseItem.from(it, api)
|
||||||
return@withContext fetchedItem.let {
|
return@withContext fetchedItem.let {
|
||||||
|
|
@ -89,10 +79,8 @@ abstract class LoadingItemViewModel(
|
||||||
|
|
||||||
open suspend fun fetchAndSetItem(itemId: UUID): BaseItem =
|
open suspend fun fetchAndSetItem(itemId: UUID): BaseItem =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val fetchedItem = api.userLibraryApi.getItem(itemId).content
|
val item = fetchItem(itemId)
|
||||||
val item = BaseItem.from(fetchedItem, api)
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@LoadingItemViewModel.item.value = item
|
|
||||||
loading.value = LoadingState.Success
|
loading.value = LoadingState.Success
|
||||||
}
|
}
|
||||||
return@withContext item
|
return@withContext item
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ class PlaylistViewModel
|
||||||
Dispatchers.IO +
|
Dispatchers.IO +
|
||||||
LoadingExceptionHandler(loading, "Failed to fetch playlist $playlistId"),
|
LoadingExceptionHandler(loading, "Failed to fetch playlist $playlistId"),
|
||||||
) {
|
) {
|
||||||
val playlist = fetchItem(playlistId, null)
|
val playlist = fetchItem(playlistId)
|
||||||
val request =
|
val request =
|
||||||
GetPlaylistItemsRequest(
|
GetPlaylistItemsRequest(
|
||||||
playlistId = playlist.id,
|
playlistId = playlist.id,
|
||||||
|
|
|
||||||
|
|
@ -48,17 +48,14 @@ class MovieViewModel
|
||||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||||
private val themeSongPlayer: ThemeSongPlayer,
|
private val themeSongPlayer: ThemeSongPlayer,
|
||||||
) : LoadingItemViewModel(api) {
|
) : LoadingItemViewModel(api) {
|
||||||
private lateinit var itemId: UUID
|
|
||||||
|
|
||||||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||||
val people = MutableLiveData<List<Person>>(listOf())
|
val people = MutableLiveData<List<Person>>(listOf())
|
||||||
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
||||||
val similar = MutableLiveData<List<BaseItem>>()
|
val similar = MutableLiveData<List<BaseItem>>()
|
||||||
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
|
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
|
||||||
|
|
||||||
fun init(itemId: UUID): Job? {
|
fun init(itemId: UUID): Job? =
|
||||||
this.itemId = itemId
|
viewModelScope.launch(
|
||||||
return viewModelScope.launch(
|
|
||||||
Dispatchers.IO +
|
Dispatchers.IO +
|
||||||
LoadingExceptionHandler(
|
LoadingExceptionHandler(
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -124,7 +121,6 @@ class MovieViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun setWatched(played: Boolean) =
|
fun setWatched(played: Boolean) =
|
||||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ class SeriesViewModel
|
||||||
"Error loading series $seriesId",
|
"Error loading series $seriesId",
|
||||||
) + Dispatchers.IO,
|
) + Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
val item = fetchItem(seriesId, potential)
|
val item = fetchItem(seriesId)
|
||||||
val seasons = getSeasons(item)
|
val seasons = getSeasons(item)
|
||||||
|
|
||||||
// If a particular season was requested, fetch those episodes, otherwise get the first season
|
// If a particular season was requested, fetch those episodes, otherwise get the first season
|
||||||
|
|
@ -263,7 +263,7 @@ class SeriesViewModel
|
||||||
if (listIndex != null) {
|
if (listIndex != null) {
|
||||||
refreshEpisode(itemId, listIndex)
|
refreshEpisode(itemId, listIndex)
|
||||||
} else {
|
} else {
|
||||||
fetchItem(seriesId, null)
|
fetchItem(seriesId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ class SeriesViewModel
|
||||||
played: Boolean,
|
played: Boolean,
|
||||||
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
) = viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||||
setWatched(seasonId, played, null)
|
setWatched(seasonId, played, null)
|
||||||
val series = fetchItem(seriesId, null)
|
val series = fetchItem(seriesId)
|
||||||
val seasons = getSeasons(series)
|
val seasons = getSeasons(series)
|
||||||
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +284,7 @@ class SeriesViewModel
|
||||||
} else {
|
} else {
|
||||||
api.playStateApi.markUnplayedItem(seriesId)
|
api.playStateApi.markUnplayedItem(seriesId)
|
||||||
}
|
}
|
||||||
val series = fetchItem(seriesId, null)
|
val series = fetchItem(seriesId)
|
||||||
val seasons = getSeasons(series)
|
val seasons = getSeasons(series)
|
||||||
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,5 +102,8 @@
|
||||||
<string name="nav_drawer_switch_on_focus_summary_off">Click to switch pages</string>
|
<string name="nav_drawer_switch_on_focus_summary_off">Click to switch pages</string>
|
||||||
<string name="show_next_up_when">Show next up</string>
|
<string name="show_next_up_when">Show next up</string>
|
||||||
<string name="next_up">Next Up</string>
|
<string name="next_up">Next Up</string>
|
||||||
|
<string name="community_rating">Community Rating</string>
|
||||||
|
<string name="critic_rating">Critic Rating</string>
|
||||||
|
<string name="play_count">Play Count</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue