From d7f95cd23d89e58d97fbd79aa45d7639d24c1e00 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 8 Jan 2024 20:01:48 +0100 Subject: [PATCH] feat: show duration in downloads and improve downloads UI --- .../17.json | 533 ++++++++++++++++++ .../com/github/libretube/db/AppDatabase.kt | 5 +- .../com/github/libretube/db/obj/Download.kt | 3 + .../libretube/services/DownloadService.kt | 1 + .../libretube/ui/adapters/DownloadsAdapter.kt | 5 + .../main/res/layout/downloaded_media_row.xml | 67 ++- 6 files changed, 596 insertions(+), 18 deletions(-) create mode 100644 app/schemas/com.github.libretube.db.AppDatabase/17.json diff --git a/app/schemas/com.github.libretube.db.AppDatabase/17.json b/app/schemas/com.github.libretube.db.AppDatabase/17.json new file mode 100644 index 000000000..a95613314 --- /dev/null +++ b/app/schemas/com.github.libretube.db.AppDatabase/17.json @@ -0,0 +1,533 @@ +{ + "formatVersion": 1, + "database": { + "version": 17, + "identityHash": "30434ae9f3be749f074b6c8a786f27ca", + "entities": [ + { + "tableName": "watchHistoryItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER, `isShort` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isShort", + "columnName": "isShort", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "watchPosition", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "searchHistoryItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`query` TEXT NOT NULL, PRIMARY KEY(`query`))", + "fields": [ + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "query" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "customInstance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `apiUrl` TEXT NOT NULL, `frontendUrl` TEXT NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "apiUrl", + "columnName": "apiUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "frontendUrl", + "columnName": "frontendUrl", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "localSubscription", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`channelId` TEXT NOT NULL, PRIMARY KEY(`channelId`))", + "fields": [ + { + "fieldPath": "channelId", + "columnName": "channelId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "channelId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlistBookmark", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`playlistId` TEXT NOT NULL, `playlistName` TEXT, `thumbnailUrl` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `videos` INTEGER NOT NULL, PRIMARY KEY(`playlistId`))", + "fields": [ + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playlistName", + "columnName": "playlistName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videos", + "columnName": "videos", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "playlistId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalPlaylist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT NOT NULL, `description` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LocalPlaylistItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` INTEGER NOT NULL, `videoId` TEXT NOT NULL, `title` TEXT, `uploadDate` TEXT, `uploader` TEXT, `uploaderUrl` TEXT, `uploaderAvatar` TEXT, `thumbnailUrl` TEXT, `duration` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderUrl", + "columnName": "uploaderUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderAvatar", + "columnName": "uploaderAvatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "download", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`videoId` TEXT NOT NULL, `title` TEXT NOT NULL, `description` TEXT NOT NULL, `uploader` TEXT NOT NULL, `duration` INTEGER DEFAULT NULL, `uploadDate` TEXT, `thumbnailPath` TEXT, PRIMARY KEY(`videoId`))", + "fields": [ + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uploader", + "columnName": "uploader", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": false, + "defaultValue": "NULL" + }, + { + "fieldPath": "uploadDate", + "columnName": "uploadDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thumbnailPath", + "columnName": "thumbnailPath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "videoId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "downloadItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` TEXT NOT NULL, `videoId` TEXT NOT NULL, `fileName` TEXT NOT NULL, `path` TEXT NOT NULL, `url` TEXT, `format` TEXT, `quality` TEXT, `language` TEXT, `downloadSize` INTEGER NOT NULL, FOREIGN KEY(`videoId`) REFERENCES `download`(`videoId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fileName", + "columnName": "fileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quality", + "columnName": "quality", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "downloadSize", + "columnName": "downloadSize", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_downloadItem_path", + "unique": true, + "columnNames": [ + "path" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_downloadItem_path` ON `${TABLE_NAME}` (`path`)" + } + ], + "foreignKeys": [ + { + "table": "download", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "videoId" + ], + "referencedColumns": [ + "videoId" + ] + } + ] + }, + { + "tableName": "subscriptionGroups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `channels` TEXT NOT NULL, `index` INTEGER NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "channels", + "columnName": "channels", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index", + "columnName": "index", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "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, '30434ae9f3be749f074b6c8a786f27ca')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/libretube/db/AppDatabase.kt b/app/src/main/java/com/github/libretube/db/AppDatabase.kt index ea21b710f..d4a238e39 100644 --- a/app/src/main/java/com/github/libretube/db/AppDatabase.kt +++ b/app/src/main/java/com/github/libretube/db/AppDatabase.kt @@ -39,12 +39,13 @@ import com.github.libretube.db.obj.WatchPosition DownloadItem::class, SubscriptionGroup::class ], - version = 16, + version = 17, autoMigrations = [ AutoMigration(from = 7, to = 8), AutoMigration(from = 8, to = 9), AutoMigration(from = 9, to = 10), - AutoMigration(from = 10, to = 11) + AutoMigration(from = 10, to = 11), + AutoMigration(from = 16, to = 17) ] ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/com/github/libretube/db/obj/Download.kt b/app/src/main/java/com/github/libretube/db/obj/Download.kt index a30953dc2..447be46e4 100644 --- a/app/src/main/java/com/github/libretube/db/obj/Download.kt +++ b/app/src/main/java/com/github/libretube/db/obj/Download.kt @@ -1,5 +1,6 @@ package com.github.libretube.db.obj +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import java.nio.file.Path @@ -12,6 +13,8 @@ data class Download( val title: String = "", val description: String = "", val uploader: String = "", + @ColumnInfo(defaultValue = "NULL") + val duration: Long? = null, val uploadDate: LocalDate? = null, val thumbnailPath: Path? = null ) diff --git a/app/src/main/java/com/github/libretube/services/DownloadService.kt b/app/src/main/java/com/github/libretube/services/DownloadService.kt index 2d7bc05c2..a6a01de4e 100644 --- a/app/src/main/java/com/github/libretube/services/DownloadService.kt +++ b/app/src/main/java/com/github/libretube/services/DownloadService.kt @@ -118,6 +118,7 @@ class DownloadService : LifecycleService() { streams.title, streams.description, streams.uploader, + streams.duration, streams.uploadTimestamp.toLocalDateTime(TimeZone.currentSystemDefault()).date, thumbnailTargetPath ) diff --git a/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt index 4959ecaec..7288c626d 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt @@ -3,6 +3,7 @@ package com.github.libretube.ui.adapters import android.annotation.SuppressLint import android.content.Context import android.content.Intent +import android.text.format.DateUtils import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.os.bundleOf @@ -74,6 +75,10 @@ class DownloadsAdapter( } else { downloadOverlay.isGone = true fileSize.text = totalSizeInfo + durationContainer.isVisible = true + download.duration?.let { + thumbnailDuration.text = DateUtils.formatElapsedTime(it) + } } download.thumbnailPath?.let { path -> diff --git a/app/src/main/res/layout/downloaded_media_row.xml b/app/src/main/res/layout/downloaded_media_row.xml index d02a95075..68e3666cc 100644 --- a/app/src/main/res/layout/downloaded_media_row.xml +++ b/app/src/main/res/layout/downloaded_media_row.xml @@ -18,15 +18,42 @@ + + + + + + + @@ -75,20 +102,28 @@ android:layout_marginVertical="2dp" android:textSize="12sp" /> - + android:orientation="horizontal"> + + + + + + - - \ No newline at end of file