From 41306565abd72abdfb1c94cebdd68f1f7c637400 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sat, 15 Jul 2023 20:37:28 +0200 Subject: [PATCH] way too many stuff - long press download button to save it for later and not schedule it. Also works when u try to queue multiple items - change app icon background based on theme - fixed bug when trying to redownload a history item and wanting to use a different type - made the scheduled item auto-update after its queued so the app doesnt have to update when it begins downloading - some fixes with output templates - fixed container and vcodec being saved with translated strings and not constants - added download thumbnail functionality. Click the result card in the middle and use the video player. Also observe running and queued downloads for that item - removed api key - fixed recommendations sometimes not showing in that location - added feature to disable thumbnails on certain screens. U can choose - added feature to convert subs to different formats - added youtube music search provider - made app name have a color depending on the theme - fixed format card not showing a translated string on best and worst strings - added ability to hide the format intent filter from the share menu - fixed app killing active downloads when removing a queued item - fixed filename template not showing uploader on odysee - other small stuff --- app/build.gradle | 2 +- .../9.json | 517 ++++++++++++++++ app/src/main/AndroidManifest.xml | 73 ++- .../main/java/com/deniscerri/ytdlnis/App.kt | 26 + .../com/deniscerri/ytdlnis/MainActivity.kt | 3 + .../ytdlnis/adapter/ActiveDownloadAdapter.kt | 25 +- .../adapter/ActiveDownloadMinifiedAdapter.kt | 142 +++++ .../ConfigureMultipleDownloadsAdapter.kt | 26 +- .../ytdlnis/adapter/GenericDownloadAdapter.kt | 25 +- .../ytdlnis/adapter/HistoryAdapter.kt | 26 +- .../deniscerri/ytdlnis/adapter/HomeAdapter.kt | 30 +- .../ytdlnis/adapter/PlaylistAdapter.kt | 25 +- .../ytdlnis/database/dao/DownloadDao.kt | 7 +- .../database/repository/DownloadRepository.kt | 8 +- .../database/repository/ResultRepository.kt | 2 +- .../database/viewmodel/DownloadViewModel.kt | 79 ++- .../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 13 + .../downloadcard/CutVideoBottomSheetDialog.kt | 60 +- .../ui/downloadcard/DownloadAudioFragment.kt | 14 +- .../downloadcard/DownloadBottomSheetDialog.kt | 18 + .../DownloadMultipleBottomSheetDialog.kt | 18 + .../ui/downloadcard/DownloadVideoFragment.kt | 14 +- .../FormatSelectionBottomSheetDialog.kt | 2 +- .../downloadcard/ResultCardDetailsDialog.kt | 560 ++++++++++++++++++ .../ui/downloads/DownloadQueueMainFragment.kt | 14 +- .../ytdlnis/ui/downloads/HistoryFragment.kt | 24 + .../ui/downloads/QueuedDownloadsFragment.kt | 23 +- .../ui/downloads/SavedDownloadsFragment.kt | 385 ++++++++++++ .../ui/more/settings/BaseSettingsFragment.kt | 3 + .../more/settings/GeneralSettingsFragment.kt | 25 +- .../ui/more/settings/MainSettingsFragment.kt | 4 +- .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 277 +++------ .../com/deniscerri/ytdlnis/util/ThemeUtil.kt | 100 +++- .../com/deniscerri/ytdlnis/util/UiUtil.kt | 16 +- .../ytdlnis/util/VideoPlayerUtil.kt | 63 ++ .../work/UpdatePlaylistFormatsWorker.kt | 64 ++ .../ytdlnis/work/UpdateYTDLWorker.kt | 46 ++ app/src/main/res/anim/fade_in.xml | 14 + .../res/drawable/baseline_format_quote_24.xml | 5 + .../res/drawable/baseline_hide_image_24.xml | 6 + .../drawable/ic_launcher_background_dark.xml | 10 + .../drawable/ic_launcher_background_light.xml | 10 + .../layout/active_download_card_minified.xml | 167 ++++++ .../fragment_generic_download_queue.xml | 1 + app/src/main/res/layout/log_card.xml | 4 +- .../main/res/layout/result_card_details.xml | 225 +++++++ app/src/main/res/menu/download_queue_menu.xml | 6 + .../res/menu/saved_downloads_menu_context.xml | 29 + .../mipmap-anydpi-v26/ic_launcher_dark.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_light.xml | 6 + .../ic_launcher_round_dark.xml | 5 + .../ic_launcher_round_light.xml | 5 + app/src/main/res/navigation/settings_nav.xml | 15 +- app/src/main/res/values-sq-rAL/strings.xml | 2 +- app/src/main/res/values/arrays.xml | 27 + app/src/main/res/values/strings.xml | 10 +- app/src/main/res/values/styles.xml | 4 + .../main/res/xml/downloading_preferences.xml | 16 +- app/src/main/res/xml/general_preferences.xml | 17 + .../main/res/xml/processing_preferences.xml | 9 + app/src/main/res/xml/updating_preferences.xml | 2 +- 61 files changed, 2975 insertions(+), 385 deletions(-) create mode 100644 app/schemas/com.deniscerri.ytdlnis.database.DBManager/9.json create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ResultCardDetailsDialog.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/SavedDownloadsFragment.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/util/VideoPlayerUtil.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/work/UpdatePlaylistFormatsWorker.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/work/UpdateYTDLWorker.kt create mode 100644 app/src/main/res/anim/fade_in.xml create mode 100644 app/src/main/res/drawable/baseline_format_quote_24.xml create mode 100644 app/src/main/res/drawable/baseline_hide_image_24.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background_dark.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background_light.xml create mode 100644 app/src/main/res/layout/active_download_card_minified.xml create mode 100644 app/src/main/res/layout/result_card_details.xml create mode 100644 app/src/main/res/menu/saved_downloads_menu_context.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_dark.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_light.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round_dark.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round_light.xml diff --git a/app/build.gradle b/app/build.gradle index c9aaa2ae..bb3f0370 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ def properties = new Properties() def versionMajor = 1 def versionMinor = 6 def versionPatch = 3 -def versionBuild = 4 // bump for dogfood builds, public betas, etc. +def versionBuild = 5 // bump for dogfood builds, public betas, etc. def versionExt = "" if (versionBuild > 0){ diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/9.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/9.json new file mode 100644 index 00000000..9ab542b7 --- /dev/null +++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/9.json @@ -0,0 +1,517 @@ +{ + "formatVersion": 1, + "database": { + "version": 9, + "identityHash": "bcd20f64b54fbc6e470be0d15059a66e", + "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, `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": "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)", + "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" + } + ], + "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, `workID` BLOB)", + "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": "workID", + "columnName": "workID", + "affinity": "BLOB", + "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)", + "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": [] + }, + { + "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": [] + } + ], + "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, 'bcd20f64b54fbc6e470be0d15059a66e')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c4795898..705a895b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,13 @@ + + + @@ -31,7 +38,8 @@ android:supportsRtl="true" android:theme="@style/BaseTheme" tools:ignore="DataExtractionRules" - tools:targetApi="tiramisu"> + tools:targetApi="tiramisu" + android:banner="@mipmap/ic_launcher"> @@ -101,14 +109,71 @@ + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (1, TimeUnit.DAYS) + .setConstraints(constraints) + .build() + + WorkManager.getInstance(this@App).enqueueUniquePeriodicWork( + "ytdlp-Updater", + ExistingPeriodicWorkPolicy.KEEP, + periodicWorkRequest + ) + }catch (e: Exception){ Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show() e.printStackTrace() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index 773e8123..44a15157 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -34,8 +34,11 @@ import androidx.fragment.app.FragmentContainerView import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController +import androidx.navigation.NavDirections +import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController +import androidx.navigation.navOptions import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt index 3b61b3c0..8d79d471 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.adapter import android.app.Activity +import android.content.SharedPreferences import android.graphics.Color import android.os.Handler import android.os.Looper @@ -9,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -29,10 +31,12 @@ import java.lang.StringBuilder class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { private val onItemClickListener: OnItemClickListener private val activity: Activity + private val sharedPreferences: SharedPreferences init { this.onItemClickListener = onItemClickListener this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { @@ -52,20 +56,25 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - // THUMBNAIL ---------------------------------- - val thumbnail = card.findViewById(R.id.image_view) - val imageURL = item!!.thumb val uiHandler = Handler(Looper.getMainLooper()) - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { + val thumbnail = card.findViewById(R.id.image_view) + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ + val imageURL = item!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } 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) } } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) // PROGRESS BAR ---------------------------------------------------- val progressBar = card.findViewById(R.id.progress) - progressBar.tag = "${item.id}##progress" + progressBar.tag = "${item!!.id}##progress" progressBar.progress = 0 progressBar.isIndeterminate = true diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt new file mode 100644 index 00000000..842ebba6 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt @@ -0,0 +1,142 @@ +package com.deniscerri.ytdlnis.adapter + +import android.app.Activity +import android.content.SharedPreferences +import android.graphics.Color +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.DiffUtil +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.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.util.FileUtil +import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView +import com.google.android.material.chip.Chip +import com.google.android.material.progressindicator.LinearProgressIndicator +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.ShapeAppearanceModel +import com.squareup.picasso.Picasso +import java.lang.StringBuilder + +class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { + private val onItemClickListener: OnItemClickListener + private val activity: Activity + private val sharedPreferences: SharedPreferences + + init { + this.onItemClickListener = onItemClickListener + this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) + } + + class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { + val cardView: MaterialCardView + + init { + cardView = itemView.findViewById(R.id.active_download_card_view) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val cardView = LayoutInflater.from(parent.context) + .inflate(R.layout.active_download_card_minified, parent, false) + return ViewHolder(cardView, onItemClickListener) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = getItem(position) + val card = holder.cardView + val uiHandler = Handler(Looper.getMainLooper()) + val thumbnail = card.findViewById(R.id.image_view) + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ + val imageURL = item!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } 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) } + } + + // PROGRESS BAR ---------------------------------------------------- + val progressBar = card.findViewById(R.id.progress) + progressBar.tag = "${item!!.id}##progress" + progressBar.progress = 0 + progressBar.isIndeterminate = true + + // TITLE ---------------------------------- + val itemTitle = card.findViewById(R.id.title) + var title = item.title + if (title.length > 100) { + title = title.substring(0, 40) + "..." + } + itemTitle.text = title.ifEmpty { item.url } + + val duration = card.findViewById(R.id.duration) + duration.text = item.duration + + + val formatDetailsChip = card.findViewById(R.id.format_note) + val formatDetailsText = StringBuilder(item.format.format_note.uppercase()) + + val codecText = + if (item.format.encoding != "") { + item.format.encoding.uppercase() + }else if (item.format.vcodec != "none" && item.format.vcodec != ""){ + item.format.vcodec.uppercase() + } else { + item.format.acodec.uppercase() + } + if (codecText != "" && codecText != "none"){ + formatDetailsText.append(" \t •\t $codecText") + } + + val fileSize = FileUtil.convertFileSize(item.format.filesize) + if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize") + + formatDetailsChip.text = formatDetailsText + + // CANCEL BUTTON ---------------------------------- + val cancelButton = card.findViewById(R.id.active_download_cancel) + if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) + + cancelButton.setOnClickListener { + onItemClickListener.onCancelClick(item.id) + } + + card.setOnClickListener { + onItemClickListener.onCardClick() + } + } + interface OnItemClickListener { + fun onCancelClick(itemID: Long) + fun onCardClick() + } + + companion object { + private val DIFF_CALLBACK: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { + val ranged = arrayListOf(oldItem.id, newItem.id) + return ranged[0] == ranged[1] + } + + override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { + return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt index 7e929217..22c98177 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt @@ -1,6 +1,8 @@ package com.deniscerri.ytdlnis.adapter import android.app.Activity +import android.content.SharedPreferences +import android.graphics.Color import android.os.Handler import android.os.Looper import android.view.LayoutInflater @@ -9,6 +11,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -24,10 +27,12 @@ import java.util.Locale class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { private val onItemClickListener: OnItemClickListener private val activity: Activity + private val sharedPreferences : SharedPreferences init { this.onItemClickListener = onItemClickListener this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { @@ -47,18 +52,25 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - // THUMBNAIL ---------------------------------- - val thumbnail = card.findViewById(R.id.downloads_image_view) - val imageURL = item!!.thumb + val uiHandler = Handler(Looper.getMainLooper()) - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { + val thumbnail = card.findViewById(R.id.downloads_image_view) + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ + val imageURL = item!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } 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) } } val duration = card.findViewById(R.id.duration) - duration.text = item.duration + duration.text = item!!.duration // TITLE ---------------------------------- val itemTitle = card.findViewById(R.id.title) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt index 8a66db34..6e171ea0 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt @@ -2,6 +2,8 @@ package com.deniscerri.ytdlnis.adapter import android.annotation.SuppressLint import android.app.Activity +import android.content.SharedPreferences +import android.graphics.Color import android.os.Handler import android.os.Looper import android.view.LayoutInflater @@ -9,6 +11,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -25,11 +28,13 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: private val onItemClickListener: OnItemClickListener private val activity: Activity private val checkedItems: ArrayList + private val sharedPreferences: SharedPreferences init { checkedItems = ArrayList() this.onItemClickListener = onItemClickListener this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { @@ -49,18 +54,23 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val item = getItem(position) val card = holder.cardView - // THUMBNAIL ---------------------------------- - val thumbnail = card.findViewById(R.id.downloads_image_view) - val imageURL = item!!.thumb val uiHandler = Handler(Looper.getMainLooper()) - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { + val thumbnail = card.findViewById(R.id.downloads_image_view) + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ + val imageURL = item!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } else { + uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } + } + }else{ uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } } val duration = card.findViewById(R.id.duration) - duration.text = item.duration + duration.text = item!!.duration // TITLE ---------------------------------- val itemTitle = card.findViewById(R.id.title) @@ -102,6 +112,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: 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) else -> { actionButton.setIconResource(R.drawable.ic_baseline_file_open_24) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt index dfb2baf7..2e375b10 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt @@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.adapter import android.annotation.SuppressLint import android.app.Activity +import android.content.SharedPreferences import android.graphics.Color import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter @@ -13,6 +14,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -32,11 +34,13 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit private val checkedItems: ArrayList private val onItemClickListener: OnItemClickListener private val activity: Activity + private val sharedPreferences: SharedPreferences init { checkedItems = ArrayList() this.onItemClickListener = onItemClickListener this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { @@ -56,20 +60,26 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - // THUMBNAIL ---------------------------------- - val thumbnail = card.findViewById(R.id.downloads_image_view) - val imageURL = item!!.thumb + val uiHandler = Handler(Looper.getMainLooper()) - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { + val thumbnail = card.findViewById(R.id.downloads_image_view) + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")){ + val imageURL = item!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } 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) } } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) // TITLE ---------------------------------- val itemTitle = card.findViewById(R.id.downloads_title) - var title = item.title + var title = item!!.title if (title.length > 100) { title = title.substring(0, 40) + "..." } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt index 105c4e8a..9abe7c85 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.adapter import android.app.Activity +import android.content.SharedPreferences import android.graphics.Color import android.os.Handler import android.os.Looper @@ -10,6 +11,8 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView +import androidx.preference.Preference +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -26,11 +29,13 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) private val checkedVideos: ArrayList private val onItemClickListener: OnItemClickListener private val activity: Activity + private val sharedPreferences: SharedPreferences init { checkedVideos = ArrayList() this.onItemClickListener = onItemClickListener this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { @@ -50,22 +55,26 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) override fun onBindViewHolder(holder: ViewHolder, position: Int) { val video = getItem(position) val card = holder.cardView - // THUMBNAIL ---------------------------------- + + val uiHandler = Handler(Looper.getMainLooper()) val thumbnail = card.findViewById(R.id.result_image_view) - val imageURL = video!!.thumb - if (imageURL.isNotEmpty()) { - val uiHandler = Handler(Looper.getMainLooper()) - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ + val imageURL = video!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } else { + uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } + } thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) - } else { - val uiHandler = Handler(Looper.getMainLooper()) + }else{ uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } - thumbnail.setColorFilter(Color.argb(20, 0, 0, 0)) } // TITLE ---------------------------------- val videoTitle = card.findViewById(R.id.result_title) - var title = video.title + var title = video!!.title if (title.length > 100) { title = title.substring(0, 40) + "..." } @@ -145,6 +154,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) card.setOnClickListener { if (checkedVideos.size > 0) { checkCard(card, videoURL) + }else{ + onItemClickListener.onCardDetailsClick(videoURL) } } } @@ -165,6 +176,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) fun onButtonClick(videoURL: String, type: DownloadViewModel.Type?) fun onLongButtonClick(videoURL: String, type: DownloadViewModel.Type?) fun onCardClick(videoURL: String, add: Boolean) + fun onCardDetailsClick(videoURL: String) } fun checkAll(items: List?){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt index a8a0bf28..b676fd0f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt @@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.adapter import android.annotation.SuppressLint import android.app.Activity +import android.content.SharedPreferences import android.graphics.Color import android.os.Handler import android.os.Looper @@ -12,6 +13,7 @@ import android.widget.CheckBox import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -26,11 +28,13 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi private val checkedItems: ArrayList private val onItemClickListener: OnItemClickListener private val activity: Activity + private val sharedPreferences: SharedPreferences init { checkedItems = ArrayList() this.onItemClickListener = onItemClickListener this.activity = activity + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) } class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) { @@ -50,20 +54,25 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView - // THUMBNAIL ---------------------------------- - val thumbnail = card.findViewById(R.id.downloads_image_view) - val imageURL = item!!.thumb val uiHandler = Handler(Looper.getMainLooper()) - if (imageURL.isNotEmpty()) { - uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } - } else { + val thumbnail = card.findViewById(R.id.downloads_image_view) + + // THUMBNAIL ---------------------------------- + if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){ + val imageURL = item!!.thumb + if (imageURL.isNotEmpty()) { + uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } + } else { + uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } + } + thumbnail.setColorFilter(Color.argb(95, 0, 0, 0)) + }else{ uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) } } - thumbnail.setColorFilter(Color.argb(95, 0, 0, 0)) // TITLE ---------------------------------- val itemTitle = card.findViewById(R.id.title) - var title = item.title + var title = item!!.title if (title.length > 100) { title = title.substring(0, 40) + "..." } 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 4dc8f4fa..ab7f765b 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 @@ -40,8 +40,8 @@ interface DownloadDao { @Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC") fun getErroredDownloadsList() : List - @Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC") - fun getProcessingDownloads() : Flow> + @Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC") + fun getSavedDownloads() : Flow> @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") fun getDownloadById(id: Long) : DownloadItem @@ -70,6 +70,9 @@ interface DownloadDao { @Query("DELETE FROM downloads WHERE status='Error'") suspend fun deleteErrored() + @Query("DELETE FROM downloads WHERE status='Saved'") + suspend fun deleteSaved() + @Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued'") suspend fun cancelQueued() 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 7b01a504..84e0ed80 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 @@ -13,10 +13,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) { val queuedDownloads : Flow> = downloadDao.getQueuedDownloads() val cancelledDownloads : Flow> = downloadDao.getCancelledDownloads() val erroredDownloads : Flow> = downloadDao.getErroredDownloads() - val processingDownloads : Flow> = downloadDao.getProcessingDownloads() + val savedDownloads : Flow> = downloadDao.getSavedDownloads() enum class Status { - Active, Queued, Error, Processing, Cancelled + Active, Queued, Error, Processing, Cancelled, Saved } suspend fun insert(item: DownloadItem) : Long { @@ -73,6 +73,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) { downloadDao.deleteErrored() } + suspend fun deleteSaved(){ + downloadDao.deleteSaved() + } + suspend fun cancelQueued(){ downloadDao.cancelQueued() } 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 a501c140..23d25c0a 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 @@ -24,7 +24,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co suspend fun updateTrending(){ deleteAll() val infoUtil = InfoUtil(context) - val items = infoUtil.getTrending(context) + val items = infoUtil.getTrending() itemCount.value = items.size for (i in items){ resultDao.insert(i!!) 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 02f28007..87078cd9 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 @@ -20,10 +20,13 @@ import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager +import androidx.work.workDataOf 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 @@ -40,13 +43,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.runBlocking import java.io.File import java.util.Locale import java.util.concurrent.TimeUnit class DownloadViewModel(application: Application) : AndroidViewModel(application) { + private val dbManager: DBManager private val repository : DownloadRepository private val sharedPreferences: SharedPreferences private val commandTemplateDao: CommandTemplateDao @@ -57,7 +61,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val activeDownloadsCount : LiveData val cancelledDownloads : LiveData> val erroredDownloads : LiveData> - val processingDownloads : LiveData> + val savedDownloads : LiveData> private var bestVideoFormat : Format private var bestAudioFormat : Format @@ -72,7 +76,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } init { - val dao = DBManager.getInstance(application).downloadDao + dbManager = DBManager.getInstance(application) + val dao = dbManager.downloadDao repository = DownloadRepository(dao) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) commandTemplateDao = DBManager.getInstance(application).commandTemplateDao @@ -82,7 +87,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application queuedDownloads = repository.queuedDownloads.asLiveData() activeDownloads = repository.activeDownloads.asLiveData() activeDownloadsCount = repository.activeDownloadsCount.asLiveData() - processingDownloads = repository.processingDownloads.asLiveData() + savedDownloads = repository.savedDownloads.asLiveData() cancelledDownloads = repository.cancelledDownloads.asLiveData() erroredDownloads = repository.erroredDownloads.asLiveData() @@ -97,8 +102,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val videoFormatValues = resources.getStringArray(R.array.video_formats_values) - var videoContainer = sharedPreferences.getString("video_format", "Default") - if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue) + val videoContainer = sharedPreferences.getString("video_format", "Default") defaultVideoFormats = mutableListOf() videoFormatValues.forEach { @@ -116,8 +120,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application bestVideoFormat = defaultVideoFormats.last() - var audioContainer = sharedPreferences.getString("audio_format", "mp3") - if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue) + val audioContainer = sharedPreferences.getString("audio_format", "mp3") bestAudioFormat = Format( "best", audioContainer!!, @@ -374,8 +377,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application fun getGenericAudioFormats() : MutableList{ val audioFormats = resources.getStringArray(R.array.audio_formats) val formats = mutableListOf() - var containerPreference = sharedPreferences.getString("audio_format", "Default") - if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue) + val containerPreference = sharedPreferences.getString("audio_format", "") audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) } return formats } @@ -383,9 +385,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application fun getGenericVideoFormats() : MutableList{ val videoFormats = resources.getStringArray(R.array.video_formats_values) val formats = mutableListOf() - var containerPreference = sharedPreferences.getString("video_format", "Default") - if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue) - videoFormats.forEach { formats.add(Format(it, containerPreference!!,resources.getString(R.string.defaultValue),"", "",0, it)) } + val containerPreference = sharedPreferences.getString("video_format", "") + videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) } return formats } @@ -415,6 +416,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application repository.deleteErrored() } + fun deleteSaved() = viewModelScope.launch(Dispatchers.IO) { + repository.deleteSaved() + } + fun cancelQueued() = viewModelScope.launch(Dispatchers.IO) { repository.cancelQueued() } @@ -450,6 +455,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true) val queuedItems = mutableListOf() var lastDownloadId = repository.getLastDownloadId() + + var exists = false + items.forEach { lastDownloadId++ it.status = DownloadRepository.Status.Queued.toString() @@ -458,9 +466,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application d.status = DownloadRepository.Status.Queued.toString() d.toString() == it.toString() } != null) { - Looper.prepare().run { - Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() - } + exists = true }else{ if (it.id == 0L){ it.id = lastDownloadId @@ -475,6 +481,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } } + if (exists){ + Looper.prepare().run { + Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() + } + } + queuedItems.forEach { val currentTime = System.currentTimeMillis() var delay = if (it.downloadStartTime != 0L){ @@ -498,12 +510,6 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application workRequest ).enqueue() - it.id = withContext(Dispatchers.IO){ - repository.checkIfReDownloadingErroredOrCancelled(it) - } - if (it.id != 0L){ - repository.delete(it) - } } val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered @@ -512,6 +518,35 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show() } } + + items.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){} + } + } + 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 + dao.update(downloadItem) + } + } + } + return wasQuickDownloaded } } \ No newline at end of file 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 92a87620..4cef7f45 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -5,12 +5,14 @@ import android.app.Activity import android.content.* import android.content.Context.CLIPBOARD_SERVICE import android.content.res.ColorStateList +import android.content.res.Resources.Theme import android.graphics.Color import android.os.Bundle import android.os.Handler import android.os.Looper import android.text.Editable import android.util.Log +import android.util.TypedValue import android.view.* import android.view.View.* import android.widget.* @@ -37,7 +39,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog +import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog import com.deniscerri.ytdlnis.util.InfoUtil +import com.deniscerri.ytdlnis.util.ThemeUtil import com.facebook.shimmer.ShimmerFrameLayout import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.MaterialToolbar @@ -133,6 +137,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene searchHistory = view.findViewById(R.id.search_history_scroll_view) searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout) + materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) + homeAdapter = HomeAdapter( this, @@ -567,6 +573,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene } + override fun onCardDetailsClick(videoURL: String) { + if (parentFragmentManager.findFragmentByTag("resultDetails") == null && resultsList != null && resultsList!!.isNotEmpty()){ + val bottomSheet = ResultCardDetailsDialog(resultsList!!.first{it!!.url == videoURL}!!) + bottomSheet.show(parentFragmentManager, "cutVideoSheet") + } + } + override fun onClick(v: View) { val viewIdName: String = try { v.tag.toString() 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 245ab95f..ee474f1c 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 @@ -32,6 +32,7 @@ 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.VideoPlayerUtil import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.button.MaterialButton @@ -104,64 +105,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls behavior.peekHeight = displayMetrics.heightPixels } -// val renderersFactory = DefaultRenderersFactory(requireContext().applicationContext) -// .setEnableDecoderFallback(true) -// .setExtensionRendererMode(EXTENSION_RENDERER_MODE_PREFER) -// .setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunnelingDecoder -> -// var decoderInfo: MutableList = -// MediaCodecSelector.DEFAULT -// .getDecoderInfos( -// mimeType, -// requiresSecureDecoder, -// requiresTunnelingDecoder -// ) -// if (MimeTypes.VIDEO_H264 == mimeType) { -// // copy the list because MediaCodecSelector.DEFAULT returns an unmodifiable list -// decoderInfo = ArrayList(decoderInfo) -// decoderInfo.reverse() -// } -// decoderInfo -// } - - val cronetEngine: CronetEngine = CronetEngine.Builder(App.instance) - .enableHttp2(true) - .enableQuic(true) - .enableBrotli(true) - .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 1024L * 1024L) // 1MiB - .build() - - val trackSelector = DefaultTrackSelector(requireContext()) - val loadControl = DefaultLoadControl.Builder() - // cache the last three minutes - .setBackBuffer(1000 * 60 * 3, true) - .setBufferDurationsMs( - 1000 * 10, // exo default is 50s - 50000, - DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, - DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS - ) - .build() - - val cronetDataSourceFactory = CronetDataSource.Factory( - cronetEngine, - Executors.newCachedThreadPool() - ) - val dataSourceFactory = DefaultDataSource.Factory(requireContext(), cronetDataSourceFactory) - - player = ExoPlayer.Builder(requireContext()) - .setUsePlatformDiagnostics(false) - .setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory)) - .setTrackSelector(trackSelector) - .setLoadControl(loadControl) - .setHandleAudioBecomingNoisy(true) - .setAudioAttributes( - AudioAttributes.Builder() - .setUsage(C.USAGE_MEDIA) - .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) - .build() - , - true) - .build() + player = VideoPlayerUtil.buildPlayer(requireContext()) val frame = view.findViewById(R.id.frame_layout) val videoView = view.findViewById(R.id.video_view) 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 37075b85..2437436b 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 @@ -142,9 +142,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr //if its updating a present downloaditem and its the wrong category if (currentDownloadItem!!.type != Type.audio){ downloadItem.type = Type.audio - downloadItem.format = - downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) } - .maxByOrNull { it.filesize }!! + runCatching { + downloadItem.format = + downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) } + .maxByOrNull { it.filesize }!! + }.onFailure { + downloadItem.format = downloadViewModel.getGenericAudioFormats().last() + } } } if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }) @@ -160,11 +164,11 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr val formatCard = view.findViewById(R.id.format_card_constraintLayout) val chosenFormat = downloadItem.format - UiUtil.populateFormatCard(formatCard, chosenFormat, null) + UiUtil.populateFormatCard(requireContext(), formatCard, chosenFormat, null) val listener = object : OnFormatClickListener { override fun onFormatClick(allFormats: List>, item: List) { downloadItem.format = item.first().format - UiUtil.populateFormatCard(formatCard, item.first().format, null) + UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format, null) lifecycleScope.launch { withContext(Dispatchers.IO){ resultItem.formats.removeAll(formats.toSet()) 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 e4fd0ad9..1d2bc1f4 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 @@ -23,6 +23,7 @@ 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.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel @@ -33,6 +34,7 @@ import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayout import kotlinx.coroutines.* import java.text.SimpleDateFormat @@ -201,6 +203,22 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val dismiss() } + download.setOnLongClickListener { + val dd = MaterialAlertDialogBuilder(requireContext()) + dd.setTitle(getString(R.string.save_for_later)) + dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } + dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> + lifecycleScope.launch(Dispatchers.IO){ + val item = getDownloadItem() + item.status = DownloadRepository.Status.Saved.toString() + downloadViewModel.updateDownload(item) + dismiss() + } + } + dd.show() + true + } + val link = view.findViewById