diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7339f8f2..bba8e391 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -59,4 +59,8 @@ -keepclassmembers enum * { *; } -keepclassmembers class * extends androidx.work.Worker { public (android.content.Context,androidx.work.WorkerParameters); -} \ No newline at end of file +} + +-dontshrink +-dontobfuscate +-dontoptimize \ No newline at end of file diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/4.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/4.json new file mode 100644 index 00000000..d4366537 --- /dev/null +++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/4.json @@ -0,0 +1,421 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "f911bed4915605d5b09bfd82892e2d7a", + "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, `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": "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)", + "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 + } + ], + "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, `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, `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumb", + "columnName": "thumb", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "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": "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" + } + ], + "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)", + "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 + } + ], + "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": [] + } + ], + "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, 'f911bed4915605d5b09bfd82892e2d7a')" + ] + } +} \ No newline at end of file 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 d5e74c1b..2e3acd68 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt @@ -8,10 +8,11 @@ import com.deniscerri.ytdlnis.database.models.* @TypeConverters(Converters::class) @Database( entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class, CookieItem::class], - version = 3, + version = 4, autoMigrations = [ AutoMigration (from = 1, to = 2), - AutoMigration (from = 2, to = 3) + AutoMigration (from = 2, to = 3), + AutoMigration (from = 3, to = 4) ] ) abstract class DBManager : RoomDatabase(){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueActivity.kt index adfa74e8..c456a614 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueActivity.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.ui.downloads import android.content.Context +import android.content.DialogInterface import android.os.Bundle import android.view.MenuItem import android.view.View @@ -13,6 +14,7 @@ import androidx.work.WorkManager import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayout @@ -81,14 +83,20 @@ class DownloadQueueActivity : AppCompatActivity(){ try{ when(m.itemId){ R.id.clear_queue -> { - cancelAllDownloads() - downloadViewModel.deleteQueued() + showDeleteDialog() { + cancelAllDownloads() + downloadViewModel.deleteQueued() + } } R.id.clear_cancelled -> { - downloadViewModel.deleteCancelled() + showDeleteDialog() { + downloadViewModel.deleteCancelled() + } } R.id.clear_errored -> { - downloadViewModel.deleteErrored() + showDeleteDialog() { + downloadViewModel.deleteErrored() + } } } }catch (e: Exception){ @@ -99,6 +107,16 @@ class DownloadQueueActivity : AppCompatActivity(){ } } + private fun showDeleteDialog (deleteClicked: (deleteClicked: Boolean) -> Unit){ + val deleteDialog = MaterialAlertDialogBuilder(this) + deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) + deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } + deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> + deleteClicked(true) + } + deleteDialog.show() + } + private fun cancelAllDownloads() { workManager.cancelAllWork() } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt index 3a758166..31c10484 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -288,11 +288,11 @@ class DownloadWorker( } }.onSuccess { //move file from internal to set download directory - setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads)) + setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${fileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) var finalPath : String? try { finalPath = moveFile(tempFileDir.absoluteFile, downloadLocation){ progress -> - setProgressAsync(workDataOf("progress" to progress, "output" to "Moving file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads)) + setProgressAsync(workDataOf("progress" to progress, "output" to "Moving file to ${fileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) } setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $finalPath", "id" to downloadItem.id, "log" to logDownloads)) }catch (e: Exception){ diff --git a/app/src/main/res/layout/active_download_card.xml b/app/src/main/res/layout/active_download_card.xml index 00b4ac28..ab7a52ca 100644 --- a/app/src/main/res/layout/active_download_card.xml +++ b/app/src/main/res/layout/active_download_card.xml @@ -9,6 +9,7 @@ diff --git a/fastlane/metadata/android/en-US/changelogs/10500.txt b/fastlane/metadata/android/en-US/changelogs/10500.txt new file mode 100644 index 00000000..5a49a8ad --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10500.txt @@ -0,0 +1,41 @@ +Moved all of the app to kotlin +Fixed Song Thumbnails not showing on music apps using mediastore (like Samsung Music) +Cards update and animate nicely when new items are added +Turned download service to a worker and combined default downloads with custom command +Added Formats and File Sizes on Results +Added ability to change download path from the download card +Combined Audio / Video / Command in a single card through tabs +Added ability to download multiple vids at the same time (Limit is 5 for now) +Added ability to schedule downloads. You can put the date and time. +Fixed File Scanning +Made downloads download to temporary folders. Each version of the file depending on the url and format type has its own folder. If you cancel a download and restart it, it will resume the downloads based on the correct format. After download, temp folder gets axed. +Fixed downloads not getting cancelled sometimes +Added ability to update yt-dlp from a nightly build +Changed search suggestion provider to google's own api +Added more sorting options in the history fragment +Implemented Language switcher in the app. For devices on Android 13 and up, go to App Details and change it there +Added material date and time pickers +Updated bottom sheet design +Added separate format selection card +Implemented bottom card in the share menu instead of opening the whole app. If its a txt file it will open the app +Added m-time as a preference +Added ability to get result item formats if you searched for it or if it is a playlist item since they don't have formats +Added Download Logs +Added Search History +Added Separate Download Queue Screen. (Double Click the download icon or go to more section to access) +Fixed bugs on terminal mode +Fixed downlaod logs crashing on older apis +Added long press to open download card even if the setting is disabled +Fixed bug of download card duplicating when switching formats in share activity +Implemented Custom Command Tab in the download card. (Disabled by default. You need to create at least one command template in the more section) +Added tabs to download queue section (running, queued, cancelled, errored) +Added ability to update custom command right from the download card & modify the chosen one just for that download +Fixed search history storing duplicate queries +Implemented Preferred Download Type. On Share Activity or when searching for one result, it will open the preferred tab on the download card +Implemented processing multiple items from a playlist in the share activity. You can select a portion of them, then you can modify each item just like you would on a normal link. You can change the download directory for all items at once, and you can schedule all of them like a normal download. You can also change their type to audio/video/command all at once +Fixed Download All button not showing up after reopening app with a playlist as result +Added copy log in the download log screen +Fixed download logs not properly live scrolling +Added ability to retry cancelled downloads +Added ability to open logs directly from the list of errored downloads (if logs were enabled for that download at the time) +Showed link copied to clipboard as item suggestion in searchview \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 79deb20c..f6a47f6b 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,10 +1,20 @@ -Download audio and video files on different formats through yt-dlp. -Download videos, playlists, multi-select items. -Really user friendly. -Download History. -Command Line Functionality. -Material You Compatible. -Foreground Download Service. -Share links directly to the app. -Built in search functionality. -Automatically checks for app updates. \ No newline at end of file +download audio/video files from more than 1000 websites using yt-dlp +download playlists or multi select items +queue downloads and schedule them by date and time +use custom commands and templates or go full terminal mode +remove sponsorblock chapters +embed subtitles/metadata/chapters etc +modify metadata such as title and author +split item into separate files depending on its chapters +select different download formats +process multiple items before download +quick share card right from the share menu +search or insert a link from the app +log downloads +re-download cancelled or failed downloads +incognito mode when you don't want to save a download history +download multiple items at the same time +change preferred audio/video formats and qualities +Material You interface +MVVM Architecture w/ WorkManager +easy to use :) \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 1e9bc8dc..b381d248 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Android YouTube Downloader app \ No newline at end of file +Android Video/Audio Downloader app using yt-dlp \ No newline at end of file