From 177ba4881c7bd49db8ee6a3fcb1f9274c1223528 Mon Sep 17 00:00:00 2001 From: zaednasr <75589932+zaednasr@users.noreply.github.com> Date: Sun, 25 Feb 2024 19:23:38 +0100 Subject: [PATCH] 1.7.3 --- app/build.gradle | 2 +- .../15.json | 724 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 1 - .../com/deniscerri/ytdlnis/MainActivity.kt | 3 - .../deniscerri/ytdlnis/database/DBManager.kt | 35 +- .../database/dao/CommandTemplateDao.kt | 6 +- .../ytdlnis/database/dao/CookieDao.kt | 8 +- .../ytdlnis/database/dao/DownloadDao.kt | 46 +- .../ytdlnis/database/dao/HistoryDao.kt | 6 +- .../deniscerri/ytdlnis/database/dao/LogDao.kt | 7 +- .../ytdlnis/database/dao/ObserveSourcesDao.kt | 7 +- .../ytdlnis/database/dao/ResultDao.kt | 12 +- .../ytdlnis/database/dao/SearchHistoryDao.kt | 5 +- .../ytdlnis/database/dao/TerminalDao.kt | 17 +- .../database/models/ObserveSourcesItem.kt | 4 +- .../database/models/SearchSuggestionItem.kt | 5 - .../database/models/VideoPreferences.kt | 3 +- .../database/repository/CookieRepository.kt | 2 +- .../database/repository/DownloadRepository.kt | 76 +- .../database/repository/HistoryRepository.kt | 2 +- .../database/repository/LogRepository.kt | 14 +- .../repository/ObserveSourcesRepository.kt | 17 + .../database/repository/ResultRepository.kt | 116 ++- .../database/viewmodel/CookieViewModel.kt | 64 +- .../database/viewmodel/DownloadViewModel.kt | 98 +-- .../database/viewmodel/HistoryViewModel.kt | 2 +- .../viewmodel/ObserveSourcesViewModel.kt | 10 +- .../database/viewmodel/ResultViewModel.kt | 122 +-- .../CancelDownloadNotificationReceiver.kt | 1 - .../ytdlnis/receiver/ObserveAlarmReceiver.kt | 1 - .../PauseDownloadNotificationReceiver.kt | 10 +- .../AlarmStartReceiver.kt | 1 - .../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 56 +- .../ui/adapter/ActiveDownloadAdapter.kt | 9 +- .../adapter/ActiveDownloadMinifiedAdapter.kt | 3 +- .../ui/adapter/AlreadyExistsAdapter.kt | 7 - .../ConfigureMultipleDownloadsAdapter.kt | 3 + .../ytdlnis/ui/adapter/DownloadLogsAdapter.kt | 15 +- .../ui/adapter/GenericDownloadAdapter.kt | 16 +- .../ytdlnis/ui/adapter/HistoryAdapter.kt | 2 +- .../ytdlnis/ui/adapter/HomeAdapter.kt | 4 - .../ui/adapter/ObserveSourcesAdapter.kt | 4 - .../ui/adapter/SearchSuggestionsAdapter.kt | 1 - .../ytdlnis/ui/adapter/TemplatesAdapter.kt | 2 - .../ui/downloadcard/AddExtraCommandsDialog.kt | 2 +- .../ConfigureDownloadBottomSheetDialog.kt | 5 +- .../downloadcard/CutVideoBottomSheetDialog.kt | 4 +- .../ui/downloadcard/DownloadAudioFragment.kt | 38 +- .../downloadcard/DownloadBottomSheetDialog.kt | 85 +- .../downloadcard/DownloadCommandFragment.kt | 4 +- .../DownloadMultipleBottomSheetDialog.kt | 19 +- .../ui/downloadcard/DownloadVideoFragment.kt | 24 +- .../FormatSelectionBottomSheetDialog.kt | 4 - .../ObserveSourcesBottomSheetDialog.kt | 19 +- .../downloadcard/ResultCardDetailsDialog.kt | 15 +- .../downloadcard/SelectPlaylistItemsDialog.kt | 5 +- .../ui/downloads/ActiveDownloadsFragment.kt | 4 +- .../downloads/CancelledDownloadsFragment.kt | 5 +- .../downloads/DownloadListFragmentAdapter.kt | 4 - .../ui/downloads/DownloadQueueMainFragment.kt | 57 +- .../ui/downloads/ErroredDownloadsFragment.kt | 5 +- .../ytdlnis/ui/downloads/HistoryFragment.kt | 10 +- .../ui/downloads/QueuedDownloadsFragment.kt | 33 +- .../ui/downloads/SavedDownloadsFragment.kt | 5 +- .../ui/more/CommandTemplatesFragment.kt | 8 +- .../ytdlnis/ui/more/CookiesFragment.kt | 16 +- .../ytdlnis/ui/more/ObserveSourcesFragment.kt | 77 +- .../ytdlnis/ui/more/WebViewActivity.kt | 85 +- .../more/downloadLogs/DownloadLogFragment.kt | 9 +- .../downloadLogs/DownloadLogListFragment.kt | 2 +- .../ui/more/settings/BaseSettingsFragment.kt | 1 - .../more/settings/FolderSettingsFragment.kt | 1 - .../ui/more/settings/MainSettingsFragment.kt | 351 +++++---- .../settings/ProcessingSettingsFragment.kt | 1 - .../more/settings/UpdateSettingsFragment.kt | 87 ++- .../terminal/TerminalDownloadsListFragment.kt | 2 +- .../ui/more/terminal/TerminalFragment.kt | 7 +- .../deniscerri/ytdlnis/util/DownloadUtil.kt | 92 --- .../com/deniscerri/ytdlnis/util/Extensions.kt | 26 +- .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 205 ++--- .../ytdlnis/util/NotificationUtil.kt | 5 - .../com/deniscerri/ytdlnis/util/ThemeUtil.kt | 2 - .../com/deniscerri/ytdlnis/util/UiUtil.kt | 85 +- .../com/deniscerri/ytdlnis/util/UpdateUtil.kt | 7 +- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 62 +- .../ytdlnis/work/ObserveSourceWorker.kt | 175 ++--- .../ytdlnis/work/TerminalDownloadWorker.kt | 3 +- .../ytdlnis/work/UpdateYTDLWorker.kt | 2 - .../drawable/baseline_network_locked_24.xml | 13 +- .../main/res/drawable/baseline_numbers_24.xml | 5 + .../res/drawable/ic_baseline_file_open_24.xml | 14 +- .../res/drawable/ic_concurrent_downloads.xml | 14 +- app/src/main/res/drawable/ic_dns.xml | 10 + app/src/main/res/drawable/ic_end.xml | 10 + app/src/main/res/drawable/ic_experiment.xml | 10 + app/src/main/res/drawable/ic_incognito.xml | 13 +- app/src/main/res/drawable/ic_ipv4.xml | 10 + app/src/main/res/drawable/ic_license.xml | 10 + app/src/main/res/drawable/ic_limitrate.xml | 10 + .../main/res/drawable/ic_music_formatcard.xml | 5 + app/src/main/res/drawable/ic_open_file.xml | 5 + app/src/main/res/drawable/ic_retries.xml | 11 + app/src/main/res/drawable/ic_start.xml | 10 + app/src/main/res/drawable/ic_update_app.xml | 13 +- .../main/res/drawable/metered_networks.xml | 13 +- .../main/res/layout/active_download_card.xml | 17 +- .../layout/active_download_card_minified.xml | 5 +- app/src/main/res/layout/activity_settings.xml | 1 + app/src/main/res/layout/activity_terminal.xml | 1 + app/src/main/res/layout/adjust_video.xml | 129 ++-- .../main/res/layout/already_exists_item.xml | 2 + .../audio_download_preferences_dialog.xml | 22 + app/src/main/res/layout/changelog_item.xml | 4 +- .../main/res/layout/command_template_item.xml | 1 - app/src/main/res/layout/cut_video_sheet.xml | 5 +- .../main/res/layout/download_bottom_sheet.xml | 4 +- .../layout/download_multiple_bottom_sheet.xml | 45 +- .../layout/extra_commands_bottom_sheet.xml | 3 + app/src/main/res/layout/format_item.xml | 2 +- .../res/layout/format_select_bottom_sheet.xml | 24 +- app/src/main/res/layout/fragment_active.xml | 3 +- .../res/layout/fragment_command_templates.xml | 1 + app/src/main/res/layout/fragment_cookies.xml | 1 + .../main/res/layout/fragment_download_log.xml | 30 +- .../res/layout/fragment_download_log_list.xml | 1 + .../fragment_download_queue_main_screen.xml | 2 +- .../res/layout/fragment_observe_sources.xml | 1 + app/src/main/res/layout/fragment_terminal.xml | 1 + app/src/main/res/layout/history_card.xml | 19 +- .../main/res/layout/history_card_multiple.xml | 19 +- .../history_item_details_bottom_sheet.xml | 5 +- .../layout/observe_sources_bottom_sheet.xml | 113 +-- app/src/main/res/layout/playlist_item.xml | 6 - app/src/main/res/layout/result_card.xml | 20 +- .../main/res/layout/result_card_details.xml | 5 + .../res/layout/search_suggestion_item.xml | 1 + app/src/main/res/layout/webview_activity.xml | 1 + app/src/main/res/menu/log_detail_menu.xml | 6 +- app/src/main/res/menu/terminal_top_menu.xml | 6 +- app/src/main/res/values/arrays.xml | 12 + app/src/main/res/values/strings.xml | 12 +- .../main/res/xml/downloading_preferences.xml | 10 +- app/src/main/res/xml/general_preferences.xml | 14 + .../main/res/xml/processing_preferences.xml | 9 + app/src/main/res/xml/root_preferences.xml | 2 +- app/src/main/res/xml/updating_preferences.xml | 15 +- .../android/en-US/changelogs/10730.txt | 23 + 147 files changed, 2645 insertions(+), 1341 deletions(-) create mode 100644 app/schemas/com.deniscerri.ytdlnis.database.DBManager/15.json delete mode 100644 app/src/main/java/com/deniscerri/ytdlnis/util/DownloadUtil.kt create mode 100644 app/src/main/res/drawable/baseline_numbers_24.xml create mode 100644 app/src/main/res/drawable/ic_dns.xml create mode 100644 app/src/main/res/drawable/ic_end.xml create mode 100644 app/src/main/res/drawable/ic_experiment.xml create mode 100644 app/src/main/res/drawable/ic_ipv4.xml create mode 100644 app/src/main/res/drawable/ic_license.xml create mode 100644 app/src/main/res/drawable/ic_limitrate.xml create mode 100644 app/src/main/res/drawable/ic_music_formatcard.xml create mode 100644 app/src/main/res/drawable/ic_open_file.xml create mode 100644 app/src/main/res/drawable/ic_retries.xml create mode 100644 app/src/main/res/drawable/ic_start.xml create mode 100644 app/src/main/res/layout/audio_download_preferences_dialog.xml create mode 100644 fastlane/metadata/android/en-US/changelogs/10730.txt diff --git a/app/build.gradle b/app/build.gradle index 5304dbf3..78502114 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { def properties = new Properties() def versionMajor = 1 def versionMinor = 7 -def versionPatch = 2 +def versionPatch = 3 def versionBuild = 0 // bump for dogfood builds, public betas, etc. def versionExt = "" diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/15.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/15.json new file mode 100644 index 00000000..fde9fc50 --- /dev/null +++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/15.json @@ -0,0 +1,724 @@ +{ + "formatVersion": 1, + "database": { + "version": 15, + "identityHash": "3f4252e3a5d5fbf3d4706a4d999055f1", + "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, `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": "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)", + "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 + } + ], + "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)", + "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" + } + ], + "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": "observeSources", + "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, `status` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyWeekDay` TEXT NOT NULL, `everyMonthDay` INTEGER NOT NULL, `everyTime` INTEGER NOT NULL, `startsTime` INTEGER NOT NULL, `startsMonth` TEXT 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, `alreadyProcessedLinks` TEXT NOT NULL)", + "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": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyNr", + "columnName": "everyNr", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "everyCategory", + "columnName": "everyCategory", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyWeekDay", + "columnName": "everyWeekDay", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyMonthDay", + "columnName": "everyMonthDay", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "everyTime", + "columnName": "everyTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startsTime", + "columnName": "startsTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startsMonth", + "columnName": "startsMonth", + "affinity": "TEXT", + "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": "alreadyProcessedLinks", + "columnName": "alreadyProcessedLinks", + "affinity": "TEXT", + "notNull": true + } + ], + "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, '3f4252e3a5d5fbf3d4706a4d999055f1')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dd009739..1257f698 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,6 @@ tools:ignore="ScopedStorage" /> - diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index c13ba5a4..89d3a283 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -13,16 +13,13 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import android.util.Log -import android.view.Gravity import android.view.View import android.view.WindowInsets import android.widget.CheckBox -import android.widget.FrameLayout import android.widget.TextView import android.widget.Toast import androidx.annotation.RequiresApi import androidx.constraintlayout.widget.ConstraintLayout -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ActivityCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.forEach diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt index 8c51f85e..749b28d3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt @@ -1,14 +1,30 @@ package com.deniscerri.ytdlnis.database import android.content.Context -import androidx.room.* -import androidx.room.migration.AutoMigrationSpec -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase -import androidx.work.impl.Migration_11_12 -import androidx.work.impl.Migration_12_13 -import com.deniscerri.ytdlnis.database.dao.* -import com.deniscerri.ytdlnis.database.models.* +import androidx.room.AutoMigration +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao +import com.deniscerri.ytdlnis.database.dao.CookieDao +import com.deniscerri.ytdlnis.database.dao.DownloadDao +import com.deniscerri.ytdlnis.database.dao.HistoryDao +import com.deniscerri.ytdlnis.database.dao.LogDao +import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao +import com.deniscerri.ytdlnis.database.dao.ResultDao +import com.deniscerri.ytdlnis.database.dao.SearchHistoryDao +import com.deniscerri.ytdlnis.database.dao.TerminalDao +import com.deniscerri.ytdlnis.database.models.CommandTemplate +import com.deniscerri.ytdlnis.database.models.CookieItem +import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.models.HistoryItem +import com.deniscerri.ytdlnis.database.models.LogItem +import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem +import com.deniscerri.ytdlnis.database.models.ResultItem +import com.deniscerri.ytdlnis.database.models.SearchHistoryItem +import com.deniscerri.ytdlnis.database.models.TemplateShortcut +import com.deniscerri.ytdlnis.database.models.TerminalItem @TypeConverters(Converters::class) @Database( @@ -24,7 +40,7 @@ import com.deniscerri.ytdlnis.database.models.* TerminalItem::class, ObserveSourcesItem::class ], - version = 14, + version = 15, autoMigrations = [ AutoMigration (from = 1, to = 2), AutoMigration (from = 2, to = 3), @@ -39,6 +55,7 @@ import com.deniscerri.ytdlnis.database.models.* AutoMigration (from = 11, to = 12), AutoMigration (from = 12, to = 13), // AutoMigration (from = 13, to = 14) MANUALLY HANDLED + AutoMigration (from = 14, to = 15), ] ) abstract class DBManager : RoomDatabase(){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt index 94888975..ed82344d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt @@ -1,6 +1,10 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.TemplateShortcut import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt index d5ad77f0..f4426698 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt @@ -1,6 +1,10 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update import com.deniscerri.ytdlnis.database.models.CookieItem import kotlinx.coroutines.flow.Flow @@ -10,7 +14,7 @@ interface CookieDao { fun getAllCookies() : List @Query("SELECT * FROM cookies WHERE url = :url LIMIT 1") - fun getByURL(url: String) : CookieItem + fun getByURL(url: String) : CookieItem? @Query("SELECT * FROM cookies ORDER BY id DESC") fun getAllCookiesFlow() : Flow> diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt index 754e7663..dd6fe092 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt @@ -1,7 +1,14 @@ package com.deniscerri.ytdlnis.database.dao import androidx.paging.PagingSource -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.RewriteQueriesToDropUnusedColumns +import androidx.room.Transaction +import androidx.room.Update +import androidx.room.Upsert import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItemSimple import com.deniscerri.ytdlnis.database.models.Format @@ -19,14 +26,14 @@ interface DownloadDao { @Query("SELECT * FROM downloads WHERE status = 'Processing'") fun getProcessingDownloads() : Flow> - @Query("SELECT COUNT(*) FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')") - fun getActiveDownloadsCountFlow() : Flow + @Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)") + fun getDownloadsCountFlow(statuses: List) : Flow @Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)") fun getDownloadsCountByStatusFlow(status : List) : Flow - @Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)") - fun getDownloadsCountByStatus(status : List) : Int + @Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)") + fun getDownloadsCountByStatus(statuses : List) : Int @Query(""" @@ -79,6 +86,9 @@ interface DownloadDao { @Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id") fun getQueuedDownloadsList() : List + @Query("SELECT id FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id") + fun getQueuedDownloadsListIDs() : List + @RewriteQueriesToDropUnusedColumns @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC") fun getCancelledDownloads() : PagingSource @@ -207,28 +217,30 @@ interface DownloadDao { fun getURLsByStatus(status: List) : List @Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)") - fun resetScheduleTimeForItems(list: List) + suspend fun resetScheduleTimeForItems(list: List) @Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)") - fun reQueueDownloadItems(list: List) + suspend fun reQueueDownloadItems(list: List) @Query("Update downloads SET status='Saved' WHERE status='Processing'") fun updateProcessingtoSavedStatus() @Transaction - fun putAtTopOfTheQueue(id: Long){ - val downloads = getQueuedDownloadsList() - val newID = downloads.first().id + suspend fun putAtTopOfTheQueue(existingIDs: List){ + val downloads = getQueuedDownloadsListIDs() + val newIDs = downloads.sortedBy { it }.take(existingIDs.size) - updateDownloadID(id, -id) - resetScheduleTimeForItems(listOf(-id)) - - downloads.reversed().dropWhile { it.id == id }.forEach { - updateDownloadID(it.id, it.id + 1) + resetScheduleTimeForItems(existingIDs) + existingIDs.forEach { updateDownloadID(it, -it) } + downloads.filter { !existingIDs.contains(it) }.reversed().forEach { + updateDownloadID(it, it + existingIDs.size) + } + + existingIDs.forEachIndexed { idx, it -> + updateDownloadID(-it, newIDs[idx]) } - updateDownloadID(-id, newID) } @Query("Update downloads set id=:newId where id=:id") - fun updateDownloadID(id: Long, newId: Long) + suspend fun updateDownloadID(id: Long, newId: Long) } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt index beb39c4b..7acbc849 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt @@ -1,6 +1,10 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update import com.deniscerri.ytdlnis.database.models.HistoryItem import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt index be21f5e6..9c8544dc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/LogDao.kt @@ -1,6 +1,11 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update import com.deniscerri.ytdlnis.database.models.LogItem import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt index 2ee57b32..767bf711 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt @@ -1,7 +1,10 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* -import com.deniscerri.ytdlnis.database.models.CookieItem +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt index d28d30ff..7726d340 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt @@ -1,6 +1,11 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction +import androidx.room.Update import com.deniscerri.ytdlnis.database.models.ResultItem import kotlinx.coroutines.flow.Flow @@ -27,6 +32,11 @@ interface ResultDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertMultiple(items: List) : List + @Transaction + suspend fun insertMultipleNoDuplicates(items: List) : List { + return insertMultiple(items.filter { getResultByURL(it.url) == null }) + } + @Update(onConflict = OnConflictStrategy.REPLACE) suspend fun update(item: ResultItem) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/SearchHistoryDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/SearchHistoryDao.kt index 94bd2056..fcaebc8c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/SearchHistoryDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/SearchHistoryDao.kt @@ -1,6 +1,9 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query import com.deniscerri.ytdlnis.database.models.SearchHistoryItem @Dao diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt index 707e87fa..f1592553 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt @@ -1,7 +1,11 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Transaction import com.deniscerri.ytdlnis.database.models.TerminalItem +import com.deniscerri.ytdlnis.util.Extensions.appendLineToLog import kotlinx.coroutines.flow.Flow @Dao @@ -28,16 +32,7 @@ interface TerminalDao { fun updateLog(line: String, id: Long){ val t = getTerminalById(id) ?: return val log = t.log ?: "" - val lines = log.split("\n") - //clean dublicate progress + add newline - var newLine = line - if (newLine.contains("[download")){ - newLine = "[download]" + line.split("[download]").last() - } - - val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" - - updateTerminalLog(l, id) + updateTerminalLog(log.appendLineToLog(line), id) } @Insert diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt index 4241b58c..8533262f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt @@ -7,8 +7,6 @@ import androidx.room.PrimaryKey import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository import kotlinx.parcelize.Parcelize import java.time.Month -import java.util.Calendar -import java.util.Date @Entity(tableName = "observeSources") @Parcelize @@ -32,6 +30,8 @@ data class ObserveSourcesItem( var endsAfterCount: Int, @ColumnInfo(defaultValue = "0") var runCount: Int, + @ColumnInfo(defaultValue = "0") + var getOnlyNewUploads: Boolean, var retryMissingDownloads: Boolean, var alreadyProcessedLinks : MutableList ) : Parcelable diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/SearchSuggestionItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/SearchSuggestionItem.kt index 0f1bf51c..acc171ca 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/SearchSuggestionItem.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/SearchSuggestionItem.kt @@ -1,10 +1,5 @@ package com.deniscerri.ytdlnis.database.models -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel - data class SearchSuggestionItem( var text: String, val type: SearchSuggestionType, diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt index 9fad348a..673c472f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt @@ -13,5 +13,6 @@ data class VideoPreferences ( var writeAutoSubs: Boolean = false, var subsLanguages: String = "en.*,.*-orig", var audioFormatIDs : ArrayList = arrayListOf(), - var removeAudio: Boolean = false + var removeAudio: Boolean = false, + var alsoDownloadAsAudio: Boolean = false ) : Parcelable diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt index b42699a9..e6cb4335 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt @@ -11,7 +11,7 @@ class CookieRepository(private val cookieDao: CookieDao) { return cookieDao.getAllCookies() } - fun getByURL(url: String) : CookieItem { + fun getByURL(url: String) : CookieItem? { return cookieDao.getByURL(url) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt index 2336f2c0..e5f8d480 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt @@ -1,15 +1,32 @@ package com.deniscerri.ytdlnis.database.repository +import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.os.Looper +import android.widget.Toast import androidx.paging.Pager import androidx.paging.PagingConfig +import androidx.preference.PreferenceManager +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkInfo +import androidx.work.WorkManager +import androidx.work.await import com.deniscerri.ytdlnis.App +import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItemSimple import com.deniscerri.ytdlnis.util.FileUtil +import com.deniscerri.ytdlnis.work.DownloadWorker import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import java.io.File +import java.util.concurrent.TimeUnit class DownloadRepository(private val downloadDao: DownloadDao) { @@ -36,12 +53,20 @@ class DownloadRepository(private val downloadDao: DownloadDao) { pagingSourceFactory = {downloadDao.getSavedDownloads()} ) - val activeDownloadsCount : Flow = downloadDao.getActiveDownloadsCountFlow() + val activeDownloadsCount : Flow = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.ActivePaused, Status.PausedReQueued).toListString()) + val queuedDownloadsCount : Flow = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Queued, Status.QueuedPaused).toListString()) + val cancelledDownloadsCount : Flow = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Cancelled).toListString()) + val erroredDownloadsCount : Flow = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Error).toListString()) + val savedDownloadsCount : Flow = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Saved).toListString()) enum class Status { Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing } + private fun List.toListString() : List{ + return this.map { it.toString() } + } + suspend fun insert(item: DownloadItem) : Long { return downloadDao.insert(item) } @@ -162,4 +187,53 @@ class DownloadRepository(private val downloadDao: DownloadDao) { downloadDao.removeAllLogID() } + + @SuppressLint("RestrictedApi") + suspend fun startDownloadWorker(queuedItems: List, context: Context, inputData: Data.Builder = Data.Builder()) { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true) + val workManager = WorkManager.getInstance(context) + + val currentWork = workManager.getWorkInfosByTag("download").await() + if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){ + + val currentTime = System.currentTimeMillis() + var delay = 0L + if (queuedItems.isNotEmpty()){ + delay = if (queuedItems[0].downloadStartTime != 0L){ + queuedItems[0].downloadStartTime.minus(currentTime) + } else 0 + if (delay <= 60000L) delay = 0L + } + + + val workConstraints = Constraints.Builder() + if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED) + + val workRequest = OneTimeWorkRequestBuilder() + .addTag("download") + .setConstraints(workConstraints.build()) + .setInitialDelay(delay, TimeUnit.MILLISECONDS) + .setInputData(inputData.build()) + + queuedItems.forEach { + workRequest.addTag(it.id.toString()) + } + + workManager.enqueueUniqueWork( + System.currentTimeMillis().toString(), + ExistingWorkPolicy.REPLACE, + workRequest.build() + ) + + } + + val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered + if (!allowMeteredNetworks && isCurrentNetworkMetered){ + Looper.prepare().run { + Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show() + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt index a90d876b..9f13a414 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt @@ -1,11 +1,11 @@ package com.deniscerri.ytdlnis.database.repository +import com.deniscerri.ytdlnis.database.DBManager.SORTING import com.deniscerri.ytdlnis.database.dao.HistoryDao import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.util.FileUtil import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest -import com.deniscerri.ytdlnis.database.DBManager.SORTING class HistoryRepository(private val historyDao: HistoryDao) { val items : Flow> = historyDao.getAllHistory() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt index 90366ebe..f5712b7c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt @@ -2,9 +2,8 @@ package com.deniscerri.ytdlnis.database.repository import com.deniscerri.ytdlnis.database.dao.LogDao import com.deniscerri.ytdlnis.database.models.LogItem +import com.deniscerri.ytdlnis.util.Extensions.appendLineToLog import kotlinx.coroutines.flow.Flow -import java.util.regex.MatchResult -import java.util.regex.Pattern class LogRepository(private val logDao: LogDao) { val items : Flow> = logDao.getAllLogsFlow() @@ -43,16 +42,7 @@ class LogRepository(private val logDao: LogDao) { runCatching { val item = getItem(id) ?: return val log = item.content ?: "" - val lines = log.split("\n") - //clean dublicate progress + add newline - var newLine = line - if (newLine.contains("[download")){ - newLine = "[download]" + line.split("[download]").last() - } - - val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" - item.content = l - + item.content = log.appendLineToLog(line) //item.content += "\n$line" logDao.update(item) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt index 9ee8bf3d..2ffa2dff 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt @@ -1,8 +1,13 @@ package com.deniscerri.ytdlnis.database.repository +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem +import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver import kotlinx.coroutines.flow.Flow class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) { @@ -57,4 +62,16 @@ class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) observeSourcesDao.update(item) } + fun cancelObservationTaskByID(context: Context, id: Long){ + val alarmManager = context.getSystemService(AlarmManager::class.java) + alarmManager.cancel( + PendingIntent.getBroadcast( + context, + id.toInt(), + Intent(context, ObserveAlarmReceiver::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt index afe8b8ee..0368e712 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt @@ -1,17 +1,24 @@ package com.deniscerri.ytdlnis.database.repository import android.content.Context +import android.util.Patterns import com.deniscerri.ytdlnis.database.dao.ResultDao +import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.util.InfoUtil import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import java.util.regex.Pattern class ResultRepository(private val resultDao: ResultDao, private val context: Context) { private val tag: String = "ResultRepository" val allResults : Flow> = resultDao.getResults() var itemCount = MutableStateFlow(-1) + enum class SourceType { + YOUTUBE_VIDEO, YOUTUBE_PLAYLIST, SEARCH_QUERY, YT_DLP + } + suspend fun insert(it: ResultItem){ resultDao.insert(it) } @@ -28,46 +35,52 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co resultDao.insertMultiple(items) } - suspend fun search(inputQuery: String, resetResults: Boolean) : ArrayList{ + suspend fun search(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : ArrayList{ val infoUtil = InfoUtil(context) if (resetResults) deleteAll() val res = infoUtil.search(inputQuery) itemCount.value = res.size - val ids = resultDao.insertMultiple(res) - ids.forEachIndexed { index, id -> - res[index]?.id = id + if (addToResults){ + val ids = resultDao.insertMultiple(res) + ids.forEachIndexed { index, id -> + res[index].id = id + } } return res } - suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean) : ArrayList{ + suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : ArrayList{ val infoUtil = InfoUtil(context) - val v = infoUtil.getYoutubeVideo(inputQuery) + val v = infoUtil.getYoutubeVideo(inputQuery) ?: return arrayListOf() if (resetResults) { deleteAll() itemCount.value = v.size }else{ - v.filter { it?.playlistTitle.isNullOrBlank() }.forEach { it?.playlistTitle = "ytdlnis-Search" } + v.filter { it.playlistTitle.isBlank() }.forEach { it.playlistTitle = "ytdlnis-Search" } } - val ids = resultDao.insertMultiple(v) - ids.forEachIndexed { index, id -> - v[index]?.id = id + if (addToResults){ + val ids = resultDao.insertMultiple(v) + ids.forEachIndexed { index, id -> + v[index].id = id + } } return ArrayList(v) } - suspend fun getPlaylist(inputQuery: String, resetResults: Boolean) : ArrayList{ + suspend fun getPlaylist(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : ArrayList{ val query = inputQuery.split("list=".toRegex()).dropLastWhile { it.isEmpty() } .toTypedArray()[1].split("&").first() var nextPageToken = "" if (resetResults) deleteAll() val infoUtil = InfoUtil(context) - val items : ArrayList = arrayListOf() + val items = arrayListOf() do { - val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "") - val ids = resultDao.insertMultiple(tmp.videos.toList()) - ids.forEachIndexed { index, id -> - tmp.videos[index]?.id = id + val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0].playlistTitle else "") + if (addToResults){ + val ids = resultDao.insertMultiple(tmp.videos.toList()) + ids.forEachIndexed { index, id -> + tmp.videos[index].id = id + } } items.addAll(tmp.videos) val tmpToken = tmp.nextPageToken @@ -79,19 +92,23 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co return items } - suspend fun getDefault(inputQuery: String, resetResults: Boolean) : ArrayList { + suspend fun getDefault(inputQuery: String, resetResults: Boolean, addToResults: Boolean, singleItem: Boolean = false) : ArrayList { val infoUtil = InfoUtil(context) - val items = infoUtil.getFromYTDL(inputQuery) + val items = infoUtil.getFromYTDL(inputQuery, singleItem) if (resetResults) { deleteAll() itemCount.value = items.size }else{ - items.filter { it?.playlistTitle.isNullOrBlank() }.forEach { it!!.playlistTitle = "ytdlnis-Search" } + items.filter { it.playlistTitle.isNullOrBlank() }.forEach { it.playlistTitle = "ytdlnis-Search" } } - val ids = resultDao.insertMultiple(items.toList()) - ids.forEachIndexed { index, id -> - items[index]?.id = id + + if (addToResults){ + val ids = resultDao.insertMultiple(items.toList()) + ids.forEachIndexed { index, id -> + items[index].id = id + } } + return items } @@ -111,4 +128,59 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co fun getItemByURL(url: String): ResultItem? { return resultDao.getResultByURL(url) } + + suspend fun getResultsFromSource(inputQuery: String, resetResults: Boolean, addToResults: Boolean = true, singleItem: Boolean = false) : ArrayList { + return when(getQueryType(inputQuery)){ + SourceType.YOUTUBE_VIDEO -> { + getYoutubeVideo(inputQuery, resetResults, addToResults) + } + SourceType.YOUTUBE_PLAYLIST -> { + if (singleItem){ + getDefault(inputQuery, resetResults, addToResults, true) + }else{ + getPlaylist(inputQuery, resetResults, addToResults) + } + } + SourceType.SEARCH_QUERY -> { + search(inputQuery, resetResults, addToResults) + } + SourceType.YT_DLP -> { + getDefault(inputQuery, resetResults, addToResults) + } + } + + } + + fun getQueryType(inputQuery: String) : SourceType { + var type = SourceType.SEARCH_QUERY + val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)") + val m = p.matcher(inputQuery) + if (m.find()) { + type = SourceType.YOUTUBE_VIDEO + if (inputQuery.contains("playlist?list=")) { + type = SourceType.YOUTUBE_PLAYLIST + } + } else if (Patterns.WEB_URL.matcher(inputQuery).matches()) { + type = SourceType.YT_DLP + } + return type + } + + suspend fun updateDownloadItem( + downloadItem: DownloadItem + ) : DownloadItem? { + if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){ + runCatching { + val info = getResultsFromSource(downloadItem.url, resetResults = false, addToResults = false, singleItem = true).first() + if (downloadItem.title.isEmpty()) downloadItem.title = info.title + if (downloadItem.author.isEmpty()) downloadItem.author = info.author + downloadItem.duration = info.duration + downloadItem.website = info.website + if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb + return downloadItem + } + } + return null + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt index dde43c31..b1784046 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt @@ -17,12 +17,11 @@ import com.deniscerri.ytdlnis.BuildConfig import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.models.CookieItem import com.deniscerri.ytdlnis.database.repository.CookieRepository -import com.deniscerri.ytdlnis.ui.more.CookiesFragment import com.deniscerri.ytdlnis.ui.more.WebViewActivity -import com.deniscerri.ytdlnis.util.FileUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File +import java.net.URL import java.util.Date class CookieViewModel(private val application: Application) : AndroidViewModel(application) { @@ -44,7 +43,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a return repository.getAll() } - fun getByURL(url: String) : CookieItem { + fun getByURL(url: String) : CookieItem? { return repository.getByURL(url) } @@ -75,7 +74,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a } @SuppressLint("SdCardPath") - fun getCookiesFromDB() : Result = kotlin.runCatching { + fun getCookiesFromDB(url: String) : Result = kotlin.runCatching { CookieManager.getInstance().run { if (!hasCookies()) throw Exception("There is no cookies in the database!") flush() @@ -83,30 +82,35 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a val dbPath = File("/data/data/${BuildConfig.APPLICATION_ID}/").walkTopDown().find { it.name == "Cookies" } ?: throw Exception("Cookies File not found!") - SQLiteDatabase.openDatabase( + val db = SQLiteDatabase.openDatabase( dbPath.absolutePath, null, OPEN_READONLY - ).run { - val projection = arrayOf( - CookieObject.HOST, - CookieObject.EXPIRY, - CookieObject.PATH, - CookieObject.NAME, - CookieObject.VALUE, - CookieObject.SECURE - ) - val cookieList = mutableListOf() - query( - "cookies", projection, null, null, null, null, null - ).run { - while (moveToNext()) { - val expiry = getLong(getColumnIndexOrThrow(CookieObject.EXPIRY)) - val name = getString(getColumnIndexOrThrow(CookieObject.NAME)) - val value = getString(getColumnIndexOrThrow(CookieObject.VALUE)) - val path = getString(getColumnIndexOrThrow(CookieObject.PATH)) - val secure = getLong(getColumnIndexOrThrow(CookieObject.SECURE)) == 1L - val hostKey = getString(getColumnIndexOrThrow(CookieObject.HOST)) + ) - val host = if (hostKey[0] != '.') ".$hostKey" else hostKey + val projection = arrayOf( + CookieObject.HOST, + CookieObject.EXPIRY, + CookieObject.PATH, + CookieObject.NAME, + CookieObject.VALUE, + CookieObject.SECURE + ) + val cookieList = mutableListOf() + db.query( + "cookies", projection, null, null, null, null, null + ).run { + while (moveToNext()) { + val expiry = getLong(getColumnIndexOrThrow(CookieObject.EXPIRY)) + val name = getString(getColumnIndexOrThrow(CookieObject.NAME)) + val value = getString(getColumnIndexOrThrow(CookieObject.VALUE)) + val path = getString(getColumnIndexOrThrow(CookieObject.PATH)) + val secure = getLong(getColumnIndexOrThrow(CookieObject.SECURE)) == 1L + val hostKey = getString(getColumnIndexOrThrow(CookieObject.HOST)) + + + val host = if (hostKey[0] != '.') ".$hostKey" else hostKey + + val requiredHost = URL(url).host + if(host.contains(requiredHost)){ cookieList.add( WebViewActivity.CookieItem( domain = host, @@ -118,13 +122,13 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a ) ) } - close() } close() - cookieList.fold(StringBuilder("")) { acc, cookie -> - acc.append(cookie.toNetscapeFormat()).append("\n") - }.toString() } + db.close() + cookieList.fold(StringBuilder("")) { acc, cookie -> + acc.append(cookie.toNetscapeFormat()).append("\n") + }.toString() } fun updateCookiesFile() = viewModelScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt index 94bf4364..c7d3d2f1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt @@ -1,36 +1,25 @@ package com.deniscerri.ytdlnis.database.viewmodel -import android.annotation.SuppressLint import android.app.Application -import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration import android.content.res.Resources -import android.net.ConnectivityManager -import android.os.Looper import android.util.DisplayMetrics -import android.widget.Toast -import androidx.compose.animation.core.updateTransition import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.preference.PreferenceManager -import androidx.work.Constraints import androidx.work.Data import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkInfo import androidx.work.WorkManager -import androidx.work.await import com.deniscerri.ytdlnis.App import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.dao.DownloadDao -import com.deniscerri.ytdlnis.database.dao.ResultDao import com.deniscerri.ytdlnis.database.models.AudioPreferences import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.DownloadItem @@ -41,12 +30,11 @@ import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.VideoPreferences import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.HistoryRepository +import com.deniscerri.ytdlnis.database.repository.ResultRepository import com.deniscerri.ytdlnis.ui.downloadcard.FormatTuple -import com.deniscerri.ytdlnis.util.DownloadUtil import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.work.AlarmScheduler -import com.deniscerri.ytdlnis.work.DownloadWorker import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope @@ -59,7 +47,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.io.File import java.util.Locale -import java.util.concurrent.TimeUnit class DownloadViewModel(private val application: Application) : AndroidViewModel(application) { @@ -72,11 +59,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val queuedDownloads : Flow> val activeDownloads : Flow> val processingDownloads : Flow> - val activeDownloadsCount : Flow val cancelledDownloads : Flow> val erroredDownloads : Flow> val savedDownloads : Flow> + val activeDownloadsCount : Flow + val queuedDownloadsCount : Flow + val cancelledDownloadsCount : Flow + val erroredDownloadsCount : Flow + val savedDownloadsCount : Flow + private var bestVideoFormat : Format private var bestAudioFormat : Format private var defaultVideoFormats : MutableList @@ -94,6 +86,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel private var audioCodec: String? private val dao: DownloadDao private val historyRepository: HistoryRepository + private val resultRepository: ResultRepository data class AlreadyExistsUIState( var historyItems: MutableList, @@ -120,15 +113,21 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel dao = dbManager.downloadDao repository = DownloadRepository(dao) historyRepository = HistoryRepository(dbManager.historyDao) + resultRepository = ResultRepository(dbManager.resultDao, application) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) commandTemplateDao = DBManager.getInstance(application).commandTemplateDao infoUtil = InfoUtil(application) + activeDownloadsCount = repository.activeDownloadsCount + queuedDownloadsCount = repository.queuedDownloadsCount + cancelledDownloadsCount = repository.cancelledDownloadsCount + erroredDownloadsCount = repository.erroredDownloadsCount + savedDownloadsCount = repository.savedDownloadsCount + allDownloads = repository.allDownloads.flow queuedDownloads = repository.queuedDownloads.flow activeDownloads = repository.activeDownloads processingDownloads = repository.processingDownloads - activeDownloadsCount = repository.activeDownloadsCount savedDownloads = repository.savedDownloads.flow cancelledDownloads = repository.cancelledDownloads.flow erroredDownloads = repository.erroredDownloads.flow @@ -663,17 +662,17 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel suspend fun resetScheduleTimeForItemsAndStartDownload(items: List) = CoroutineScope(Dispatchers.IO).launch { dbManager.downloadDao.resetScheduleTimeForItems(items) - DownloadUtil.startDownloadWorker(emptyList(), application) + repository.startDownloadWorker(emptyList(), application) } - suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{ - dbManager.downloadDao.putAtTopOfTheQueue(id) - DownloadUtil.startDownloadWorker(emptyList(), application) + suspend fun putAtTopOfQueue(ids: List) = CoroutineScope(Dispatchers.IO).launch{ + dbManager.downloadDao.putAtTopOfTheQueue(ids) + repository.startDownloadWorker(emptyList(), application) } suspend fun reQueueDownloadItems(items: List) = CoroutineScope(Dispatchers.IO).launch { dbManager.downloadDao.reQueueDownloadItems(items) - DownloadUtil.startDownloadWorker(emptyList(), application) + repository.startDownloadWorker(emptyList(), application) } suspend fun queueDownloads(items: List, ign : Boolean = false) : Pair, List> { @@ -761,19 +760,27 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel if (queuedItems.isNotEmpty()){ if (!useScheduler || alarmScheduler.isDuringTheScheduledTime()){ - DownloadUtil.startDownloadWorker(queuedItems, context) + repository.startDownloadWorker(queuedItems, context) if(!useScheduler){ queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach { - try{ - updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao) - }catch (ignored: Exception){} + CoroutineScope(Dispatchers.IO).launch { + runCatching { + resultRepository.updateDownloadItem(it)?.apply { + repository.updateWithoutUpsert(this) + } + } + } } }else{ queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach { - try{ - updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao) - }catch (ignored: Exception){} + CoroutineScope(Dispatchers.IO).launch { + runCatching { + resultRepository.updateDownloadItem(it)?.apply { + repository.updateWithoutUpsert(this) + } + } + } } } } @@ -819,7 +826,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel items.forEachIndexed { index, i -> i.format = selectedFormats[index].format if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) } - updateDownload(i) + repository.update(i) } return items.map { it.format.filesize } @@ -829,7 +836,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val items = repository.getProcessingDownloads() items.forEach { i -> i.format = format - updateDownload(i) + repository.update(i) } } @@ -837,7 +844,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val items = repository.getProcessingDownloads() items.forEach { i -> i.downloadPath = path - updateDownload(i) + repository.update(i) } } @@ -866,7 +873,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel dbManager.resultDao.update(this) } } - updateDownload(i) + repository.update(i) } } @@ -874,7 +881,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel dao.getProcessingDownloadsList().apply { this.forEach { it.status = DownloadRepository.Status.Saved.toString() - updateDownload(it) + repository.update(it) } val ids = this.map { it.id } @@ -900,7 +907,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel repository.getProcessingDownloads().apply { val new = switchDownloadType(this, newType) new.forEach { - updateDownload(it) + repository.update(it) } } } @@ -933,28 +940,5 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel return dao.getURLsByStatus(list.map { it.toString() }) } - private fun updateDownloadItem( - downloadItem: DownloadItem, - infoUtil: InfoUtil, - dao: DownloadDao, - resultDao: ResultDao - ) : Boolean { - var wasQuickDownloaded = false - if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){ - runCatching { - val info = infoUtil.getMissingInfo(downloadItem.url) - if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString() - if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString() - downloadItem.duration = info?.duration.toString() - downloadItem.website = info?.website.toString() - if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString() - runBlocking { - wasQuickDownloaded = resultDao.getCountInt() == 0 - repository.updateWithoutUpsert(downloadItem) - } - } - } - return wasQuickDownloaded - } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt index cb8646c2..93adf859 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt @@ -8,9 +8,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.deniscerri.ytdlnis.database.DBManager +import com.deniscerri.ytdlnis.database.DBManager.SORTING import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.repository.HistoryRepository -import com.deniscerri.ytdlnis.database.DBManager.SORTING import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt index 00eb6c46..f7ff0d82 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt @@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.database.viewmodel import android.app.AlarmManager import android.app.Application import android.app.PendingIntent -import android.content.Context import android.content.Intent import android.content.SharedPreferences import androidx.lifecycle.AndroidViewModel @@ -12,12 +11,9 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.database.DBManager -import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver -import com.deniscerri.ytdlnis.util.DownloadUtil -import com.deniscerri.ytdlnis.util.Extensions.closestValue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.time.Month @@ -63,13 +59,13 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie } fun delete(item: ObserveSourcesItem) = viewModelScope.launch(Dispatchers.IO) { - runCatching { DownloadUtil.cancelObservationTaskByID(application, item.id) } + runCatching { repository.cancelObservationTaskByID(application, item.id) } repository.delete(item) } fun deleteAll() = viewModelScope.launch(Dispatchers.IO) { getAll().forEach { - runCatching { DownloadUtil.cancelObservationTaskByID(application, it.id) } + runCatching { repository.cancelObservationTaskByID(application, it.id) } } repository.deleteAll() @@ -94,7 +90,7 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE)) - DownloadUtil.cancelObservationTaskByID(application, id) + repository.cancelObservationTaskByID(application, id) val intent = Intent(application, ObserveAlarmReceiver::class.java) intent.putExtra("id", id) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt index c24f2dcf..61c8eac6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt @@ -5,7 +5,6 @@ import android.app.ActivityManager.RunningAppProcessInfo import android.app.Application import android.content.SharedPreferences import android.util.Log -import android.util.Patterns import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData @@ -21,14 +20,14 @@ import com.deniscerri.ytdlnis.database.repository.ResultRepository import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.NotificationUtil +import com.yausername.youtubedl_android.YoutubeDLException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.regex.Pattern +import java.util.concurrent.CancellationException class ResultViewModel(private val application: Application) : AndroidViewModel(application) { @@ -53,6 +52,18 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a errorMessage = null, actions = null )) + + + val updatingData: MutableStateFlow = MutableStateFlow(false) + var updateResultData: MutableStateFlow?> = MutableStateFlow(null) + private var updateResultDataJob : Job? = null + + val updatingFormats: MutableStateFlow = MutableStateFlow(false) + var updateFormatsResultData: MutableStateFlow?> = MutableStateFlow(null) + private var updateFormatsResultDataJob: Job? = null + + var parsingQueries: Job? = null + private val sharedPreferences: SharedPreferences init { @@ -86,41 +97,45 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a deleteAll() } } - suspend fun parseQueries(inputQueries: List) : List { - if (inputQueries.size > 1){ - repository.itemCount.value = inputQueries.size - } - val resetResults = inputQueries.size == 1 - uiState.update {it.copy(processing = true, errorMessage = null, actions = null)} - return withContext(Dispatchers.IO){ - val res = mutableListOf() - inputQueries.forEach { inputQuery -> - val type = getQueryType(inputQuery) - try { - when (type) { - "Search" -> res.addAll(repository.search(inputQuery, resetResults)) - "YT_Video" -> res.addAll(repository.getYoutubeVideo(inputQuery, resetResults)) - "YT_Playlist" -> res.addAll(repository.getPlaylist(inputQuery, resetResults)) - else -> res.addAll(repository.getDefault(inputQuery, resetResults)) - } - } catch (e: Exception) { - if (updateResultDataJob?.isCancelled == false){ - uiState.update {it.copy( - processing = false, - errorMessage = Pair(R.string.no_results, e.message.toString()), - actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG)) - )} - Log.e(tag, e.toString()) - } + fun cancelParsingQueries(){ + parsingQueries?.cancel(CancellationException()) + uiState.update {it.copy(processing = false)} + } + suspend fun parseQueries(inputQueries: List, onResult: (list: List) -> Unit) { + if (parsingQueries == null || parsingQueries?.isCancelled == true || parsingQueries?.isCompleted == true) { + parsingQueries = viewModelScope.launch(Dispatchers.IO) { + if (inputQueries.size > 1){ + repository.itemCount.value = inputQueries.size } + val resetResults = inputQueries.size == 1 + + uiState.update {it.copy(processing = true, errorMessage = null, actions = null)} + val res = mutableListOf() + inputQueries.forEach { inputQuery -> + try { + res.addAll(repository.getResultsFromSource(inputQuery, resetResults)) + } catch (e: Exception) { + if (updateResultDataJob?.isCancelled == false || e is YoutubeDLException){ + uiState.update {it.copy( + processing = false, + errorMessage = Pair(R.string.no_results, e.message.toString()), + actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG)) + )} + Log.e(tag, e.toString()) + } + + } + } + if (!isForegrounded() && inputQueries.size > 1){ + notificationUtil.showQueriesFinished() + } + uiState.update {it.copy(processing = false)} + onResult(res) } - if (!isForegrounded() && inputQueries.size > 1){ - notificationUtil.showQueriesFinished() - } - uiState.update {it.copy(processing = false)} - res + }else{ + onResult(listOf()) } } @@ -133,20 +148,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a }.getOrElse { true } } - private fun getQueryType(inputQuery: String) : String { - var type = "Search" - val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)") - val m = p.matcher(inputQuery) - if (m.find()) { - type = "YT_Video" - if (inputQuery.contains("playlist?list=")) { - type = "YT_Playlist" - } - } else if (Patterns.WEB_URL.matcher(inputQuery).matches()) { - type = "Default" - } - return type - } + fun insert(items: ArrayList) = viewModelScope.launch(Dispatchers.IO){ items.forEach { @@ -196,21 +198,19 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a } } - val updatingData: MutableStateFlow = MutableStateFlow(false) - var updateResultData: MutableStateFlow?> = MutableStateFlow(null) - private var updateResultDataJob : Job? = null - - val updatingFormats: MutableStateFlow = MutableStateFlow(false) - var updateFormatsResultData: MutableStateFlow?> = MutableStateFlow(null) - private var updateFormatsResultDataJob: Job? = null suspend fun updateItemData(res: ResultItem){ if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){ updateResultDataJob = viewModelScope.launch(Dispatchers.IO) { updatingData.emit(true) - val result = parseQueries(listOf(res.url)) - updatingData.emit(false) - updateResultData.emit(result) + updatingData.value = true + parseQueries(listOf(res.url)){ result -> + viewModelScope.launch(Dispatchers.IO){ + updatingData.emit(false) + updatingData.value = false + updateResultData.emit(result) + } + } } updateResultDataJob?.start() @@ -218,6 +218,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a if (it != null){ viewModelScope.launch(Dispatchers.IO) { updatingData.emit(false) + updatingData.value = false updateResultData.emit(mutableListOf()) } } @@ -226,13 +227,14 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a } suspend fun cancelUpdateItemData(){ - updateResultDataJob?.cancel() + updateResultDataJob?.cancel(CancellationException()) updatingData.emit(false) + updatingData.value = false updateResultData.emit(null) } suspend fun cancelUpdateFormatsItemData(){ - updateFormatsResultDataJob?.cancel() + updateFormatsResultDataJob?.cancel(CancellationException()) updatingFormats.emit(false) updateFormatsResultData.emit(null) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt index d0e2a33f..1731ff3e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt @@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.util.Log import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.util.NotificationUtil diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt index 84ef1d1c..e81eeb9a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt @@ -8,7 +8,6 @@ import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager -import com.deniscerri.ytdlnis.work.DownloadWorker import com.deniscerri.ytdlnis.work.ObserveSourceWorker import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt index 4d2a778a..5906fa12 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt @@ -10,9 +10,11 @@ import com.yausername.youtubedl_android.YoutubeDL import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class PauseDownloadNotificationReceiver : BroadcastReceiver() { override fun onReceive(c: Context, intent: Intent) { + val result = goAsync() val id = intent.getIntExtra("itemID", 0) if (id != 0) { runCatching { @@ -20,13 +22,17 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() { val notificationUtil = NotificationUtil(c) notificationUtil.cancelDownloadNotification(id) YoutubeDL.getInstance().destroyProcessById(id.toString()) - notificationUtil.createResumeDownload(id, title) val dbManager = DBManager.getInstance(c) CoroutineScope(Dispatchers.IO).launch{ - runCatching { + try { val item = dbManager.downloadDao.getDownloadById(id.toLong()) item.status = DownloadRepository.Status.ActivePaused.toString() dbManager.downloadDao.update(item) + }finally { + withContext(Dispatchers.Main){ + notificationUtil.createResumeDownload(id, title) + result.finish() + } } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt index 612e7480..308f9b08 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt @@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.util.Log import androidx.work.Constraints import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt index 52b07b00..2d26548a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -1,6 +1,5 @@ package com.deniscerri.ytdlnis.ui -import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.Activity import android.content.* @@ -15,14 +14,12 @@ import android.util.Log import android.util.Patterns import android.view.* import android.view.View.* -import android.view.animation.Interpolator import android.widget.* import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.constraintlayout.widget.ConstraintLayout import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.os.bundleOf import androidx.core.view.children import androidx.core.view.forEach import androidx.core.view.isVisible @@ -45,7 +42,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter import com.deniscerri.ytdlnis.ui.adapter.SearchSuggestionsAdapter -import com.deniscerri.ytdlnis.util.Extensions import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.ThemeUtil @@ -220,7 +216,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti if (inputQueries != null) { resultViewModel.deleteAll() lifecycleScope.launch(Dispatchers.IO){ - resultViewModel.parseQueries(inputQueries!!) + resultViewModel.parseQueries(inputQueries!!){} inputQueries = null } } @@ -321,11 +317,25 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti requireView().post { checkClipboard().apply { - clipboardFab?.isVisible = this != null - clipboardFab?.setOnClickListener { - searchView!!.setText(this) - clipboardFab?.isVisible = false - initSearch(searchView!!) + this?.apply { + clipboardFab?.isVisible = this.isNotEmpty() + clipboardFab?.setOnClickListener { + if (this.size == 1){ + searchView?.setText(this.first()) + clipboardFab?.isVisible = false + initSearch(searchView!!) + }else{ + searchBar?.performClick() + lifecycleScope.launch { + withContext(Dispatchers.IO){ + delay(500) + } + this@apply.forEach { + onSearchSuggestionAdd(it) + } + } + } + } } lifecycleScope.launch { @@ -435,6 +445,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti searchBar!!.setOnMenuItemClickListener { m: MenuItem -> when (m.itemId) { R.id.delete_results -> { + resultViewModel.cancelParsingQueries() resultViewModel.getTrending() selectedObjects = ArrayList() searchBar!!.setText("") @@ -489,7 +500,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti val url = checkClipboard() url?.apply { - combinedList.add(0, SearchSuggestionItem(this, SearchSuggestionType.CLIPBOARD)) + combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD)) } @@ -614,10 +625,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti } }else{ - resultViewModel.parseQueries(queryList) + resultViewModel.parseQueries(queryList){} } }else{ - resultViewModel.parseQueries(queryList) + resultViewModel.parseQueries(queryList){} } } } @@ -830,12 +841,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti } - private fun checkClipboard(): String?{ + private fun checkClipboard(): List?{ return kotlin.runCatching { val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager val clip = clipboard.primaryClip!!.getItemAt(0).text - val matchesURL = Patterns.WEB_URL.matcher(clip.toString()).matches() - return if (matchesURL) clip.toString() else null + return clip.split("\r\n").filter { Patterns.WEB_URL.matcher(it).matches() } }.getOrNull() } @@ -850,9 +860,17 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti } override fun onSearchSuggestionClick(text: String) { - clipboardFab?.isVisible = false - searchView!!.setText(text) - initSearch(searchView!!) + val res = text.split("\n") + if (res.size == 1){ + clipboardFab?.isVisible = false + searchView!!.setText(text) + initSearch(searchView!!) + }else{ + res.forEach { + onSearchSuggestionAdd(it) + } + } + } override fun onSearchSuggestionAdd(text: String) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt index 8b7ed9c4..84ed7bfc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt @@ -10,7 +10,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.LinearLayout import android.widget.TextView import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager @@ -22,9 +21,7 @@ import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel -import com.deniscerri.ytdlnis.util.Extensions import com.deniscerri.ytdlnis.util.Extensions.dp -import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView @@ -63,7 +60,6 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - card.popup() card.tag = "${item!!.id}##card" val uiHandler = Handler(Looper.getMainLooper()) val thumbnail = card.findViewById(R.id.image_view) @@ -98,7 +94,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: // Author ---------------------------------- val author = card.findViewById(R.id.author) var info = item.author - if (item.duration.isNotEmpty()) { + if (item.duration.isNotEmpty() && item.duration != "-1") { if (item.author.isNotEmpty()) info += " • " info += item.duration } @@ -153,6 +149,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val value = animation.animatedValue as Int pauseButton.cornerRadius = value pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white) + pauseButton.contentDescription = activity.getString(R.string.pause) pauseButton.isEnabled = true pauseButton.tag = ActiveDownloadAction.Pause } @@ -171,6 +168,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val value = animation.animatedValue as Int pauseButton.cornerRadius = value pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) + pauseButton.contentDescription = activity.getString(R.string.start) pauseButton.tag = ActiveDownloadAction.Resume pauseButton.isEnabled = true } @@ -180,6 +178,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: DownloadRepository.Status.PausedReQueued -> { progressBar.isIndeterminate = true pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.ic_refresh) + pauseButton.contentDescription = activity.getString(R.string.please_wait) pauseButton.tag = null pauseButton.isEnabled = false cancelButton.visibility = View.GONE diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt index 98a43063..3f9668dd 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt @@ -25,7 +25,6 @@ import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView import com.google.android.material.progressindicator.LinearProgressIndicator import com.squareup.picasso.Picasso -import java.lang.StringBuilder class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder( DIFF_CALLBACK @@ -122,11 +121,13 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac if (item.status == DownloadRepository.Status.ActivePaused.toString()){ progressBar.isIndeterminate = false pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) + pauseButton.contentDescription = activity.getString(R.string.start) pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume cancelButton.visibility = View.VISIBLE }else{ progressBar.isIndeterminate = true pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white) + pauseButton.contentDescription = activity.getString(R.string.pause) cancelButton.visibility = View.GONE pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt index 684929c6..adee2e73 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button -import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil @@ -13,16 +12,10 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem -import com.deniscerri.ytdlnis.database.models.HistoryItem -import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel -import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog import com.deniscerri.ytdlnis.util.Extensions.popup -import com.deniscerri.ytdlnis.util.UiUtil -import com.google.android.material.floatingactionbutton.FloatingActionButton import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter, AlreadyExistsAdapter.ViewHolder>(AsyncDifferConfig.Builder( DIFF_CALLBACK diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt index 1e1cc60a..01799569 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ConfigureMultipleDownloadsAdapter.kt @@ -130,12 +130,15 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener when(item.type) { DownloadViewModel.Type.audio -> { btn.setIconResource(R.drawable.ic_music) + btn.contentDescription = activity.getString(R.string.audio) } DownloadViewModel.Type.video -> { btn.setIconResource(R.drawable.ic_video) + btn.contentDescription = activity.getString(R.string.video) } else -> { btn.setIconResource(R.drawable.ic_terminal) + btn.contentDescription = activity.getString(R.string.command) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/DownloadLogsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/DownloadLogsAdapter.kt index e50894a6..89be5480 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/DownloadLogsAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/DownloadLogsAdapter.kt @@ -63,9 +63,18 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac val downloadTypeIcon = card.findViewById(R.id.download_type) when(item.downloadType){ - DownloadViewModel.Type.audio -> downloadTypeIcon.setIconResource(R.drawable.ic_music) - DownloadViewModel.Type.video -> downloadTypeIcon.setIconResource(R.drawable.ic_video) - DownloadViewModel.Type.command -> downloadTypeIcon.setIconResource(R.drawable.ic_terminal) + DownloadViewModel.Type.audio -> { + downloadTypeIcon.setIconResource(R.drawable.ic_music) + downloadTypeIcon.contentDescription = activity.getString(R.string.audio) + } + DownloadViewModel.Type.video -> { + downloadTypeIcon.setIconResource(R.drawable.ic_video) + downloadTypeIcon.contentDescription = activity.getString(R.string.video) + } + DownloadViewModel.Type.command -> { + downloadTypeIcon.setIconResource(R.drawable.ic_terminal) + downloadTypeIcon.contentDescription = activity.getString(R.string.command) + } else -> {} } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt index d291ef40..f8206899 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/GenericDownloadAdapter.kt @@ -118,11 +118,21 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: if (actionButton.hasOnClickListeners()) actionButton.setOnClickListener(null) when(item.status){ - DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh) - DownloadRepository.Status.Saved.toString() -> actionButton.setIconResource(R.drawable.ic_downloads) - DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24) + DownloadRepository.Status.Cancelled.toString() -> { + actionButton.setIconResource(R.drawable.ic_refresh) + actionButton.contentDescription = activity.getString(R.string.download) + } + DownloadRepository.Status.Saved.toString() -> { + actionButton.setIconResource(R.drawable.ic_downloads) + actionButton.contentDescription = activity.getString(R.string.download) + } + DownloadRepository.Status.Queued.toString() -> { + actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24) + actionButton.contentDescription = activity.getString(R.string.Remove) + } else -> { actionButton.setIconResource(R.drawable.ic_baseline_file_open_24) + actionButton.contentDescription = activity.getString(R.string.logs) if (item.logID == null){ actionButton.visibility = View.GONE } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt index 6d7b0cab..e39e4fc8 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt @@ -114,7 +114,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit author.text = item.author val length = card.findViewById(R.id.length) - length.text = if(item.downloadPath.size == 1) item.duration else "[${item.downloadPath.size}]" + length.text = if(item.downloadPath.size == 1) item.duration else "" // TIME DOWNLOADED ---------------------------------- diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt index 4ab2e4a7..0dfd561b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HomeAdapter.kt @@ -1,9 +1,7 @@ package com.deniscerri.ytdlnis.ui.adapter -import android.animation.ValueAnimator import android.app.Activity import android.content.SharedPreferences -import android.graphics.Color import android.os.Handler import android.os.Looper import android.view.LayoutInflater @@ -19,7 +17,6 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel -import com.deniscerri.ytdlnis.util.Extensions import com.deniscerri.ytdlnis.util.Extensions.popup import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView @@ -72,7 +69,6 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) } else { uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) }else{ uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt index f6d221d4..5ee5390c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ObserveSourcesAdapter.kt @@ -1,11 +1,9 @@ package com.deniscerri.ytdlnis.ui.adapter import android.app.Activity -import android.graphics.Color import android.os.Handler import android.os.Looper import android.text.format.DateFormat -import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -25,7 +23,6 @@ import com.google.android.material.card.MaterialCardView import com.google.android.material.chip.Chip import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.progressindicator.LinearProgressIndicator -import com.squareup.picasso.Picasso import java.text.DateFormatSymbols import java.text.SimpleDateFormat import java.util.Calendar @@ -139,7 +136,6 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity: } }else{ info.isVisible = true - checkMissing.isVisible = true btn.setImageResource(R.drawable.ic_search) btn.setOnClickListener { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/SearchSuggestionsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/SearchSuggestionsAdapter.kt index 72e89754..ee21f79f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/SearchSuggestionsAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/SearchSuggestionsAdapter.kt @@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.ui.adapter import android.app.Activity import android.content.SharedPreferences -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/TemplatesAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/TemplatesAdapter.kt index ca221183..2e21938b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/TemplatesAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/TemplatesAdapter.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.compose.ui.util.fastJoinToString import androidx.core.view.isVisible import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil @@ -14,7 +13,6 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.CommandTemplate -import com.deniscerri.ytdlnis.database.models.LogItem import com.deniscerri.ytdlnis.util.Extensions.popup import com.google.android.material.card.MaterialCardView diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt index 43552868..f11e478f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt @@ -19,9 +19,9 @@ import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel +import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil -import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt index 78edc2e5..65457dfc 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt @@ -19,8 +19,6 @@ import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.deniscerri.ytdlnis.R -import com.deniscerri.ytdlnis.database.DBManager -import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel @@ -36,7 +34,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -//TODO MAKE CONFIGURE LIKE NORMAL SHEET class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private val currentDownloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() { private lateinit var tabLayout: TabLayout private lateinit var viewPager2: ViewPager2 @@ -77,6 +74,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private tabLayout = view.findViewById(R.id.download_tablayout) viewPager2 = view.findViewById(R.id.download_viewpager) + viewPager2.isUserInputEnabled = sharedPreferences.getBoolean("swipe_gestures_download_card", true) (viewPager2.getChildAt(0) as? RecyclerView)?.apply { isNestedScrollingEnabled = false @@ -231,6 +229,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private kotlin.runCatching { val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author) + f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first()) } } 1 -> { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt index f2e98e89..42dacacf 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt @@ -28,10 +28,10 @@ import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.ChapterItem import com.deniscerri.ytdlnis.database.models.DownloadItem -import com.deniscerri.ytdlnis.util.InfoUtil -import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth import com.deniscerri.ytdlnis.util.Extensions.toStringDuration +import com.deniscerri.ytdlnis.util.InfoUtil +import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.VideoPlayerUtil import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt index db2f768f..35fb8236 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt @@ -15,7 +15,6 @@ import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import android.widget.LinearLayout import android.widget.TextView -import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible @@ -23,7 +22,6 @@ import androidx.core.view.setPadding import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.media3.common.util.Log import androidx.preference.PreferenceManager import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdlnis.R @@ -37,14 +35,13 @@ import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.card.MaterialCardView -import com.google.android.material.progressindicator.LinearProgressIndicator +import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @@ -83,7 +80,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { downloadItem = withContext(Dispatchers.IO) { if (currentDownloadItem != null){ @@ -304,12 +300,22 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private bottomSheet.show(parentFragmentManager, "cutVideoSheet") } }, - updateDataClicked = { - CoroutineScope(Dispatchers.IO).launch { - resultItem?.apply { - val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java] - rsVM.updateItemData(this) + cutDisabledClicked = { + val isUpdatingData = ViewModelProvider(requireActivity())[ResultViewModel::class.java].updatingData.value + if(isUpdatingData){ + val snack = Snackbar.make(view, context.getString(R.string.please_wait), Snackbar.LENGTH_SHORT) + snack.show() + }else{ + val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT) + snack.setAction(R.string.update){ + CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { + resultItem?.apply { + val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java] + rsVM.updateItemData(this) + } + } } + snack.show() } }, extraCommandsClicked = { @@ -328,6 +334,16 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private }catch (e : Exception){ e.printStackTrace() } + super.onViewCreated(view, savedInstanceState) + } + } + + @SuppressLint("RestrictedApi") + fun updateSelectedAudioFormat(formatID: String){ + resultItem?.formats?.find { it.format_id == formatID }?.apply { + downloadItem.format = this + val formatCard = requireView().findViewById(R.id.format_card_constraintLayout) + UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, listOf()) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt index dff14516..64037cd1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt @@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard import android.annotation.SuppressLint import android.app.Dialog +import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences import android.content.res.Configuration @@ -19,7 +20,11 @@ import android.widget.TextView import android.widget.Toast import androidx.core.content.edit import androidx.core.os.bundleOf +import androidx.core.view.get import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.commitNow import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -139,6 +144,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { tabLayout = view.findViewById(R.id.download_tablayout) viewPager2 = view.findViewById(R.id.download_viewpager) updateItem = view.findViewById(R.id.update_item) + viewPager2.isUserInputEnabled = sharedPreferences.getBoolean("swipe_gestures_download_card", true) //loading shimmers @@ -307,12 +313,31 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { download.isEnabled = false val item: DownloadItem = getDownloadItem() item.downloadStartTime = it.timeInMillis - runBlocking { - downloadViewModel.queueDownloads(listOf(item)) - val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime) - Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show() + if (item.videoPreferences.alsoDownloadAsAudio){ + val itemsToQueue = mutableListOf() + itemsToQueue.add(item) + + getAlsoAudioDownloadItem{ audioDownloadItem -> + audioDownloadItem.downloadStartTime = it.timeInMillis + itemsToQueue.add(audioDownloadItem) + + runBlocking { + downloadViewModel.queueDownloads(itemsToQueue) + val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime) + Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show() + } + dismiss() + } + }else{ + runBlocking { + downloadViewModel.queueDownloads(listOf(item)) + val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime) + Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show() + } + dismiss() + } - dismiss() + } } download!!.setOnClickListener { @@ -324,10 +349,24 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { scheduleBtn.isEnabled = false download.isEnabled = false val item: DownloadItem = getDownloadItem() - runBlocking { - downloadViewModel.queueDownloads(listOf(item)) + if (item.videoPreferences.alsoDownloadAsAudio){ + val itemsToQueue = mutableListOf() + itemsToQueue.add(item) + + getAlsoAudioDownloadItem { + itemsToQueue.add(it) + + runBlocking { + downloadViewModel.queueDownloads(itemsToQueue) + } + dismiss() + } + }else{ + runBlocking { + downloadViewModel.queueDownloads(listOf(item)) + } + dismiss() } - dismiss() } } @@ -563,7 +602,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } } - private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{ + private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem { return when(selectedTabPosition){ 0 -> { val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment @@ -580,6 +619,33 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } } + private fun getAlsoAudioDownloadItem(finished: (it: DownloadItem) -> Unit) { + try { + val ff = (fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment) + ff.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first()) + finished(ff.downloadItem) + }catch (e: Exception){ + val fragmentLifecycleCallback = object: + FragmentManager.FragmentLifecycleCallbacks() { + + override fun onFragmentStarted(fm: FragmentManager, f: Fragment) { + fragmentManager?.unregisterFragmentLifecycleCallbacks(this) + val ff = (f as DownloadAudioFragment) + ff.requireView().post { + ff.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first()) + finished(ff.downloadItem) + } + super.onFragmentStarted(fm, f) + } + + + } + + fragmentManager?.registerFragmentLifecycleCallbacks(fragmentLifecycleCallback, true) + viewPager2.setCurrentItem(0, true) + } + } + private fun updateWhenSwitching(){ val prevDownloadItem = getDownloadItem( if (viewPager2.currentItem == 1) 0 else 1 @@ -591,6 +657,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { kotlin.runCatching { val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author) + f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first()) } } 1 -> { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt index a75e5b6b..6ab94497 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt @@ -30,17 +30,15 @@ import com.deniscerri.ytdlnis.database.models.Format import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.UiUtil -import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard import com.google.android.material.card.MaterialCardView import com.google.android.material.chip.Chip import com.google.android.material.textfield.TextInputLayout import com.google.gson.Gson -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index 28bd0e76..c2b33c05 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -10,8 +10,6 @@ import android.content.res.Configuration import android.graphics.Canvas import android.graphics.Color import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.text.format.DateFormat import android.util.DisplayMetrics import android.view.LayoutInflater @@ -30,15 +28,10 @@ import androidx.preference.PreferenceManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.work.Data -import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.Format -import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel @@ -48,8 +41,6 @@ import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil -import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker -import com.google.android.material.appbar.AppBarLayout import com.google.android.material.bottomappbar.BottomAppBar import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -63,7 +54,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.text.SimpleDateFormat import java.util.Locale @@ -80,6 +70,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure private lateinit var behavior: BottomSheetBehavior private lateinit var bottomAppBar: BottomAppBar private lateinit var filesize : TextView + private lateinit var count : TextView private lateinit var sharedPreferences: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { @@ -135,6 +126,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure val scheduleBtn = view.findViewById(R.id.bottomsheet_schedule_button) val download = view.findViewById