Store remembered tabs in app database instead of preferences (#1541)
## Description Moves storing the remembered tabs from app preferences to the app database. This means deleting the user will remove the remembered tabs and the app preferences file is smaller and faster to load. The remembered tabs are migrated automatically when the app is updated. There are no significant user facing changes in this PR. ### Related issues N/A ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
a4ee1e5175
commit
b49926f80b
24 changed files with 1379 additions and 599 deletions
|
|
@ -0,0 +1,720 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 34,
|
||||
"identityHash": "278e485064fc9f184a2181599f0f8efb",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "servers",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT, `url` TEXT NOT NULL, `version` TEXT, `lastUsed` TEXT, 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
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUsed",
|
||||
"columnName": "lastUsed",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"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, `pin` TEXT, `requireLogin` INTEGER NOT NULL DEFAULT false, `lastUsed` TEXT, `uiLanguage` 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"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pin",
|
||||
"columnName": "pin",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "requireLogin",
|
||||
"columnName": "requireLogin",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUsed",
|
||||
"columnName": "lastUsed",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "uiLanguage",
|
||||
"columnName": "uiLanguage",
|
||||
"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, `order` INTEGER NOT NULL DEFAULT -1, 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
|
||||
},
|
||||
{
|
||||
"fieldPath": "order",
|
||||
"columnName": "order",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "-1"
|
||||
}
|
||||
],
|
||||
"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, `filter` TEXT NOT NULL DEFAULT '{}', `viewOptions` TEXT, 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
|
||||
},
|
||||
{
|
||||
"fieldPath": "filter",
|
||||
"columnName": "filter",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'{}'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "viewOptions",
|
||||
"columnName": "viewOptions",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "playback_effects",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`jellyfinUserRowId` INTEGER NOT NULL, `itemId` TEXT NOT NULL, `type` TEXT NOT NULL, `rotation` INTEGER NOT NULL, `brightness` INTEGER NOT NULL, `contrast` INTEGER NOT NULL, `saturation` INTEGER NOT NULL, `hue` INTEGER NOT NULL, `red` INTEGER NOT NULL, `green` INTEGER NOT NULL, `blue` INTEGER NOT NULL, `blur` INTEGER NOT NULL, PRIMARY KEY(`jellyfinUserRowId`, `itemId`, `type`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "jellyfinUserRowId",
|
||||
"columnName": "jellyfinUserRowId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "itemId",
|
||||
"columnName": "itemId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.rotation",
|
||||
"columnName": "rotation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.brightness",
|
||||
"columnName": "brightness",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.contrast",
|
||||
"columnName": "contrast",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.saturation",
|
||||
"columnName": "saturation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.hue",
|
||||
"columnName": "hue",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.red",
|
||||
"columnName": "red",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.green",
|
||||
"columnName": "green",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.blue",
|
||||
"columnName": "blue",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoFilter.blur",
|
||||
"columnName": "blur",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"jellyfinUserRowId",
|
||||
"itemId",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "PlaybackLanguageChoice",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` INTEGER NOT NULL, `seriesId` TEXT NOT NULL, `itemId` TEXT, `audioLanguage` TEXT, `subtitleLanguage` TEXT, `subtitlesDisabled` INTEGER, PRIMARY KEY(`userId`, `seriesId`), FOREIGN KEY(`userId`) REFERENCES `users`(`rowId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "seriesId",
|
||||
"columnName": "seriesId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "itemId",
|
||||
"columnName": "itemId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "audioLanguage",
|
||||
"columnName": "audioLanguage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtitleLanguage",
|
||||
"columnName": "subtitleLanguage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtitlesDisabled",
|
||||
"columnName": "subtitlesDisabled",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"userId",
|
||||
"seriesId"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "users",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"userId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"rowId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ItemTrackModification",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` INTEGER NOT NULL, `itemId` TEXT NOT NULL, `trackIndex` INTEGER NOT NULL, `delayMs` INTEGER NOT NULL, PRIMARY KEY(`userId`, `itemId`, `trackIndex`), 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": "trackIndex",
|
||||
"columnName": "trackIndex",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "delayMs",
|
||||
"columnName": "delayMs",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"userId",
|
||||
"itemId",
|
||||
"trackIndex"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "users",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"userId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"rowId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "seerr_servers",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `name` TEXT, `version` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_seerr_servers_url",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"url"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_seerr_servers_url` ON `${TABLE_NAME}` (`url`)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "seerr_users",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`jellyfinUserRowId` INTEGER NOT NULL, `serverId` INTEGER NOT NULL, `authMethod` TEXT NOT NULL, `username` TEXT, `password` TEXT, `credential` TEXT, PRIMARY KEY(`jellyfinUserRowId`, `serverId`), FOREIGN KEY(`serverId`) REFERENCES `seerr_servers`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`jellyfinUserRowId`) REFERENCES `users`(`rowId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "jellyfinUserRowId",
|
||||
"columnName": "jellyfinUserRowId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverId",
|
||||
"columnName": "serverId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authMethod",
|
||||
"columnName": "authMethod",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "credential",
|
||||
"columnName": "credential",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"jellyfinUserRowId",
|
||||
"serverId"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "seerr_servers",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"serverId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "users",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"jellyfinUserRowId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"rowId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "RememberedTab",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`userId` INTEGER NOT NULL, `itemId` TEXT NOT NULL, `index` INTEGER 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": "index",
|
||||
"columnName": "index",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"userId",
|
||||
"itemId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_RememberedTab_userId_itemId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"userId",
|
||||
"itemId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_RememberedTab_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, '278e485064fc9f184a2181599f0f8efb')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
|||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||
import com.github.damontecres.wholphin.data.model.PlaybackEffect
|
||||
import com.github.damontecres.wholphin.data.model.PlaybackLanguageChoice
|
||||
import com.github.damontecres.wholphin.data.model.RememberedTab
|
||||
import com.github.damontecres.wholphin.data.model.SeerrServer
|
||||
import com.github.damontecres.wholphin.data.model.SeerrUser
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptions
|
||||
|
|
@ -46,8 +47,9 @@ import java.util.UUID
|
|||
ItemTrackModification::class,
|
||||
SeerrServer::class,
|
||||
SeerrUser::class,
|
||||
RememberedTab::class,
|
||||
],
|
||||
version = 33,
|
||||
version = 34,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(3, 4),
|
||||
|
|
@ -64,6 +66,7 @@ import java.util.UUID
|
|||
AutoMigration(30, 31),
|
||||
AutoMigration(31, 32),
|
||||
AutoMigration(32, 33),
|
||||
AutoMigration(33, 34),
|
||||
],
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
@ -81,6 +84,8 @@ abstract class AppDatabase : RoomDatabase() {
|
|||
abstract fun seerrServerDao(): SeerrServerDao
|
||||
|
||||
abstract fun playbackEffectDao(): PlaybackEffectDao
|
||||
|
||||
abstract fun rememberedTabDao(): RememberedTabDao
|
||||
}
|
||||
|
||||
class Converters {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
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.RememberedTab
|
||||
|
||||
@Dao
|
||||
interface RememberedTabDao {
|
||||
@Query("SELECT * from RememberedTab WHERE userId=:userId AND itemId=:itemId")
|
||||
suspend fun getRememberedTab(
|
||||
userId: Int,
|
||||
itemId: String,
|
||||
): RememberedTab?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(item: RememberedTab): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun saveAll(items: List<RememberedTab>)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.Index
|
||||
|
||||
@Entity(
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = JellyfinUser::class,
|
||||
parentColumns = arrayOf("rowId"),
|
||||
childColumns = arrayOf("userId"),
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
onUpdate = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
indices = [Index("userId", "itemId", unique = true)],
|
||||
primaryKeys = ["userId", "itemId"],
|
||||
)
|
||||
data class RememberedTab(
|
||||
val userId: Int,
|
||||
val itemId: String,
|
||||
val index: Int,
|
||||
)
|
||||
|
|
@ -6,7 +6,11 @@ import android.os.Build
|
|||
import androidx.core.content.edit
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.damontecres.wholphin.data.JellyfinServerDao
|
||||
import com.github.damontecres.wholphin.data.RememberedTabDao
|
||||
import com.github.damontecres.wholphin.data.SeerrServerDao
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.data.model.RememberedTab
|
||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.ScreensaverPreference
|
||||
|
|
@ -28,9 +32,12 @@ import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
|||
import com.github.damontecres.wholphin.ui.setup.seerr.migrateSeerrUrl
|
||||
import com.github.damontecres.wholphin.util.Version
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -44,6 +51,8 @@ class AppUpgradeHandler
|
|||
@param:ApplicationContext private val context: Context,
|
||||
private val appPreferences: DataStore<AppPreferences>,
|
||||
private val seerrServerDao: SeerrServerDao,
|
||||
private val serverDao: JellyfinServerDao,
|
||||
private val rememberedTabDao: RememberedTabDao,
|
||||
) {
|
||||
val pkgInfo: PackageInfo get() = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
val currentVersion: Version get() = Version.fromString(pkgInfo.versionName!!)
|
||||
|
|
@ -350,5 +359,55 @@ class AppUpgradeHandler
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (previous.isEqualOrBefore(Version.fromString("1.0.1-2-g0"))) {
|
||||
Timber.i("Starting tab migration")
|
||||
val users = mutableMapOf<UUID, JellyfinUser?>()
|
||||
val tabs =
|
||||
appPreferences.data.first().interfacePreferences.rememberedTabsMap.mapNotNull { (key, tabIndex) ->
|
||||
// "serverId_userId_itemId", itemId can have underscores
|
||||
try {
|
||||
val firstUnderScore = key.indexOf("_").takeIf { it >= 1 }
|
||||
if (firstUnderScore != null) {
|
||||
val secondUnderscore =
|
||||
key
|
||||
.indexOf("_", startIndex = firstUnderScore + 1)
|
||||
.takeIf { it >= 1 }
|
||||
if (secondUnderscore != null) {
|
||||
val serverId = key.substring(0, firstUnderScore).toUUIDOrNull()
|
||||
val userId =
|
||||
key
|
||||
.substring(firstUnderScore + 1, secondUnderscore)
|
||||
.toUUIDOrNull()
|
||||
val itemId = key.substring(secondUnderscore + 1, key.length)
|
||||
if (userId != null && serverId != null) {
|
||||
return@mapNotNull users
|
||||
.getOrPut(userId) {
|
||||
serverDao.getUser(serverId, userId)
|
||||
}?.let {
|
||||
RememberedTab(it.rowId, itemId, tabIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error parsing '%s'", key)
|
||||
}
|
||||
null
|
||||
}
|
||||
Timber.v("Migrating %s tabs", tabs.size)
|
||||
try {
|
||||
if (tabs.isNotEmpty()) {
|
||||
rememberedTabDao.saveAll(tabs)
|
||||
}
|
||||
appPreferences.updateData {
|
||||
it.updateInterfacePreferences {
|
||||
clearRememberedTabs()
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error saving migrated tabs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import com.github.damontecres.wholphin.data.RememberedTabDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.RememberedTab
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RememberedTabService
|
||||
@Inject
|
||||
constructor(
|
||||
private val serverRepository: ServerRepository,
|
||||
private val rememberedTabDao: RememberedTabDao,
|
||||
) {
|
||||
suspend fun getRememberedTab(itemId: String): Int? =
|
||||
withContext(Dispatchers.IO) {
|
||||
serverRepository.currentUser?.rowId?.let { userId ->
|
||||
rememberedTabDao.getRememberedTab(userId, itemId)?.index
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveRememberedTab(
|
||||
itemId: String,
|
||||
tabIndex: Int,
|
||||
): Unit =
|
||||
withContext(Dispatchers.IO) {
|
||||
serverRepository.currentUser?.rowId?.let { userId ->
|
||||
rememberedTabDao.save(RememberedTab(userId, itemId, tabIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,12 @@
|
|||
package com.github.damontecres.wholphin.services.hilt
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.work.WorkManager
|
||||
import com.github.damontecres.wholphin.BuildConfig
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||
import com.github.damontecres.wholphin.services.SeerrApi
|
||||
import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -22,7 +16,6 @@ import kotlinx.coroutines.CoroutineDispatcher
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.Jellyfin
|
||||
import org.jellyfin.sdk.android.androidDevice
|
||||
|
|
@ -160,51 +153,6 @@ object AppModule {
|
|||
@Singleton
|
||||
fun apiClient(jellyfin: Jellyfin) = jellyfin.createApi()
|
||||
|
||||
/**
|
||||
* Implementation of [RememberTabManager] which remembers by server, user, & item
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
fun rememberTabManager(
|
||||
serverRepository: ServerRepository,
|
||||
appPreference: DataStore<AppPreferences>,
|
||||
@IoCoroutineScope scope: CoroutineScope,
|
||||
) = object : RememberTabManager {
|
||||
fun key(itemId: String): String =
|
||||
serverRepository.current.value.let {
|
||||
"${it?.server?.id}_${it?.user?.id}_$itemId"
|
||||
}
|
||||
|
||||
override fun getRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: String,
|
||||
defaultTab: Int,
|
||||
): Int {
|
||||
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
|
||||
return preferences.appPreferences.interfacePreferences
|
||||
.getRememberedTabsOrDefault(key(itemId), defaultTab)
|
||||
} else {
|
||||
return defaultTab
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: String,
|
||||
tabIndex: Int,
|
||||
) {
|
||||
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
appPreference.updateData {
|
||||
it.updateInterfacePreferences {
|
||||
putRememberedTabs(key(itemId), tabIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@IoDispatcher
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
|||
import com.github.damontecres.wholphin.data.Migrations
|
||||
import com.github.damontecres.wholphin.data.PlaybackEffectDao
|
||||
import com.github.damontecres.wholphin.data.PlaybackLanguageChoiceDao
|
||||
import com.github.damontecres.wholphin.data.RememberedTabDao
|
||||
import com.github.damontecres.wholphin.data.SeerrServerDao
|
||||
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
|
|
@ -74,6 +75,10 @@ object DatabaseModule {
|
|||
@Singleton
|
||||
fun playbackEffectDao(db: AppDatabase): PlaybackEffectDao = db.playbackEffectDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun rememberedTabDao(db: AppDatabase): RememberedTabDao = db.rememberedTabDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun userPreferencesDataStore(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.focus.FocusRequester
|
||||
|
|
@ -15,6 +18,9 @@ import androidx.compose.ui.focus.focusRequester
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Composable
|
||||
fun CircularProgress(modifier: Modifier = Modifier) {
|
||||
|
|
@ -58,3 +64,41 @@ fun LoadingPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the space and take focus, showing a loading indicator after the specified delay
|
||||
*/
|
||||
@Composable
|
||||
fun DelayedLoadingPage(
|
||||
modifier: Modifier = Modifier,
|
||||
focusEnabled: Boolean = true,
|
||||
delay: Duration = 300.milliseconds,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
if (focusEnabled) {
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
}
|
||||
var show by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
delay(delay)
|
||||
show = true
|
||||
}
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester)
|
||||
.focusable(focusEnabled),
|
||||
) {
|
||||
if (show) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.border,
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(48.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import androidx.compose.ui.focus.FocusDirection
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
|
|
@ -42,26 +41,26 @@ import androidx.tv.material3.Text
|
|||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.ui.util.StringStringProvider
|
||||
import timber.log.Timber
|
||||
|
||||
@Composable
|
||||
fun TabRow(
|
||||
selectedTabIndex: Int,
|
||||
tabs: List<String>,
|
||||
focusRequesters: List<FocusRequester>,
|
||||
tabs: List<TabDetails>,
|
||||
onClick: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val state = rememberLazyListState()
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
if (selectedTabIndex >= 0) {
|
||||
if (selectedTabIndex in tabs.indices) {
|
||||
state.animateScrollToItem(selectedTabIndex, -(state.layoutInfo.viewportSize.width / 3.5).toInt())
|
||||
}
|
||||
}
|
||||
var rowHasFocus by remember { mutableStateOf(false) }
|
||||
|
||||
val currentSelectedTabIndex by rememberUpdatedState(selectedTabIndex)
|
||||
val currentFocusRequesters by rememberUpdatedState(focusRequesters)
|
||||
// val currentFocusRequesters by rememberUpdatedState(focusRequesters)
|
||||
val currentOnClick by rememberUpdatedState(onClick)
|
||||
|
||||
LazyRow(
|
||||
|
|
@ -76,21 +75,21 @@ fun TabRow(
|
|||
// If entering from left or right, use last or first tab
|
||||
// Otherwise use the selected tab
|
||||
val index = currentSelectedTabIndex
|
||||
val requesters = currentFocusRequesters
|
||||
// val requesters = currentFocusRequesters
|
||||
Timber.v("onEnter requestedFocusDirection=$requestedFocusDirection, selectedTabIndex=$index")
|
||||
val focusRequester =
|
||||
val tab =
|
||||
if (requestedFocusDirection == FocusDirection.Left) {
|
||||
requesters.lastOrNull()
|
||||
tabs.lastOrNull()
|
||||
} else if (requestedFocusDirection == FocusDirection.Right) {
|
||||
requesters.firstOrNull()
|
||||
tabs.firstOrNull()
|
||||
} else {
|
||||
requesters.getOrNull(index)
|
||||
tabs.getOrNull(index)
|
||||
}
|
||||
(focusRequester ?: FocusRequester.Default).tryRequestFocus()
|
||||
(tab?.tabFocusRequester ?: FocusRequester.Default).tryRequestFocus()
|
||||
}
|
||||
},
|
||||
) {
|
||||
itemsIndexed(tabs) { index, tabTitle ->
|
||||
itemsIndexed(tabs) { index, tab ->
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val onTabClick =
|
||||
remember(index) {
|
||||
|
|
@ -99,12 +98,12 @@ fun TabRow(
|
|||
}
|
||||
}
|
||||
Tab(
|
||||
title = tabTitle,
|
||||
title = tab.title.getString(),
|
||||
selected = index == selectedTabIndex,
|
||||
rowActive = rowHasFocus,
|
||||
interactionSource = interactionSource,
|
||||
onClick = onTabClick,
|
||||
modifier = Modifier.focusRequester(focusRequesters.getOrElse(index) { FocusRequester() }),
|
||||
modifier = Modifier.focusRequester(tab.tabFocusRequester),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -195,9 +194,11 @@ private fun TabRowPreview() {
|
|||
WholphinTheme {
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||
TabRow(
|
||||
selectedTabIndex = 1,
|
||||
tabs = listOf("Tab 1", "Tab 2", "Tab 3"),
|
||||
focusRequesters = listOf(),
|
||||
selectedTabIndex = -1,
|
||||
tabs =
|
||||
remember {
|
||||
listOf("Tab 1", "Tab 2", "Tab 3").map { TabDetails(StringStringProvider(it)) }
|
||||
},
|
||||
onClick = {},
|
||||
)
|
||||
Tab(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.RememberedTabService
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.util.ResStringProvider
|
||||
import com.github.damontecres.wholphin.ui.util.StringProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltViewModel(assistedFactory = TabViewModel.Factory::class)
|
||||
class TabViewModel
|
||||
@AssistedInject
|
||||
constructor(
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
private val rememberedTabService: RememberedTabService,
|
||||
private val backdropService: BackdropService,
|
||||
@param:Assisted private val itemId: String,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(itemId: String): TabViewModel
|
||||
}
|
||||
|
||||
private val _state = MutableStateFlow<Int>(UNSET)
|
||||
val state: StateFlow<Int> = _state
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
val startingTab =
|
||||
if (shouldRememberTabs()) {
|
||||
Timber.v("Getting remembered tab for %s", itemId)
|
||||
try {
|
||||
rememberedTabService.getRememberedTab(itemId)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error getting tab for %s", itemId)
|
||||
null
|
||||
} ?: 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
_state.value = startingTab
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun shouldRememberTabs(): Boolean =
|
||||
userPreferencesService
|
||||
.getCurrent()
|
||||
.appPreferences.interfacePreferences.rememberSelectedTab
|
||||
|
||||
fun updateSelectedTabIndex(tabIndex: Int) {
|
||||
_state.value = tabIndex
|
||||
viewModelScope.launchIO {
|
||||
backdropService.clearBackdrop()
|
||||
if (shouldRememberTabs()) {
|
||||
Timber.v("Saving remembered tab for %s: %s", itemId, tabIndex)
|
||||
rememberedTabService.saveRememberedTab(itemId, tabIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TabDetails(
|
||||
val title: StringProvider,
|
||||
val tabFocusRequester: FocusRequester = FocusRequester(),
|
||||
val contentFocusRequester: FocusRequester = FocusRequester(),
|
||||
) {
|
||||
constructor(
|
||||
@StringRes stringResId: Int,
|
||||
) : this(ResStringProvider(stringResId))
|
||||
}
|
||||
|
||||
const val UNSET = -1234
|
||||
|
||||
/**
|
||||
* A page showing multiple tabs
|
||||
*
|
||||
* This handles remembering the selected tabs via [TabViewModel] if needed
|
||||
*/
|
||||
@Composable
|
||||
fun TabbedPage(
|
||||
itemId: String,
|
||||
tabs: List<TabDetails>,
|
||||
modifier: Modifier = Modifier,
|
||||
showTabs: Boolean = true,
|
||||
viewModel: TabViewModel =
|
||||
hiltViewModel<TabViewModel, TabViewModel.Factory>(
|
||||
key = itemId,
|
||||
creationCallback = { it.create(itemId) },
|
||||
),
|
||||
tabContent: @Composable (Int, TabDetails) -> Unit,
|
||||
) {
|
||||
val selectedTabIndex by viewModel.state.collectAsState()
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showTabs,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(vertical = 16.dp),
|
||||
tabs = tabs,
|
||||
onClick = viewModel::updateSelectedTabIndex,
|
||||
)
|
||||
}
|
||||
selectedTabIndex.let { tabIndex ->
|
||||
if (tabIndex >= 0) {
|
||||
tabContent.invoke(tabIndex, tabs[tabIndex])
|
||||
} else {
|
||||
DelayedLoadingPage(focusEnabled = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,15 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -32,20 +22,23 @@ import com.github.damontecres.wholphin.services.BackdropService
|
|||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderView
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabbedPage
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptions
|
||||
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
|
||||
import com.github.damontecres.wholphin.ui.detail.livetv.DvrSchedule
|
||||
import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import com.github.damontecres.wholphin.ui.util.StringStringProvider
|
||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -56,24 +49,35 @@ class LiveTvCollectionViewModel
|
|||
val api: ApiClient,
|
||||
val serverRepository: ServerRepository,
|
||||
val navigationManager: NavigationManager,
|
||||
val rememberTabManager: RememberTabManager,
|
||||
val backdropService: BackdropService,
|
||||
) : ViewModel(),
|
||||
RememberTabManager by rememberTabManager {
|
||||
val recordingFolders = MutableStateFlow<List<TabId>>(emptyList())
|
||||
) : ViewModel() {
|
||||
val state = MutableStateFlow(LiveTvRecordingFolderState())
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
val folders =
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(userId = serverRepository.currentUser?.id)
|
||||
.content.items
|
||||
.map { TabId(it.name ?: "Recordings", it.id) }
|
||||
recordingFolders.value = folders
|
||||
try {
|
||||
val folders =
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(userId = serverRepository.currentUser?.id)
|
||||
.content.items
|
||||
.map { TabId(it.name ?: "Recordings", it.id) }
|
||||
state.value =
|
||||
LiveTvRecordingFolderState(DataLoadingState.Success(folders))
|
||||
} catch (ex: CancellationException) {
|
||||
throw ex
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error fetching recording folders")
|
||||
state.value =
|
||||
LiveTvRecordingFolderState(DataLoadingState.Error(ex))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class LiveTvRecordingFolderState(
|
||||
val folders: DataLoadingState<List<TabId>> = DataLoadingState.Pending,
|
||||
)
|
||||
|
||||
data class TabId(
|
||||
val title: String,
|
||||
val id: UUID,
|
||||
|
|
@ -86,103 +90,91 @@ fun CollectionFolderLiveTv(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: LiveTvCollectionViewModel = hiltViewModel(),
|
||||
) {
|
||||
val rememberedTabIndex =
|
||||
remember { viewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
val folders by viewModel.recordingFolders.collectAsState()
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
val tvGuideStr = stringResource(R.string.tv_guide)
|
||||
val tvDvrStr = stringResource(R.string.tv_dvr_schedule)
|
||||
val tabs =
|
||||
remember(folders) {
|
||||
listOf(
|
||||
TabId(tvGuideStr, UUID.randomUUID()),
|
||||
TabId(tvDvrStr, UUID.randomUUID()),
|
||||
) + folders
|
||||
when (val st = state.folders) {
|
||||
is DataLoadingState.Error -> {
|
||||
ErrorMessage(st, modifier)
|
||||
}
|
||||
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val tabFocusRequesters = remember(tabs.size) { List(tabs.size) { FocusRequester() } }
|
||||
|
||||
val firstTabFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
|
||||
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
logTab("livetv", selectedTabIndex)
|
||||
viewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
|
||||
viewModel.backdropService.clearBackdrop()
|
||||
}
|
||||
val onClickItem = { position: Int, item: BaseItem ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showHeader,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.focusRequester(firstTabFocusRequester),
|
||||
tabs = tabs.map { it.title },
|
||||
onClick = { selectedTabIndex = it },
|
||||
focusRequesters = tabFocusRequesters,
|
||||
)
|
||||
DataLoadingState.Loading,
|
||||
DataLoadingState.Pending,
|
||||
-> {
|
||||
LoadingPage(modifier)
|
||||
}
|
||||
when (selectedTabIndex) {
|
||||
0 -> {
|
||||
TvGuideGrid(
|
||||
preferences = preferences,
|
||||
onRowPosition = {
|
||||
showHeader = it <= 0
|
||||
},
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
DvrSchedule(
|
||||
requestFocusAfterLoading = true,
|
||||
focusRequesterOnEmpty = tabFocusRequesters[1],
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
is DataLoadingState.Success<List<TabId>> -> {
|
||||
val tabs =
|
||||
remember {
|
||||
listOf(
|
||||
TabDetails(R.string.tv_guide),
|
||||
TabDetails(R.string.tv_dvr_schedule),
|
||||
) +
|
||||
st.data.map {
|
||||
TabDetails(StringStringProvider(it.title))
|
||||
}
|
||||
}
|
||||
val onClickItem =
|
||||
remember {
|
||||
{ position: Int, item: BaseItem ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val folderIndex = selectedTabIndex - 2
|
||||
if (folderIndex in folders.indices) {
|
||||
CollectionFolderView(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
itemId = folders[folderIndex].id,
|
||||
initialFilter = CollectionFolderFilter(),
|
||||
showTitle = false,
|
||||
recursive = false,
|
||||
sortOptions = VideoSortOptions,
|
||||
modifier = Modifier,
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
defaultViewOptions = ViewOptions(),
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
)
|
||||
} else {
|
||||
ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
TabbedPage(
|
||||
itemId = destination.itemId.toString(),
|
||||
tabs = tabs,
|
||||
modifier = modifier,
|
||||
showTabs = showHeader,
|
||||
) { tabIndex, tabDetails ->
|
||||
when (tabIndex) {
|
||||
0 -> {
|
||||
TvGuideGrid(
|
||||
preferences = preferences,
|
||||
onRowPosition = {
|
||||
showHeader = it <= 0
|
||||
},
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
DvrSchedule(
|
||||
requestFocusAfterLoading = true,
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val folderIndex = tabIndex - 2
|
||||
if (folderIndex in st.data.indices) {
|
||||
CollectionFolderView(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
itemId = st.data[folderIndex].id,
|
||||
initialFilter = CollectionFolderFilter(),
|
||||
showTitle = false,
|
||||
recursive = false,
|
||||
sortOptions = VideoSortOptions,
|
||||
modifier = Modifier.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
defaultViewOptions = ViewOptions(),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
} else {
|
||||
ErrorMessage("Invalid tab index $tabIndex", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,14 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
||||
|
|
@ -28,11 +18,11 @@ import com.github.damontecres.wholphin.ui.components.CollectionFolderView
|
|||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.GenreCardGrid
|
||||
import com.github.damontecres.wholphin.ui.components.RecommendedMovie
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabbedPage
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptionsPoster
|
||||
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
|
|
@ -45,51 +35,25 @@ fun CollectionFolderMovie(
|
|||
modifier: Modifier = Modifier,
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
) {
|
||||
val rememberedTabIndex =
|
||||
remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.recommended),
|
||||
stringResource(R.string.library),
|
||||
stringResource(R.string.collections),
|
||||
stringResource(R.string.genres),
|
||||
)
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
|
||||
|
||||
val firstTabFocusRequester = remember { FocusRequester() }
|
||||
// LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
|
||||
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
logTab("movie", selectedTabIndex)
|
||||
preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
|
||||
preferencesViewModel.backdropService.clearBackdrop()
|
||||
}
|
||||
remember {
|
||||
listOf(
|
||||
TabDetails(R.string.recommended),
|
||||
TabDetails(R.string.library),
|
||||
TabDetails(R.string.collections),
|
||||
TabDetails(R.string.genres),
|
||||
)
|
||||
}
|
||||
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
Column(
|
||||
TabbedPage(
|
||||
itemId = destination.itemId.toString(),
|
||||
tabs = tabs,
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showHeader,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.focusRequester(firstTabFocusRequester),
|
||||
tabs = tabs,
|
||||
onClick = { selectedTabIndex = it },
|
||||
focusRequesters = tabFocusRequesters,
|
||||
)
|
||||
}
|
||||
when (selectedTabIndex) {
|
||||
showTabs = showHeader,
|
||||
) { tabIndex, tabDetails ->
|
||||
when (tabIndex) {
|
||||
// Recommended
|
||||
0 -> {
|
||||
RecommendedMovie(
|
||||
|
|
@ -101,7 +65,7 @@ fun CollectionFolderMovie(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -128,12 +92,12 @@ fun CollectionFolderMovie(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = true,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -160,12 +124,12 @@ fun CollectionFolderMovie(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -178,12 +142,12 @@ fun CollectionFolderMovie(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
ErrorMessage("Invalid tab index $tabIndex", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,14 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -36,17 +26,16 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
|||
import com.github.damontecres.wholphin.ui.components.GenreCardGrid
|
||||
import com.github.damontecres.wholphin.ui.components.GridClickActions
|
||||
import com.github.damontecres.wholphin.ui.components.RecommendedMusic
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabbedPage
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptionsSquare
|
||||
import com.github.damontecres.wholphin.ui.data.AlbumSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.ArtistSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.SongSortOptions
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -66,10 +55,8 @@ class CollectionFolderMusicViewModel
|
|||
private val musicService: MusicService,
|
||||
private val navigationManager: NavigationManager,
|
||||
val backdropService: BackdropService,
|
||||
private val rememberTabManager: RememberTabManager,
|
||||
@Assisted private val itemId: UUID,
|
||||
) : ViewModel(),
|
||||
RememberTabManager by rememberTabManager {
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(itemId: UUID): CollectionFolderMusicViewModel
|
||||
|
|
@ -132,30 +119,16 @@ fun CollectionFolderMusic(
|
|||
creationCallback = { it.create(destination.itemId) },
|
||||
),
|
||||
) {
|
||||
val rememberedTabIndex =
|
||||
remember { viewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.recommended),
|
||||
stringResource(R.string.albums),
|
||||
stringResource(R.string.artists),
|
||||
stringResource(R.string.genres),
|
||||
stringResource(R.string.songs),
|
||||
)
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
|
||||
|
||||
val firstTabFocusRequester = remember { FocusRequester() }
|
||||
// LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
|
||||
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
logTab("music", selectedTabIndex)
|
||||
viewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
|
||||
viewModel.backdropService.clearBackdrop()
|
||||
}
|
||||
|
||||
remember {
|
||||
listOf(
|
||||
TabDetails(R.string.recommended),
|
||||
TabDetails(R.string.albums),
|
||||
TabDetails(R.string.artists),
|
||||
TabDetails(R.string.genres),
|
||||
TabDetails(R.string.songs),
|
||||
)
|
||||
}
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val actions =
|
||||
|
|
@ -167,27 +140,13 @@ fun CollectionFolderMusic(
|
|||
onClickPlayRemoteButton = viewModel::onClickPlayRemoteButton,
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
TabbedPage(
|
||||
itemId = destination.itemId.toString(),
|
||||
tabs = tabs,
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showHeader,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.focusRequester(firstTabFocusRequester),
|
||||
tabs = tabs,
|
||||
onClick = { selectedTabIndex = it },
|
||||
focusRequesters = tabFocusRequesters,
|
||||
)
|
||||
}
|
||||
when (selectedTabIndex) {
|
||||
showTabs = showHeader,
|
||||
) { tabIndex, tabDetails ->
|
||||
when (tabIndex) {
|
||||
// Recommended
|
||||
0 -> {
|
||||
RecommendedMusic(
|
||||
|
|
@ -199,7 +158,7 @@ fun CollectionFolderMusic(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -224,12 +183,12 @@ fun CollectionFolderMusic(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = true,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -254,12 +213,12 @@ fun CollectionFolderMusic(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +231,7 @@ fun CollectionFolderMusic(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -297,17 +256,17 @@ fun CollectionFolderMusic(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = true,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
ErrorMessage("Invalid tab index $tabIndex", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,102 +1,77 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.filter.DefaultTvFilterOptions
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderView
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.GenreCardGrid
|
||||
import com.github.damontecres.wholphin.ui.components.RecommendedTvShow
|
||||
import com.github.damontecres.wholphin.ui.components.StudioCardGrid
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabbedPage
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptionsPoster
|
||||
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class CollectionFolderTvViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
val navigationManager: NavigationManager,
|
||||
) : ViewModel()
|
||||
|
||||
@Composable
|
||||
fun CollectionFolderTv(
|
||||
preferences: UserPreferences,
|
||||
destination: Destination.MediaItem,
|
||||
modifier: Modifier = Modifier,
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
viewModel: CollectionFolderTvViewModel = hiltViewModel(),
|
||||
) {
|
||||
val rememberedTabIndex =
|
||||
remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.recommended),
|
||||
stringResource(R.string.library),
|
||||
stringResource(R.string.genres),
|
||||
stringResource(R.string.studios),
|
||||
)
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
|
||||
|
||||
val firstTabFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
|
||||
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
logTab("tv", selectedTabIndex)
|
||||
preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
|
||||
preferencesViewModel.backdropService.clearBackdrop()
|
||||
}
|
||||
|
||||
val onClickItem = { item: BaseItem ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
remember {
|
||||
listOf(
|
||||
TabDetails(R.string.recommended),
|
||||
TabDetails(R.string.library),
|
||||
TabDetails(R.string.genres),
|
||||
TabDetails(R.string.studios),
|
||||
)
|
||||
}
|
||||
val onClickItem =
|
||||
remember {
|
||||
{ position: Int, item: BaseItem ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
}
|
||||
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
Column(
|
||||
TabbedPage(
|
||||
itemId = destination.itemId.toString(),
|
||||
tabs = tabs,
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showHeader,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(vertical = 16.dp)
|
||||
.focusRequester(firstTabFocusRequester),
|
||||
tabs = tabs,
|
||||
onClick = { selectedTabIndex = it },
|
||||
focusRequesters = tabFocusRequesters,
|
||||
)
|
||||
}
|
||||
when (selectedTabIndex) {
|
||||
showTabs = showHeader,
|
||||
) { tabIndex, tabDetails ->
|
||||
when (tabIndex) {
|
||||
// Recommended
|
||||
0 -> {
|
||||
RecommendedTvShow(
|
||||
|
|
@ -108,7 +83,7 @@ fun CollectionFolderTv(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -132,15 +107,13 @@ fun CollectionFolderTv(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
onClickItem = { _, item ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
onClickItem = onClickItem,
|
||||
playEnabled = false,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +126,7 @@ fun CollectionFolderTv(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -165,12 +138,12 @@ fun CollectionFolderTv(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
ErrorMessage("Invalid tab index $tabIndex", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,30 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.filter.DefaultForFavoritesFilterOptions
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderView
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.GridClickActions
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabbedPage
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptionsPoster
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptionsSquare
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptionsWide
|
||||
|
|
@ -39,86 +33,55 @@ import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
|||
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FavoritesViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
val navigationManager: NavigationManager,
|
||||
) : ViewModel()
|
||||
|
||||
@Composable
|
||||
fun FavoritesPage(
|
||||
preferences: UserPreferences,
|
||||
modifier: Modifier = Modifier,
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
viewModel: FavoritesViewModel = hiltViewModel(),
|
||||
) {
|
||||
val uiPrefs = preferences.appPreferences.interfacePreferences
|
||||
val rememberedTabIndex =
|
||||
val tabs =
|
||||
remember {
|
||||
preferencesViewModel.getRememberedTab(
|
||||
preferences,
|
||||
NavDrawerItem.Favorites.id,
|
||||
0,
|
||||
listOf(
|
||||
TabDetails(R.string.movies),
|
||||
TabDetails(R.string.tv_shows),
|
||||
TabDetails(R.string.episodes),
|
||||
TabDetails(R.string.videos),
|
||||
TabDetails(R.string.playlists),
|
||||
TabDetails(R.string.people),
|
||||
)
|
||||
}
|
||||
val actions =
|
||||
remember {
|
||||
GridClickActions(
|
||||
onClickItem = { _, item -> viewModel.navigationManager.navigateTo(item.destination()) },
|
||||
)
|
||||
}
|
||||
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.movies),
|
||||
stringResource(R.string.tv_shows),
|
||||
stringResource(R.string.episodes),
|
||||
stringResource(R.string.videos),
|
||||
stringResource(R.string.playlists),
|
||||
stringResource(R.string.people),
|
||||
)
|
||||
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
logTab("favorites", selectedTabIndex)
|
||||
preferencesViewModel.saveRememberedTab(
|
||||
preferences,
|
||||
NavDrawerItem.Favorites.id,
|
||||
selectedTabIndex,
|
||||
)
|
||||
preferencesViewModel.backdropService.clearBackdrop()
|
||||
}
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val onClickItem = { item: BaseItem ->
|
||||
preferencesViewModel.navigationManager.navigateTo(item.destination())
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
Column(
|
||||
// LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
TabbedPage(
|
||||
itemId = NavDrawerItem.Favorites.id,
|
||||
tabs = tabs,
|
||||
showTabs = showHeader,
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showHeader,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp),
|
||||
tabs = tabs,
|
||||
onClick = { selectedTabIndex = it },
|
||||
focusRequesters = tabFocusRequesters,
|
||||
)
|
||||
}
|
||||
val actions =
|
||||
remember {
|
||||
GridClickActions(
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
)
|
||||
}
|
||||
|
||||
) { tabIndex, tabDetails ->
|
||||
// TODO playEnabled = true for movies & episodes
|
||||
when (selectedTabIndex) {
|
||||
when (tabIndex) {
|
||||
// Movies
|
||||
0 -> {
|
||||
CollectionFolderView(
|
||||
|
|
@ -141,13 +104,13 @@ fun FavoritesPage(
|
|||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
filterOptions = DefaultForFavoritesFilterOptions,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -173,13 +136,13 @@ fun FavoritesPage(
|
|||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
filterOptions = DefaultForFavoritesFilterOptions,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -206,13 +169,13 @@ fun FavoritesPage(
|
|||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
filterOptions = DefaultForFavoritesFilterOptions,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -238,13 +201,13 @@ fun FavoritesPage(
|
|||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
filterOptions = DefaultForFavoritesFilterOptions,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -270,13 +233,13 @@ fun FavoritesPage(
|
|||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
filterOptions = DefaultForFavoritesFilterOptions,
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -307,18 +270,18 @@ fun FavoritesPage(
|
|||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
playEnabled = false,
|
||||
filterOptions = listOf(),
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
ErrorMessage("Invalid tab index $tabIndex", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import com.github.damontecres.wholphin.ui.cards.PersonRow
|
|||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.HeaderUtils
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.TitleOrLogo
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
|
|
@ -64,6 +65,7 @@ import com.github.damontecres.wholphin.ui.logTab
|
|||
import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.ui.util.StringStringProvider
|
||||
import com.github.damontecres.wholphin.ui.util.rememberDelayedNestedScroll
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
|
|
@ -120,13 +122,21 @@ fun SeriesOverviewContent(
|
|||
var requestFocusAfterSeason by remember { mutableStateOf(false) }
|
||||
|
||||
val seasonStr = stringResource(R.string.tv_season)
|
||||
val tabFocusRequesters = remember(seasons) { List(seasons.size) { FocusRequester() } }
|
||||
val contentFocusRequesters = remember(seasons) { List(seasons.size) { FocusRequester() } }
|
||||
val tabs =
|
||||
seasons.map { season ->
|
||||
season?.name
|
||||
?: season?.data?.indexNumber?.let { "$seasonStr $it" }
|
||||
?: ""
|
||||
seasons.mapIndexed { index, season ->
|
||||
TabDetails(
|
||||
title =
|
||||
StringStringProvider(
|
||||
season?.name
|
||||
?: season?.data?.indexNumber?.let { "$seasonStr $it" }
|
||||
?: "",
|
||||
),
|
||||
tabFocusRequester = tabFocusRequesters[index],
|
||||
contentFocusRequester = contentFocusRequesters[index],
|
||||
)
|
||||
}
|
||||
val focusRequesters = remember(seasons) { List(seasons.size) { FocusRequester() } }
|
||||
|
||||
val currentOnChangeSeason by rememberUpdatedState(onChangeSeason)
|
||||
|
||||
|
|
@ -172,7 +182,6 @@ fun SeriesOverviewContent(
|
|||
requestFocusAfterSeason = true
|
||||
}
|
||||
},
|
||||
focusRequesters = focusRequesters,
|
||||
modifier =
|
||||
Modifier
|
||||
.focusRequester(tabRowFocusRequester)
|
||||
|
|
|
|||
|
|
@ -1,111 +1,78 @@
|
|||
package com.github.damontecres.wholphin.ui.discover
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabbedPage
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
|
||||
@Composable
|
||||
fun DiscoverPage(
|
||||
preferences: UserPreferences,
|
||||
modifier: Modifier = Modifier,
|
||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
) {
|
||||
val rememberedTabIndex =
|
||||
remember { preferencesViewModel.getRememberedTab(preferences, NavDrawerItem.Discover.id, 0) }
|
||||
|
||||
val tabs =
|
||||
listOf(
|
||||
stringResource(R.string.discover),
|
||||
stringResource(R.string.request),
|
||||
stringResource(R.string.search),
|
||||
)
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val tabFocusRequesters = remember(tabs) { List(tabs.size) { FocusRequester() } }
|
||||
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
logTab("discover", selectedTabIndex)
|
||||
preferencesViewModel.saveRememberedTab(preferences, NavDrawerItem.Discover.id, selectedTabIndex)
|
||||
}
|
||||
OneTimeLaunchedEffect { preferencesViewModel.backdropService.clearBackdrop() }
|
||||
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
showHeader,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp),
|
||||
tabs = tabs,
|
||||
onClick = { selectedTabIndex = it },
|
||||
focusRequesters = tabFocusRequesters,
|
||||
remember {
|
||||
listOf(
|
||||
TabDetails(R.string.discover),
|
||||
TabDetails(R.string.request),
|
||||
TabDetails(R.string.search),
|
||||
)
|
||||
}
|
||||
when (selectedTabIndex) {
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
TabbedPage(
|
||||
itemId = NavDrawerItem.Discover.id,
|
||||
tabs = tabs,
|
||||
modifier = modifier,
|
||||
showTabs = showHeader,
|
||||
) { tabIndex, tabDetails ->
|
||||
when (tabIndex) {
|
||||
// Discover
|
||||
0 -> {
|
||||
SeerrDiscoverPage(
|
||||
preferences = preferences,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize(),
|
||||
.fillMaxSize()
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
// Requests
|
||||
1 -> {
|
||||
SeerrRequestsPage(
|
||||
focusRequesterOnEmpty = tabFocusRequesters.getOrNull(selectedTabIndex),
|
||||
focusRequesterOnEmpty = tabDetails.tabFocusRequester,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize(),
|
||||
.fillMaxSize()
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
// Search
|
||||
2 -> {
|
||||
LaunchedEffect(Unit) {
|
||||
preferencesViewModel.backdropService.clearBackdrop()
|
||||
}
|
||||
DiscoverSearchPage(
|
||||
preferences = preferences,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.focusRequester(tabDetails.contentFocusRequester),
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
ErrorMessage("Invalid tab index $tabIndex", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ import com.github.damontecres.wholphin.ui.cards.ItemRowTitle
|
|||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.SearchEditTextBox
|
||||
import com.github.damontecres.wholphin.ui.components.TabDetails
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.components.VoiceInputManager
|
||||
import com.github.damontecres.wholphin.ui.components.VoiceSearchButton
|
||||
|
|
@ -518,6 +519,15 @@ fun SearchPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
val tabs =
|
||||
remember {
|
||||
listOf(
|
||||
TabDetails(R.string.library),
|
||||
TabDetails(R.string.discover),
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
|
|
@ -610,12 +620,7 @@ fun SearchPage(
|
|||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTab,
|
||||
tabs =
|
||||
listOf(
|
||||
stringResource(R.string.library),
|
||||
stringResource(R.string.discover),
|
||||
),
|
||||
focusRequesters = tabFocusRequesters,
|
||||
tabs = tabs,
|
||||
onClick = {
|
||||
selectedTab = it
|
||||
val row =
|
||||
|
|
|
|||
|
|
@ -359,6 +359,7 @@ fun DestinationContent(
|
|||
}
|
||||
|
||||
Destination.Discover -> {
|
||||
LaunchedEffect(Unit) { onClearBackdrop.invoke() }
|
||||
DiscoverPage(
|
||||
preferences = preferences,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import com.github.damontecres.wholphin.ui.launchIO
|
|||
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||
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.flow.MutableStateFlow
|
||||
|
|
@ -52,14 +51,12 @@ class PreferencesViewModel
|
|||
val navigationManager: NavigationManager,
|
||||
val backdropService: BackdropService,
|
||||
val screensaverService: ScreensaverService,
|
||||
private val rememberTabManager: RememberTabManager,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
private val deviceInfo: DeviceInfo,
|
||||
private val clientInfo: ClientInfo,
|
||||
private val updateChecker: UpdateChecker,
|
||||
) : ViewModel(),
|
||||
RememberTabManager by rememberTabManager {
|
||||
) : ViewModel() {
|
||||
val currentUser
|
||||
get() =
|
||||
serverRepository.currentUserFlow.stateIn(
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Functions to remember tabs choices for a library
|
||||
*/
|
||||
interface RememberTabManager {
|
||||
/**
|
||||
* If enabled, get the remembered tab index for the given item
|
||||
*/
|
||||
fun getRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: UUID,
|
||||
defaultTab: Int,
|
||||
): Int = getRememberedTab(preferences, itemId.toString(), defaultTab)
|
||||
|
||||
/**
|
||||
* If enabled, get the remembered tab index for the given item
|
||||
*/
|
||||
fun getRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: String,
|
||||
defaultTab: Int,
|
||||
): Int
|
||||
|
||||
/**
|
||||
* If enabled, save the remembered tab index for the given item
|
||||
*/
|
||||
fun saveRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: UUID,
|
||||
tabIndex: Int,
|
||||
) = saveRememberedTab(preferences, itemId.toString(), tabIndex)
|
||||
|
||||
/**
|
||||
* If enabled, save the remembered tab index for the given item
|
||||
*/
|
||||
fun saveRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: String,
|
||||
tabIndex: Int,
|
||||
)
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ enum DisplayToggle{
|
|||
message InterfacePreferences {
|
||||
ThemeSongVolume play_theme_songs = 1;
|
||||
bool remember_selected_tab = 2;
|
||||
map<string, int32> remembered_tabs = 3;
|
||||
map<string, int32> remembered_tabs = 3 [deprecated = true];
|
||||
AppThemeColors app_theme_colors = 4;
|
||||
bool nav_drawer_switch_on_focus = 5;
|
||||
bool show_clock = 6;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
|||
import com.github.damontecres.wholphin.data.Migrations
|
||||
import com.github.damontecres.wholphin.data.PlaybackEffectDao
|
||||
import com.github.damontecres.wholphin.data.PlaybackLanguageChoiceDao
|
||||
import com.github.damontecres.wholphin.data.RememberedTabDao
|
||||
import com.github.damontecres.wholphin.data.SeerrServerDao
|
||||
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
|
|
@ -35,7 +36,6 @@ import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
|
|||
import com.github.damontecres.wholphin.services.hilt.IoDispatcher
|
||||
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
|
||||
import com.github.damontecres.wholphin.util.CoroutineContextApiClientFactory
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -166,17 +166,6 @@ object TestModule {
|
|||
return api
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of [RememberTabManager] which remembers by server, user, & item
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
fun rememberTabManager(
|
||||
serverRepository: ServerRepository,
|
||||
appPreference: DataStore<AppPreferences>,
|
||||
@IoCoroutineScope scope: CoroutineScope,
|
||||
): RememberTabManager = mockk()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@IoDispatcher
|
||||
|
|
@ -267,6 +256,10 @@ object TestDatabaseModule {
|
|||
@Singleton
|
||||
fun playbackEffectDao(db: AppDatabase): PlaybackEffectDao = db.playbackEffectDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun rememberedTabDao(db: AppDatabase): RememberedTabDao = db.rememberedTabDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun userPreferencesDataStore(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue