From 37aec538ccaf64052953fb62e1170d39a78ad03f Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:09:05 +0100 Subject: [PATCH] more stuff --- .../21.json | 20 +- .../22.json | 766 ++++++++++++++++++ .../com/deniscerri/ytdl/database/DBManager.kt | 3 +- .../deniscerri/ytdl/database/Migrations.kt | 9 + .../ytdl/database/dao/CommandTemplateDao.kt | 14 +- .../ytdl/database/models/CommandTemplate.kt | 5 +- .../models/YoutubePlayerClientItem.kt | 5 +- .../database/repository/ResultRepository.kt | 5 +- .../database/viewmodel/DownloadViewModel.kt | 72 +- .../database/viewmodel/ResultViewModel.kt | 6 +- .../ytdl/database/viewmodel/YTDLPViewModel.kt | 91 +++ .../ytdl/ui/adapter/TemplatesAdapter.kt | 7 +- .../ui/adapter/YoutubePlayerClientAdapter.kt | 9 +- .../ui/downloadcard/AddExtraCommandsDialog.kt | 7 +- .../ObserveSourcesBottomSheetDialog.kt | 6 +- .../downloadcard/ResultCardDetailsDialog.kt | 4 + .../downloads/CancelledDownloadsFragment.kt | 4 + .../ui/downloads/ErroredDownloadsFragment.kt | 4 + .../ui/downloads/QueuedDownloadsFragment.kt | 4 + .../ui/downloads/SavedDownloadsFragment.kt | 4 + .../downloads/ScheduledDownloadsFragment.kt | 4 + .../more/downloadLogs/DownloadLogFragment.kt | 34 +- .../more/settings/UpdateSettingsFragment.kt | 11 +- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 130 ++- .../ytdl/util/extractors/YTDLPUtil.kt | 68 +- .../deniscerri/ytdl/work/DownloadWorker.kt | 24 +- .../ytdl/work/ObserveSourceWorker.kt | 15 +- .../ytdl/work/TerminalDownloadWorker.kt | 6 +- .../work/UpdateMultipleDownloadsDataWorker.kt | 3 +- .../UpdateMultipleDownloadsFormatsWorker.kt | 3 +- .../res/layout/create_command_template.xml | 30 + .../main/res/layout/download_bottom_sheet.xml | 5 +- app/src/main/res/layout/format_item.xml | 3 +- .../main/res/layout/fragment_download_log.xml | 7 + .../layout/observe_sources_bottom_sheet.xml | 151 +++- ...ube_player_client_create_bottom_sheet.xml} | 28 + app/src/main/res/values/strings.xml | 5 +- 37 files changed, 1400 insertions(+), 172 deletions(-) create mode 100644 app/schemas/com.deniscerri.ytdl.database.DBManager/22.json create mode 100644 app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt rename app/src/main/res/layout/{youtube_player_clients.xml => youtube_player_client_create_bottom_sheet.xml} (86%) diff --git a/app/schemas/com.deniscerri.ytdl.database.DBManager/21.json b/app/schemas/com.deniscerri.ytdl.database.DBManager/21.json index e1a65e77..6609fdc2 100644 --- a/app/schemas/com.deniscerri.ytdl.database.DBManager/21.json +++ b/app/schemas/com.deniscerri.ytdl.database.DBManager/21.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 21, - "identityHash": "3be2d00eeed0d077b84c352d10f6628f", + "identityHash": "60367016912c1948d70c556647e93114", "entities": [ { "tableName": "results", @@ -384,7 +384,7 @@ }, { "tableName": "commandTemplates", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0, `preferredCommandTemplate` INTEGER NOT NULL DEFAULT 0, `urlRegex` TEXT NOT NULL DEFAULT '[]')", "fields": [ { "fieldPath": "id", @@ -431,6 +431,20 @@ "affinity": "INTEGER", "notNull": true, "defaultValue": "0" + }, + { + "fieldPath": "preferredCommandTemplate", + "columnName": "preferredCommandTemplate", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "urlRegex", + "columnName": "urlRegex", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" } ], "primaryKey": { @@ -747,7 +761,7 @@ "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, '3be2d00eeed0d077b84c352d10f6628f')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '60367016912c1948d70c556647e93114')" ] } } \ No newline at end of file diff --git a/app/schemas/com.deniscerri.ytdl.database.DBManager/22.json b/app/schemas/com.deniscerri.ytdl.database.DBManager/22.json new file mode 100644 index 00000000..9191a8ae --- /dev/null +++ b/app/schemas/com.deniscerri.ytdl.database.DBManager/22.json @@ -0,0 +1,766 @@ +{ + "formatVersion": 1, + "database": { + "version": 22, + "identityHash": "a7ed801eeaef776e0fb063303b9892df", + "entities": [ + { + "tableName": "results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `creationTime` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumb", + "columnName": "thumb", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "website", + "columnName": "website", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playlistTitle", + "columnName": "playlistTitle", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "formats", + "columnName": "formats", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "urls", + "columnName": "urls", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "chapters", + "columnName": "chapters", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "playlistURL", + "columnName": "playlistURL", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "''" + }, + { + "fieldPath": "playlistIndex", + "columnName": "playlistIndex", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "creationTime", + "columnName": "creationTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `filesize` INTEGER NOT NULL DEFAULT 0, `downloadId` INTEGER NOT NULL DEFAULT 0, `command` TEXT NOT NULL DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumb", + "columnName": "thumb", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadPath", + "columnName": "downloadPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "website", + "columnName": "website", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filesize", + "columnName": "filesize", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "downloadId", + "columnName": "downloadId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "command", + "columnName": "command", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "downloads", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `incognito` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumb", + "columnName": "thumb", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "container", + "columnName": "container", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Default'" + }, + { + "fieldPath": "downloadSections", + "columnName": "downloadSections", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "allFormats", + "columnName": "allFormats", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadPath", + "columnName": "downloadPath", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "website", + "columnName": "website", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadSize", + "columnName": "downloadSize", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "playlistTitle", + "columnName": "playlistTitle", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "audioPreferences", + "columnName": "audioPreferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoPreferences", + "columnName": "videoPreferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "extraCommands", + "columnName": "extraCommands", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "customFileNameTemplate", + "columnName": "customFileNameTemplate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "SaveThumb", + "columnName": "SaveThumb", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'Queued'" + }, + { + "fieldPath": "downloadStartTime", + "columnName": "downloadStartTime", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "logID", + "columnName": "logID", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "playlistURL", + "columnName": "playlistURL", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "''" + }, + { + "fieldPath": "playlistIndex", + "columnName": "playlistIndex", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "incognito", + "columnName": "incognito", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "commandTemplates", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0, `preferredCommandTemplate` INTEGER NOT NULL DEFAULT 0, `urlRegex` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "useAsExtraCommand", + "columnName": "useAsExtraCommand", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "useAsExtraCommandAudio", + "columnName": "useAsExtraCommandAudio", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "useAsExtraCommandVideo", + "columnName": "useAsExtraCommandVideo", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "useAsExtraCommandDataFetching", + "columnName": "useAsExtraCommandDataFetching", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "preferredCommandTemplate", + "columnName": "preferredCommandTemplate", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "urlRegex", + "columnName": "urlRegex", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "searchHistory", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "templateShortcuts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "cookies", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "logs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadType", + "columnName": "downloadType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadTime", + "columnName": "downloadTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "terminalDownloads", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "command", + "columnName": "command", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "log", + "columnName": "log", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sources", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL, `syncWithSource` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadItemTemplate", + "columnName": "downloadItemTemplate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyNr", + "columnName": "everyNr", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "everyCategory", + "columnName": "everyCategory", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyTime", + "columnName": "everyTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "weeklyConfig", + "columnName": "weeklyConfig", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "monthlyConfig", + "columnName": "monthlyConfig", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "startsTime", + "columnName": "startsTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "endsDate", + "columnName": "endsDate", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "endsAfterCount", + "columnName": "endsAfterCount", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "runCount", + "columnName": "runCount", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "getOnlyNewUploads", + "columnName": "getOnlyNewUploads", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "retryMissingDownloads", + "columnName": "retryMissingDownloads", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ignoredLinks", + "columnName": "ignoredLinks", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'[]'" + }, + { + "fieldPath": "alreadyProcessedLinks", + "columnName": "alreadyProcessedLinks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncWithSource", + "columnName": "syncWithSource", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "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, 'a7ed801eeaef776e0fb063303b9892df')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/database/DBManager.kt b/app/src/main/java/com/deniscerri/ytdl/database/DBManager.kt index fc843090..36c00fd3 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/DBManager.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/DBManager.kt @@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem TerminalItem::class, ObserveSourcesItem::class ], - version = 21, + version = 22, autoMigrations = [ AutoMigration (from = 1, to = 2), AutoMigration (from = 2, to = 3), @@ -63,6 +63,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem AutoMigration (from = 18, to = 19), AutoMigration (from = 19, to = 20), //AutoMigration (from = 20, to = 21) MANUALLY HANDLED + //AutoMigration(from = 21, to = 22) MANUALLY HANDLED ] ) abstract class DBManager : RoomDatabase(){ diff --git a/app/src/main/java/com/deniscerri/ytdl/database/Migrations.kt b/app/src/main/java/com/deniscerri/ytdl/database/Migrations.kt index bbbef3aa..98076fe9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/Migrations.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/Migrations.kt @@ -42,6 +42,15 @@ object Migrations { database.execSQL("UPDATE history SET filesize = ${parsed.filesize} WHERE id = $id") } } + }, + + //add preferred command template and url regexes + Migration(21, 22) { database -> + // Add the `preferredCommandTemplate` column as INTEGER (since SQLite does not support BOOLEAN) + database.execSQL("ALTER TABLE commandTemplates ADD COLUMN preferredCommandTemplate INTEGER NOT NULL DEFAULT 0") + + // Add `urlRegex` as a JSON string (since lists are not supported in SQLite) + database.execSQL("ALTER TABLE commandTemplates ADD COLUMN urlRegex TEXT NOT NULL DEFAULT '[]'") } ) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/CommandTemplateDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/CommandTemplateDao.kt index 39393434..53acd30f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/dao/CommandTemplateDao.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/CommandTemplateDao.kt @@ -38,11 +38,14 @@ interface CommandTemplateDao { @Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1") fun getAllTemplatesAsExtraCommands() : List - @Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1") - fun getAllTemplatesAsExtraCommandsForAudio() : List + @Query("SELECT * FROM commandTemplates WHERE useAsExtraCommandDataFetching is 1") + fun getAllTemplatesAsDataFetchingExtraCommands() : List - @Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandVideo = 1") - fun getAllTemplatesAsExtraCommandsForVideo() : List + @Query("SELECT * FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1") + fun getAllTemplatesAsExtraCommandsForAudio() : List + + @Query("SELECT * FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandVideo = 1") + fun getAllTemplatesAsExtraCommandsForVideo() : List @Query("SELECT * FROM templateShortcuts ORDER BY id DESC") fun getAllShortcutsFlow() : Flow> @@ -65,6 +68,9 @@ interface CommandTemplateDao { @Query("SELECT * FROM commandTemplates ORDER BY id DESC LIMIT 1") fun getFirst() : CommandTemplate? + @Query("SELECT * FROM commandTemplates WHERE preferredCommandTemplate is 1") + fun getPreferredCommandTemplates() : List + @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(item: CommandTemplate) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/models/CommandTemplate.kt b/app/src/main/java/com/deniscerri/ytdl/database/models/CommandTemplate.kt index 933de0d6..f62419e8 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/models/CommandTemplate.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/models/CommandTemplate.kt @@ -19,5 +19,8 @@ data class CommandTemplate( @ColumnInfo(defaultValue = "1") var useAsExtraCommandVideo: Boolean, @ColumnInfo(defaultValue = "0") - var useAsExtraCommandDataFetching: Boolean + var useAsExtraCommandDataFetching: Boolean, + @ColumnInfo(defaultValue = "0") + var preferredCommandTemplate : Boolean = false, + var urlRegex: MutableList = mutableListOf() ) \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt index d1f4d492..a599e3cb 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt @@ -4,8 +4,9 @@ data class YoutubePlayerClientItem( var playerClient: String, var poTokens: MutableList, var enabled: Boolean = true, - var useOnlyPoToken: Boolean = false -) + var useOnlyPoToken: Boolean = false, + var urlRegex: MutableList = mutableListOf() + ) data class YoutubePoTokenItem( var context: String, diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt index 0655b6c5..caac3e17 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt @@ -3,6 +3,7 @@ package com.deniscerri.ytdl.database.repository import android.content.Context import android.util.Patterns import androidx.preference.PreferenceManager +import com.deniscerri.ytdl.database.dao.CommandTemplateDao import com.deniscerri.ytdl.database.dao.ResultDao import com.deniscerri.ytdl.database.models.ChapterItem import com.deniscerri.ytdl.database.models.DownloadItem @@ -21,7 +22,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.runBlocking -class ResultRepository(private val resultDao: ResultDao, private val context: Context) { +class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) { val YTDLNIS_SEARCH = "YTDLNIS_SEARCH" val allResults : Flow> = resultDao.getResults() var itemCount = MutableStateFlow(-1) @@ -31,7 +32,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co } private val youtubeApiUtil = YoutubeApiUtil(context) - private val ytdlpUtil = YTDLPUtil(context) + private val ytdlpUtil = YTDLPUtil(context, commandTemplateDao) private var newPipeUtil = NewPipeUtil(context) private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index 880af2df..eb66050a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -13,6 +13,7 @@ import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import androidx.media3.common.Player.Command import androidx.paging.PagingData import androidx.paging.filter import androidx.paging.map @@ -76,7 +77,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel private val commandTemplateDao: CommandTemplateDao private val formatUtil = FormatUtil(application) private val notificationUtil = NotificationUtil(application) - private val ytdlpUtil = YTDLPUtil(application) + private val ytdlpUtil: YTDLPUtil private val resources : Resources val allDownloads : Flow> @@ -115,8 +116,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel mutableListOf() ) - private var extraCommandsForAudio: String = "" - private var extraCommandsForVideo: String = "" + private var extraCommandsForAudio: List = listOf() + private var extraCommandsForVideo: List = listOf() private val dao: DownloadDao private val historyRepository: HistoryRepository @@ -138,11 +139,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel init { dbManager = DBManager.getInstance(application) dao = dbManager.downloadDao + commandTemplateDao = DBManager.getInstance(application).commandTemplateDao repository = DownloadRepository(dao) historyRepository = HistoryRepository(dbManager.historyDao) - resultRepository = ResultRepository(dbManager.resultDao, application) + resultRepository = ResultRepository(dbManager.resultDao, commandTemplateDao, application) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) - commandTemplateDao = DBManager.getInstance(application).commandTemplateDao + ytdlpUtil = YTDLPUtil(application, commandTemplateDao) activeDownloadsCount = repository.activeDownloadsCount activePausedDownloadsCount = repository.activePausedDownloadsCount @@ -164,8 +166,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel erroredDownloads = repository.erroredDownloads.flow viewModelScope.launch(Dispatchers.IO){ if (sharedPreferences.getBoolean("use_extra_commands", false)){ - extraCommandsForAudio = commandTemplateDao.getAllTemplatesAsExtraCommandsForAudio().joinToString(" ") - extraCommandsForVideo = commandTemplateDao.getAllTemplatesAsExtraCommandsForVideo().joinToString(" ") + extraCommandsForAudio = commandTemplateDao.getAllTemplatesAsExtraCommandsForAudio() + extraCommandsForVideo = commandTemplateDao.getAllTemplatesAsExtraCommandsForVideo() } } @@ -320,8 +322,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val extraCommands = when(type){ Type.audio -> extraCommandsForAudio Type.video -> extraCommandsForVideo - else -> "" - } + else -> listOf() + }.filter { + it.urlRegex.isEmpty() || it.urlRegex.any { u -> + Regex(u).containsMatchIn(resultItem.url) + } + }.joinToString(" ") { it.content } return DownloadItem(0, resultItem.url, @@ -330,7 +336,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel resultItem.thumb, resultItem.duration, type, - getFormat(resultItem.formats, type), + getFormat(resultItem.formats, type, resultItem.url), container!!, "", resultItem.formats, @@ -412,7 +418,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel fun switchDownloadType(list: List, type: Type) : List{ list.forEach { - val format = getFormat(it.allFormats, type) + val format = getFormat(it.allFormats, type, it.url) it.format = format var updatedDownloadPath = "" @@ -475,11 +481,15 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) - val extraCommands = when(historyItem.type){ + val extraCommands = when (historyItem.type) { Type.audio -> extraCommandsForAudio Type.video -> extraCommandsForVideo - else -> "" - } + else -> listOf() + }.filter { + it.urlRegex.isEmpty() || it.urlRegex.any { u -> + Regex(u).containsMatchIn(historyItem.url) + } + }.joinToString(" ") { it.content } val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!)) val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, subsLanguages, recodeVideo = recodeVideo) @@ -521,7 +531,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel } - fun getFormat(formats: List, type: Type) : Format { + fun getFormat(formats: List, type: Type, url: String? = null) : Format { when(type) { Type.audio -> { return cloneFormat ( @@ -550,19 +560,27 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel ) } else -> { - val preferredTemplateContent = - sharedPreferences.getString("preferred_command_template", "")!!.ifEmpty { - sharedPreferences.getString("lastCommandTemplateUsed", "")!! - } - - val c = if (preferredTemplateContent.isNotBlank()) { - commandTemplateDao.getTemplateByContent(preferredTemplateContent) - }else { - commandTemplateDao.getFirst() + val preferredCommandTemplates = commandTemplateDao.getPreferredCommandTemplates() + var template : CommandTemplate? = null + if (url != null) { + template = preferredCommandTemplates.firstOrNull { it.urlRegex.isEmpty() || it.urlRegex.any { u -> + Regex(u).containsMatchIn(url) + } } } + if (template == null) { + template = commandTemplateDao.getFirst() + } return generateCommandFormat( - c ?: CommandTemplate(0, "", preferredTemplateContent, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false) + template ?: CommandTemplate( + 0, + "", + sharedPreferences.getString("lastCommandTemplateUsed", "") ?: "", + useAsExtraCommand = false, + useAsExtraCommandAudio = false, + useAsExtraCommandVideo = false, + useAsExtraCommandDataFetching = false + ) ) } } @@ -1128,7 +1146,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val item = repository.getItemByID(id) item.allFormats.clear() item.allFormats.addAll(list) - item.format = getFormat(list, item.type) + item.format = getFormat(list, item.type, item.url) runCatching { resultRepository.getAllByURL(item.url).forEach { @@ -1144,7 +1162,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel items.forEach { item -> item.allFormats.clear() item.allFormats.addAll(list) - item.format = getFormat(list, item.type) + item.format = getFormat(list, item.type, item.url) repository.update(item) } diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt index 840dbf75..6ffdbe40 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt @@ -71,8 +71,10 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a private val sharedPreferences: SharedPreferences init { - dao = DBManager.getInstance(application).resultDao - repository = ResultRepository(dao, getApplication().applicationContext) + val dbManager = DBManager.getInstance(application) + dao = dbManager.resultDao + val commandTemplateDao = dbManager.commandTemplateDao + repository = ResultRepository(dao, commandTemplateDao, getApplication().applicationContext) searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao) items = repository.allResults.asLiveData() diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt new file mode 100644 index 00000000..77c267f2 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/YTDLPViewModel.kt @@ -0,0 +1,91 @@ +package com.deniscerri.ytdl.database.viewmodel + +import android.app.Application +import android.content.SharedPreferences +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import android.os.Parcelable +import android.util.DisplayMetrics +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.filter +import androidx.paging.map +import androidx.preference.PreferenceManager +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import com.deniscerri.ytdl.App +import com.deniscerri.ytdl.R +import com.deniscerri.ytdl.database.DBManager +import com.deniscerri.ytdl.database.dao.CommandTemplateDao +import com.deniscerri.ytdl.database.dao.DownloadDao +import com.deniscerri.ytdl.database.models.AudioPreferences +import com.deniscerri.ytdl.database.models.CommandTemplate +import com.deniscerri.ytdl.database.models.DownloadItem +import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple +import com.deniscerri.ytdl.database.models.DownloadItemSimple +import com.deniscerri.ytdl.database.models.Format +import com.deniscerri.ytdl.database.models.HistoryItem +import com.deniscerri.ytdl.database.models.ResultItem +import com.deniscerri.ytdl.database.models.VideoPreferences +import com.deniscerri.ytdl.database.repository.DownloadRepository +import com.deniscerri.ytdl.database.repository.HistoryRepository +import com.deniscerri.ytdl.database.repository.ResultRepository +import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple +import com.deniscerri.ytdl.util.Extensions.needsDataUpdating +import com.deniscerri.ytdl.util.Extensions.toListString +import com.deniscerri.ytdl.util.FileUtil +import com.deniscerri.ytdl.util.FormatUtil +import com.deniscerri.ytdl.util.NotificationUtil +import com.deniscerri.ytdl.util.extractors.YTDLPUtil +import com.deniscerri.ytdl.work.AlarmScheduler +import com.deniscerri.ytdl.work.UpdateMultipleDownloadsDataWorker +import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker +import com.google.gson.Gson +import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize +import java.io.File +import java.util.Locale + + +class YTDLPViewModel(private val application: Application) : AndroidViewModel(application) { + private val dbManager: DBManager + private val commandTemplateDao: CommandTemplateDao + private val ytdlpUtil: YTDLPUtil + + init { + dbManager = DBManager.getInstance(application) + commandTemplateDao = DBManager.getInstance(application).commandTemplateDao + ytdlpUtil = YTDLPUtil(application, commandTemplateDao) + } + + fun parseYTDLRequestString(item: DownloadItem) : String { + val req = ytdlpUtil.buildYoutubeDLRequest(item) + return ytdlpUtil.parseYTDLRequestString(req) + } + + fun getVersion(channel: String) : String { + return ytdlpUtil.getVersion(application, channel) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/TemplatesAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/TemplatesAdapter.kt index ca44b3cc..a1aac6c4 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/TemplatesAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/TemplatesAdapter.kt @@ -76,8 +76,7 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ } card.findViewById(R.id.preferredTemplate).apply { - val preferred = sharedPreferences.getString("preferred_command_template", "") - isVisible = preferred == item.content + isVisible = item.preferredCommandTemplate } if (checkedItems.contains(item.id)) { @@ -170,7 +169,9 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ oldItem.useAsExtraCommand == newItem.useAsExtraCommand && oldItem.useAsExtraCommandAudio == newItem.useAsExtraCommandAudio && oldItem.useAsExtraCommandVideo == newItem.useAsExtraCommandVideo && - oldItem.useAsExtraCommandDataFetching == newItem.useAsExtraCommandDataFetching + oldItem.useAsExtraCommandDataFetching == newItem.useAsExtraCommandDataFetching && + oldItem.preferredCommandTemplate == newItem.preferredCommandTemplate && + oldItem.urlRegex.joinToString("") == newItem.urlRegex.joinToString("") } } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt index 9da61c46..94711966 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt @@ -115,11 +115,10 @@ class YoutubePlayerClientAdapter(onItemClickListener: OnItemClickListener, activ } override fun areContentsTheSame(oldItem: YoutubePlayerClientItem, newItem: YoutubePlayerClientItem): Boolean { - return oldItem.playerClient == newItem.playerClient && oldItem.enabled == newItem.enabled && oldItem.poTokens.joinToString( - "\n" - ) { it.token } == newItem.poTokens.joinToString( - "\n" - ) { it.token } + return oldItem.playerClient == newItem.playerClient && + oldItem.enabled == newItem.enabled && + oldItem.poTokens.joinToString("\n") { it.token } == newItem.poTokens.joinToString("\n") { it.token } && + oldItem.urlRegex.joinToString("\n") == newItem.urlRegex.joinToString("\n") } } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt index aeb13c85..0f07f90d 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/AddExtraCommandsDialog.kt @@ -19,6 +19,7 @@ import androidx.preference.PreferenceManager import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.extractors.YTDLPUtil @@ -31,15 +32,15 @@ import kotlinx.coroutines.withContext class AddExtraCommandsDialog(private val item: DownloadItem? = null, private val callback: ExtraCommandsListener? = null) : BottomSheetDialogFragment() { - private lateinit var ytdlpUtil: YTDLPUtil private lateinit var sharedPreferences: SharedPreferences private lateinit var commandTemplateViewModel: CommandTemplateViewModel + private lateinit var ytdlpViewModel: YTDLPViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - ytdlpUtil = YTDLPUtil(requireActivity().applicationContext) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] + ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java] } override fun onCreateView( @@ -86,7 +87,7 @@ class AddExtraCommandsDialog(private val item: DownloadItem? = null, private val val currentText = view.findViewById(R.id.currentText) if (item != null){ - val currentCommand = ytdlpUtil.parseYTDLRequestString(ytdlpUtil.buildYoutubeDLRequest(item)) + val currentCommand = ytdlpViewModel.parseYTDLRequestString(item) currentText?.text = currentCommand }else{ view.findViewById(R.id.current).visibility = View.GONE diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt index 69f19ebb..fda3b1fc 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ObserveSourcesBottomSheetDialog.kt @@ -549,15 +549,15 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() { val url = url.editText?.text?.isNotBlank() == true val category = ObserveSourcesRepository.EveryCategory.values()[categoryAdapter.getPosition(everyCat.text.toString())] - val everyNr = everyNr.editText?.text?.isNotBlank() == true + val everyNrBool = everyNr.editText!!.text.isNotBlank() && everyNr.editText!!.text.toString() != "0" val everyTime = everyTime.editText?.text?.isNotBlank() == true val every = when(category){ ObserveSourcesRepository.EveryCategory.HOUR -> { - everyNr + everyNrBool } else -> { - everyNr && everyTime + everyNrBool && everyTime } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt index 1accbad4..e6f2213c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt @@ -44,6 +44,7 @@ import com.deniscerri.ytdl.database.models.ResultItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.ResultViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.adapter.ActiveDownloadMinifiedAdapter import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdl.util.Extensions.setFullScreen @@ -79,6 +80,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap private lateinit var videoView: PlayerView private lateinit var downloadViewModel: DownloadViewModel private lateinit var resultViewModel: ResultViewModel + private lateinit var ytdlpViewModel: YTDLPViewModel private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter private lateinit var queuedAdapter: GenericDownloadAdapter @@ -94,6 +96,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap notificationUtil = NotificationUtil(requireActivity()) downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] + ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java] downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) } @@ -446,6 +449,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap item, requireActivity(), DownloadRepository.Status.valueOf(item.status), + ytdlpViewModel, removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> sheet.hide() removeQueuedItem(itemID) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt index 7c98dfed..6563172f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt @@ -36,6 +36,7 @@ import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode @@ -60,6 +61,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic private var fragmentView: View? = null private var activity: Activity? = null private lateinit var downloadViewModel : DownloadViewModel + private lateinit var ytdlpViewModel : YTDLPViewModel private lateinit var cancelledRecyclerView : RecyclerView private lateinit var preferences : SharedPreferences private lateinit var adapter : GenericDownloadAdapter @@ -79,6 +81,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic fragmentView = inflater.inflate(R.layout.generic_list, container, false) activity = getActivity() downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] + ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java] preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) return fragmentView } @@ -174,6 +177,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic item, requireActivity(), DownloadRepository.Status.valueOf(item.status), + ytdlpViewModel, removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem(it, sheet) }, diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt index c65ec249..7a9b1e28 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt @@ -35,6 +35,7 @@ import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode @@ -58,6 +59,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL private var fragmentView: View? = null private var activity: Activity? = null private lateinit var downloadViewModel : DownloadViewModel + private lateinit var ytdlpViewModel : YTDLPViewModel private lateinit var erroredRecyclerView : RecyclerView private lateinit var preferences : SharedPreferences private lateinit var noResults : RelativeLayout @@ -77,6 +79,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL fragmentView = inflater.inflate(R.layout.generic_list, container, false) activity = getActivity() downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] + ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java] preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) return fragmentView } @@ -174,6 +177,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL item, requireActivity(), DownloadRepository.Status.valueOf(item.status), + ytdlpViewModel, removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem(it, sheet) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt index 26cf99f6..dc508f6a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt @@ -36,6 +36,7 @@ import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.adapter.QueuedDownloadAdapter import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode @@ -61,6 +62,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis private var fragmentView: View? = null private var activity: Activity? = null private lateinit var downloadViewModel : DownloadViewModel + private lateinit var ytdlpViewModel : YTDLPViewModel private lateinit var queuedRecyclerView : RecyclerView private lateinit var adapter : QueuedDownloadAdapter private lateinit var noResults : RelativeLayout @@ -80,6 +82,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis activity = getActivity() notificationUtil = NotificationUtil(requireContext()) downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] + ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java] return fragmentView } @@ -450,6 +453,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis item, requireActivity(), DownloadRepository.Status.valueOf(item.status), + ytdlpViewModel, removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> sheet.hide() removeItem(it.id) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt index c5ea851f..32914d2e 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt @@ -34,6 +34,7 @@ import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode @@ -57,6 +58,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis private var fragmentView: View? = null private var activity: Activity? = null private lateinit var downloadViewModel : DownloadViewModel + private lateinit var ytdlpViewModel : YTDLPViewModel private lateinit var savedRecyclerView: RecyclerView private lateinit var adapter: GenericDownloadAdapter private lateinit var preferences: SharedPreferences @@ -76,6 +78,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis fragmentView = inflater.inflate(R.layout.generic_list, container, false) activity = getActivity() downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] + ytdlpViewModel = ViewModelProvider(requireActivity())[YTDLPViewModel::class.java] preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) return fragmentView } @@ -183,6 +186,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis item, requireActivity(), DownloadRepository.Status.valueOf(item.status), + ytdlpViewModel, removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> removeItem(it, sheet) }, diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt index 57793bc1..f449e090 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt @@ -35,6 +35,7 @@ import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.adapter.GenericDownloadAdapter import com.deniscerri.ytdl.ui.adapter.ScheduledDownloadAdapter import com.deniscerri.ytdl.util.Extensions.enableFastScroll @@ -58,6 +59,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl private var fragmentView: View? = null private var activity: Activity? = null private lateinit var downloadViewModel : DownloadViewModel + private lateinit var ytdlpViewModel : YTDLPViewModel private lateinit var scheduledRecyclerView : RecyclerView private lateinit var preferences : SharedPreferences private lateinit var adapter : ScheduledDownloadAdapter @@ -77,6 +79,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl fragmentView = inflater.inflate(R.layout.generic_list, container, false) activity = getActivity() downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] + ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java] preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) return fragmentView } @@ -179,6 +182,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl item, requireActivity(), DownloadRepository.Status.valueOf(item.status), + ytdlpViewModel, removeItem = { it: DownloadItem, sheet: BottomSheetDialog -> sheet.hide() removeItem(it, sheet) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt index 11ffe4a8..c19699ab 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt @@ -29,15 +29,20 @@ import com.deniscerri.ytdl.database.viewmodel.LogViewModel import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.setCustomTextSize +import com.deniscerri.ytdl.work.DownloadWorker import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.slider.Slider import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode class DownloadLogFragment : Fragment() { @@ -48,6 +53,7 @@ class DownloadLogFragment : Fragment() { private lateinit var mainActivity: MainActivity private lateinit var logViewModel: LogViewModel private lateinit var sharedPreferences: SharedPreferences + private var logID: Long? = null private var autoScroll : Boolean = true private var scrollDownBtn : MenuItem? = null @@ -95,8 +101,8 @@ class DownloadLogFragment : Fragment() { .show() } - val id = arguments?.getLong("logID") - if (id == null || id == 0L) { + logID = arguments?.getLong("logID") + if (logID == null || logID == 0L) { mainActivity.onBackPressedDispatcher.onBackPressed() } @@ -105,7 +111,7 @@ class DownloadLogFragment : Fragment() { CoroutineScope(Dispatchers.IO).launch { runCatching { - val logItem = logViewModel.getItemById(id!!) + val logItem = logViewModel.getItemById(logID!!) withContext(Dispatchers.Main){ topAppBar.title = logItem.title } @@ -202,7 +208,7 @@ class DownloadLogFragment : Fragment() { } } - logViewModel.getLogFlowByID(id!!).observe(viewLifecycleOwner){logItem -> + logViewModel.getLogFlowByID(logID!!).observe(viewLifecycleOwner){logItem -> kotlin.runCatching { requireActivity().runOnUiThread{ if (logItem != null){ @@ -227,6 +233,26 @@ class DownloadLogFragment : Fragment() { scrollDownBtn?.isVisible = canVerticallyScroll } + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + super.onStop() + EventBus.getDefault().unregister(this) + } + + //dont remove + @Subscribe(threadMode = ThreadMode.MAIN) + fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) { + val progressBar = requireView().findViewById(R.id.progress) + if (event.logItemID == logID) { + progressBar.isVisible = true + progressBar.setProgress(event.progress, true) + } + } + companion object { private const val TAG = "DownloadLogActivity" } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/UpdateSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/UpdateSettingsFragment.kt index 7e0b82d4..1be1d6e9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/UpdateSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/UpdateSettingsFragment.kt @@ -29,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.get import androidx.core.view.isVisible +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceManager @@ -36,6 +37,8 @@ import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdl.BuildConfig import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.CommandTemplate +import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil.showShortcutsSheet import com.deniscerri.ytdl.util.UpdateUtil @@ -64,7 +67,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() { private var updateUtil: UpdateUtil? = null private var version: Preference? = null private lateinit var preferences: SharedPreferences - private lateinit var ytdlpUtil: YTDLPUtil + private lateinit var ytdlpViewModel: YTDLPViewModel override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -74,7 +77,9 @@ class UpdateSettingsFragment : BaseSettingsFragment() { updateYTDL = findPreference("update_ytdl") ytdlVersion = findPreference("ytdl-version") ytdlSource = findPreference("ytdlp_source_label") - ytdlpUtil = YTDLPUtil(requireContext()) + + ytdlpViewModel = ViewModelProvider(this)[YTDLPViewModel::class.java] + ytdlSource?.apply { summary = preferences.getString("ytdlp_source_label", "") setOnPreferenceClickListener { @@ -141,7 +146,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() { lifecycleScope.launch { ytdlVersion!!.summary = getString(R.string.loading) val version = withContext(Dispatchers.IO){ - ytdlpUtil.getVersion(requireContext(), preferences.getString("ytdlp_source", "stable")!!) + ytdlpViewModel.getVersion(preferences.getString("ytdlp_source", "stable")!!) } preferences.edit().apply { putString("ytdl-version", version) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index c3579d94..3627aa41 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -5,7 +5,6 @@ import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.Activity -import android.app.Dialog import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -15,11 +14,9 @@ import android.content.SharedPreferences import android.net.Uri import android.os.Build import android.text.Editable -import android.text.InputType import android.text.TextWatcher import android.text.format.DateFormat import android.util.DisplayMetrics -import android.util.Log import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -35,20 +32,14 @@ import android.widget.PopupMenu import android.widget.RadioButton import android.widget.TextView import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.DimenRes import androidx.annotation.OptIn import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.ui.unit.dp import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.text.HtmlCompat -import androidx.core.text.parseAsHtml import androidx.core.view.children import androidx.core.view.isVisible -import androidx.core.view.marginBottom -import androidx.core.view.updateLayoutParams import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doOnTextChanged import androidx.fragment.app.FragmentManager @@ -56,7 +47,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.preference.PreferenceManager import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.afollestad.materialdialogs.utils.MDUtil.textChanged -import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.R import com.deniscerri.ytdl.database.models.CommandTemplate import com.deniscerri.ytdl.database.models.DownloadItem @@ -68,8 +58,8 @@ import com.deniscerri.ytdl.database.models.YoutubePoTokenItem import com.deniscerri.ytdl.database.repository.DownloadRepository import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.downloadcard.VideoCutListener -import com.deniscerri.ytdl.ui.more.WebViewActivity import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.getMediaDuration import com.deniscerri.ytdl.util.Extensions.toStringDuration @@ -91,21 +81,17 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.materialswitch.MaterialSwitch import com.google.android.material.snackbar.Snackbar -import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputLayout -import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat import com.google.gson.Gson -import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.io.File -import java.net.URL import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale @@ -214,6 +200,8 @@ object UiUtil { val extraCommandsDataFetchingSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandDataFetching)!! val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!! val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!! + val urlRegex : TextInputLayout = bottomSheet.findViewById(R.id.url_regex)!! + val urlRegexChips : ChipGroup = bottomSheet.findViewById(R.id.urlRegexChipGroup)!! extraCommandsDataFetchingSwitch.isVisible = sharedPreferences.getBoolean("enable_data_fetching_extra_commands", false) @@ -274,7 +262,7 @@ object UiUtil { } if (item != null){ - preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "") + preferredCommandSwitch.isChecked = item.preferredCommandTemplate extraCommandsDataFetchingSwitch.isChecked = item.useAsExtraCommandDataFetching && extraCommandsDataFetchingSwitch.isVisible extraCommandsSwitch.isChecked = item.useAsExtraCommand @@ -289,15 +277,40 @@ object UiUtil { extraCommandsVideo.isVisible = false extraCommandsVideo.isChecked = false } + + val canUseURLRegex = item.useAsExtraCommand || item.useAsExtraCommandDataFetching || item.preferredCommandTemplate + urlRegex.isVisible = canUseURLRegex + urlRegexChips.isVisible = canUseURLRegex + + for(chip in item.urlRegex) { + val tmp = context.layoutInflater.inflate(R.layout.input_chip, urlRegexChips, false) as Chip + tmp.text = chip + tmp.setOnClickListener { + urlRegexChips.removeView(tmp) + } + urlRegexChips.addView(tmp) + } } + preferredCommandSwitch.setOnCheckedChangeListener { compoundButton, b -> + val canUseURLRegex = extraCommandsSwitch.isChecked || extraCommandsDataFetchingSwitch.isChecked || preferredCommandSwitch.isChecked + urlRegex.isVisible = canUseURLRegex + urlRegexChips.isVisible = canUseURLRegex + } + extraCommandsSwitch.setOnCheckedChangeListener { compoundButton, b -> extraCommandsAudio.isVisible = extraCommandsSwitch.isChecked extraCommandsAudio.isChecked = true extraCommandsVideo.isVisible = extraCommandsSwitch.isChecked extraCommandsVideo.isChecked = true + + val canUseURLRegex = extraCommandsSwitch.isChecked || extraCommandsDataFetchingSwitch.isChecked || preferredCommandSwitch.isChecked + urlRegex.isVisible = canUseURLRegex + urlRegexChips.isVisible = canUseURLRegex } + + extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b -> ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty() } @@ -306,6 +319,28 @@ object UiUtil { ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty() } + extraCommandsDataFetchingSwitch.setOnCheckedChangeListener { compoundButton, b -> + val canUseURLRegex = extraCommandsSwitch.isChecked || extraCommandsDataFetchingSwitch.isChecked || preferredCommandSwitch.isChecked + urlRegex.isVisible = canUseURLRegex + urlRegexChips.isVisible = canUseURLRegex + } + + urlRegex.isEndIconVisible = false + urlRegex.editText!!.doOnTextChanged { text, start, before, count -> + urlRegex.isEndIconVisible = urlRegex.editText!!.text.isNotBlank() + } + + urlRegex.setEndIconOnClickListener { + val text = urlRegex.editText!!.text + urlRegex.editText!!.setText("") + val tmp = context.layoutInflater.inflate(R.layout.input_chip, urlRegexChips, false) as Chip + tmp.text = text + tmp.setOnClickListener { + urlRegexChips.removeView(tmp) + } + urlRegexChips.addView(tmp) + } + commandTemplateViewModel.shortcuts.observe(lifeCycle){ shortcutsChipGroup.removeAllViews() it.forEach {shortcut -> @@ -323,15 +358,20 @@ object UiUtil { } ok.setOnClickListener { - if (preferredCommandSwitch.isChecked) { - sharedPreferences.edit().putString("preferred_command_template", content.editText!!.text.toString()).apply() - }else if (sharedPreferences.getString("preferred_command_template", "") == item?.content){ - sharedPreferences.edit().putString("preferred_command_template", "").apply() - } - - + val urlRegexes = urlRegexChips.children.map { (it as Chip).text.toString() }.toMutableList() if (item == null){ - val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked, extraCommandsDataFetchingSwitch.isChecked) + + val t = CommandTemplate( + 0, + title.editText!!.text.toString(), + content.editText!!.text.toString(), + extraCommandsSwitch.isChecked, + extraCommandsAudio.isChecked, + extraCommandsVideo.isChecked, + extraCommandsDataFetchingSwitch.isChecked, + preferredCommandSwitch.isChecked, + urlRegexes + ) commandTemplateViewModel.insert(t) newTemplate(t) }else{ @@ -341,6 +381,8 @@ object UiUtil { item.useAsExtraCommandAudio = extraCommandsAudio.isChecked item.useAsExtraCommandVideo = extraCommandsVideo.isChecked item.useAsExtraCommandDataFetching = extraCommandsDataFetchingSwitch.isChecked + item.preferredCommandTemplate = preferredCommandSwitch.isChecked + item.urlRegex = urlRegexes commandTemplateViewModel.update(item) newTemplate(item) } @@ -490,6 +532,7 @@ object UiUtil { item: DownloadItem, context: Activity, status: DownloadRepository.Status, + ytdlpViewModel: YTDLPViewModel, removeItem : (DownloadItem, BottomSheetDialog) -> Unit, downloadItem: (DownloadItem) -> Unit, longClickDownloadButton: (DownloadItem) -> Unit, @@ -598,10 +641,8 @@ object UiUtil { if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE else fileSize!!.text = fileSizeReadable - val ytdlpUtil = YTDLPUtil(context) - command?.setOnClickListener { - showGeneratedCommand(context, ytdlpUtil.parseYTDLRequestString(ytdlpUtil.buildYoutubeDLRequest(item))) + showGeneratedCommand(context, ytdlpViewModel.parseYTDLRequestString(item)) } val link = bottomSheet.findViewById