From e91262cc66cca67ff3d3000ef65ba3f560a9695e Mon Sep 17 00:00:00 2001
From: deniscerri <64997243+deniscerri@users.noreply.github.com>
Date: Sun, 20 Aug 2023 17:01:39 +0200
Subject: [PATCH] fixed webview ok button slightly improved listing of items if
u have alot of items fixed app not enabling ok button in the cut screen if
you change the TO text box only added force keyframes at cuts switch in the
cut screen Added 3 dots in multiple download card. It will have the configure
chips in a separate card. Each will have a callback that will update all
items in multiple download fixed start end textboxes showing truncanted text
in playlist select card add ability to start a download now and put the rest
of the queue behind it Scrollbar handle shrinked depending on size. Made it
same size Fixed Tapping the notification of the errored download leading to
the running tab instead of errored Set different icon for terminal in share
menu ignored extra command preference in backups of command templates made
preference titles be multiline if they are too long if the preferred download
type is command, the app will not bother fetching data beforehand fixed app
not showing the entire interface when being in landscape hid format details
in command type logs because they are useless fixed cookies import not
working from external sources other than the app fixed app using continue
button for a split second when there are no paused downloads added state in
home screen so that it wont populate trending videos while its searching for
an item implemented batch downloads in a single worker to avoid bogging down
the system with many works fixed instagram status that have multiple videos
just using the first video fixed monochrome icon not showing made yt-dlp auto
update a choice in the settings fixed bilibili not working in normal mode
fixed app overwriting files instead of adding (1) in the filename instead
---
.idea/misc.xml | 2 +-
app/build.gradle | 28 +-
app/src/main/AndroidManifest.xml | 27 +-
.../main/java/com/deniscerri/ytdlnis/App.kt | 43 +-
.../ytdlnis/adapter/ActiveDownloadAdapter.kt | 11 +-
.../ytdlnis/adapter/GenericDownloadAdapter.kt | 78 +--
.../ytdlnis/adapter/PlaylistAdapter.kt | 15 +-
.../deniscerri/ytdlnis/database/Converters.kt | 18 +-
.../deniscerri/ytdlnis/database/DBManager.kt | 5 +
.../ytdlnis/database/dao/DownloadDao.kt | 88 ++-
.../ytdlnis/database/dao/SearchHistoryDao.kt | 2 +-
.../ytdlnis/database/models/DownloadItem.kt | 2 +-
.../ytdlnis/database/models/ResultItem.kt | 4 +-
.../database/repository/DownloadRepository.kt | 57 +-
.../database/viewmodel/CookieViewModel.kt | 19 +-
.../database/viewmodel/DownloadViewModel.kt | 223 ++++---
.../database/viewmodel/ResultViewModel.kt | 14 +-
.../CancelDownloadNotificationReceiver.kt | 4 +-
.../PauseDownloadNotificationReceiver.kt | 13 +
.../ytdlnis/receiver/ShareActivity.kt | 17 +-
.../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 17 +-
.../ui/downloadcard/AddExtraCommandsDialog.kt | 16 +-
.../downloadcard/CutVideoBottomSheetDialog.kt | 88 ++-
.../ui/downloadcard/DownloadAudioFragment.kt | 149 +----
.../downloadcard/DownloadBottomSheetDialog.kt | 4 +-
.../downloadcard/DownloadCommandFragment.kt | 72 +--
.../DownloadMultipleBottomSheetDialog.kt | 203 +++++--
.../ui/downloadcard/DownloadVideoFragment.kt | 218 ++-----
.../FormatSelectionBottomSheetDialog.kt | 16 +-
.../downloadcard/ResultCardDetailsDialog.kt | 475 ++++++++-------
...Dialog.kt => SelectPlaylistItemsDialog.kt} | 83 ++-
.../ui/downloads/ActiveDownloadsFragment.kt | 158 ++---
.../downloads/CancelledDownloadsFragment.kt | 293 ++++-----
.../ui/downloads/DownloadQueueMainFragment.kt | 5 +-
.../ui/downloads/ErroredDownloadsFragment.kt | 297 ++++-----
.../ytdlnis/ui/downloads/HistoryFragment.kt | 2 +
.../ui/downloads/QueuedDownloadsFragment.kt | 401 ++++++-------
.../ui/downloads/SavedDownloadsFragment.kt | 295 ++++-----
.../ytdlnis/ui/more/CookiesFragment.kt | 1 +
.../ytdlnis/ui/more/WebViewActivity.kt | 2 +
.../more/settings/DownloadSettingsFragment.kt | 22 -
.../more/settings/FolderSettingsFragment.kt | 6 +-
.../ui/more/settings/MainSettingsFragment.kt | 3 +-
.../com/deniscerri/ytdlnis/util/FileUtil.kt | 69 ++-
.../com/deniscerri/ytdlnis/util/InfoUtil.kt | 90 +--
.../ytdlnis/util/NotificationUtil.kt | 38 ++
.../com/deniscerri/ytdlnis/util/UiUtil.kt | 565 +++++++++++++++++-
.../ytdlnis/util/VideoPlayerUtil.kt | 19 -
.../deniscerri/ytdlnis/work/DownloadWorker.kt | 356 +++++------
.../ytdlnis/work/TerminalDownloadWorker.kt | 53 +-
.../main/res/drawable/baseline_close_24.xml | 5 +
.../baseline_desktop_access_disabled_24.xml | 5 +
.../drawable/baseline_desktop_windows_24.xml | 5 +
app/src/main/res/drawable/ic_more.xml | 2 +-
.../res/drawable/terminal_icon_background.xml | 10 +
.../res/drawable/terminal_icon_foreground.xml | 15 +
.../main/res/layout-land/activity_main.xml | 61 ++
.../main/res/layout-land/fragment_home.xml | 312 ++++++++++
.../res/layout-land/result_card_details.xml | 172 ++++++
.../res/layout-sw600dp-land/fragment_home.xml | 5 -
.../main/res/layout-sw600dp/fragment_home.xml | 6 -
.../res/layout-sw950dp-land/fragment_home.xml | 5 -
app/src/main/res/layout/adjust_audio.xml | 126 ++++
app/src/main/res/layout/adjust_command.xml | 45 ++
app/src/main/res/layout/adjust_video.xml | 205 +++++++
app/src/main/res/layout/cut_video_sheet.xml | 10 +
app/src/main/res/layout/download_card.xml | 1 -
.../layout/download_multiple_bottom_sheet.xml | 5 -
.../layout/extra_commands_bottom_sheet.xml | 1 +
.../main/res/layout/format_details_sheet.xml | 141 ++---
app/src/main/res/layout/fragment_active.xml | 2 +-
.../res/layout/fragment_download_audio.xml | 126 +---
.../res/layout/fragment_download_command.xml | 45 +-
.../res/layout/fragment_download_video.xml | 204 +------
app/src/main/res/layout/fragment_history.xml | 5 -
app/src/main/res/layout/fragment_home.xml | 6 -
app/src/main/res/layout/fragment_inqueue.xml | 12 +-
app/src/main/res/layout/playlist_item.xml | 23 +-
.../main/res/layout/result_card_details.xml | 39 +-
.../main/res/layout/select_playlist_items.xml | 116 ++--
.../main/res/menu/download_multiple_menu.xml | 13 +-
.../main/res/menu/playlist_download_menu.xml | 9 +
app/src/main/res/menu/queued_menu_context.xml | 7 +
.../mipmap-anydpi-v26/ic_launcher_round.xml | 1 +
.../ic_launcher_round_dark.xml | 1 +
.../ic_launcher_round_light.xml | 1 +
.../res/mipmap-anydpi-v26/terminal_icon.xml | 5 +
.../mipmap-anydpi-v26/terminal_icon_round.xml | 5 +
.../main/res/mipmap-hdpi/terminal_icon.webp | Bin 0 -> 1896 bytes
.../res/mipmap-hdpi/terminal_icon_round.webp | Bin 0 -> 1896 bytes
.../main/res/mipmap-mdpi/terminal_icon.webp | Bin 0 -> 1168 bytes
.../res/mipmap-mdpi/terminal_icon_round.webp | Bin 0 -> 1168 bytes
.../main/res/mipmap-xhdpi/terminal_icon.webp | Bin 0 -> 2766 bytes
.../res/mipmap-xhdpi/terminal_icon_round.webp | Bin 0 -> 2766 bytes
.../main/res/mipmap-xxhdpi/terminal_icon.webp | Bin 0 -> 4380 bytes
.../mipmap-xxhdpi/terminal_icon_round.webp | Bin 0 -> 4380 bytes
.../res/mipmap-xxxhdpi/terminal_icon.webp | Bin 0 -> 6372 bytes
.../mipmap-xxxhdpi/terminal_icon_round.webp | Bin 0 -> 6372 bytes
app/src/main/res/navigation/nav_graph.xml | 10 +-
app/src/main/res/values/arrays.xml | 22 +-
app/src/main/res/values/strings.xml | 2 +
app/src/main/res/values/styles.xml | 6 +
app/src/main/res/xml/folders_preference.xml | 1 +
.../main/res/xml/processing_preferences.xml | 20 +-
app/src/main/res/xml/updating_preferences.xml | 11 +-
app/src/main/terminal_icon-playstore.png | Bin 0 -> 9680 bytes
build.gradle | 9 +-
gradle.properties | 2 +-
108 files changed, 3873 insertions(+), 2653 deletions(-)
rename app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/{SelectPlaylistItemsBottomSheetDialog.kt => SelectPlaylistItemsDialog.kt} (74%)
create mode 100644 app/src/main/res/drawable/baseline_close_24.xml
create mode 100644 app/src/main/res/drawable/baseline_desktop_access_disabled_24.xml
create mode 100644 app/src/main/res/drawable/baseline_desktop_windows_24.xml
create mode 100644 app/src/main/res/drawable/terminal_icon_background.xml
create mode 100644 app/src/main/res/drawable/terminal_icon_foreground.xml
create mode 100644 app/src/main/res/layout-land/activity_main.xml
create mode 100644 app/src/main/res/layout-land/fragment_home.xml
create mode 100644 app/src/main/res/layout-land/result_card_details.xml
create mode 100644 app/src/main/res/layout/adjust_audio.xml
create mode 100644 app/src/main/res/layout/adjust_command.xml
create mode 100644 app/src/main/res/layout/adjust_video.xml
create mode 100644 app/src/main/res/menu/playlist_download_menu.xml
create mode 100644 app/src/main/res/mipmap-anydpi-v26/terminal_icon.xml
create mode 100644 app/src/main/res/mipmap-anydpi-v26/terminal_icon_round.xml
create mode 100644 app/src/main/res/mipmap-hdpi/terminal_icon.webp
create mode 100644 app/src/main/res/mipmap-hdpi/terminal_icon_round.webp
create mode 100644 app/src/main/res/mipmap-mdpi/terminal_icon.webp
create mode 100644 app/src/main/res/mipmap-mdpi/terminal_icon_round.webp
create mode 100644 app/src/main/res/mipmap-xhdpi/terminal_icon.webp
create mode 100644 app/src/main/res/mipmap-xhdpi/terminal_icon_round.webp
create mode 100644 app/src/main/res/mipmap-xxhdpi/terminal_icon.webp
create mode 100644 app/src/main/res/mipmap-xxhdpi/terminal_icon_round.webp
create mode 100644 app/src/main/res/mipmap-xxxhdpi/terminal_icon.webp
create mode 100644 app/src/main/res/mipmap-xxxhdpi/terminal_icon_round.webp
create mode 100644 app/src/main/terminal_icon-playstore.png
diff --git a/.idea/misc.xml b/.idea/misc.xml
index f58d8de1..34572e74 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index b4b2222b..09936bfe 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,15 +2,15 @@ plugins {
id 'com.android.application'
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1'
id 'org.jetbrains.kotlin.android'
- id 'kotlin-kapt'
+ id 'com.google.devtools.ksp'
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10"
}
def properties = new Properties()
def versionMajor = 1
def versionMinor = 6
-def versionPatch = 3
-def versionBuild = 17 // bump for dogfood builds, public betas, etc.
+def versionPatch = 5
+def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = ""
if (versionBuild > 0){
@@ -49,10 +49,8 @@ android {
useSupportLibrary true
}
- kapt {
- arguments {
- arg("room.schemaLocation", "$projectDir/schemas")
- }
+ ksp {
+ arg("room.schemaLocation", "$projectDir/schemas")
}
}
@@ -76,6 +74,8 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable false
signingConfig signingConfigs.debug
+ applicationIdSuffix '.beta'
+ versionNameSuffix '-Beta'
}
debug {
@@ -98,7 +98,7 @@ android {
disable 'MissingTranslation'
}
- android.applicationVariants.all { variant ->
+ android.applicationVariants.configureEach { variant ->
variant.outputs.each { output ->
def baseAbiVersionCode = project.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI))
if (baseAbiVersionCode != null) {
@@ -149,7 +149,7 @@ dependencies {
implementation 'com.google.android.material:material:1.9.0-rc01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
- implementation 'androidx.preference:preference:1.2.1'
+ implementation 'androidx.preference:preference-ktx:1.2.1'
implementation "androidx.navigation:navigation-fragment-ktx:$navVer"
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
implementation 'androidx.core:core-ktx:1.10.1'
@@ -174,16 +174,18 @@ dependencies {
implementation "androidx.room:room-runtime:2.5.2"
implementation "androidx.room:room-ktx:2.5.2"
- kapt "androidx.room:room-compiler:2.5.2"
+ ksp "androidx.room:room-compiler:2.5.2"
+ implementation 'androidx.paging:paging-runtime-ktx:3.2.0'
+ implementation "androidx.room:room-paging:2.5.2"
androidTestImplementation "androidx.room:room-testing:2.5.2"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation "androidx.compose.runtime:runtime:$composeVer"
- androidTestImplementation "com.google.truth:truth:1.1.3"
+ androidTestImplementation 'com.google.truth:truth:1.1.5'
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVer"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVer"
- implementation 'com.google.code.gson:gson:2.9.1'
+ implementation 'com.google.code.gson:gson:2.10.1'
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10"
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
@@ -200,7 +202,7 @@ dependencies {
implementation("androidx.media3:media3-ui:$media3_version")
// For RTSP playback support with ExoPlayer
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
- implementation("androidx.media3:media3-datasource-cronet:$media3_version")
implementation "com.anggrayudi:storage:1.5.5"
+ implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 91a929f8..94e2d12e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -57,6 +57,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -204,7 +217,7 @@
@@ -212,18 +225,6 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/App.kt b/app/src/main/java/com/deniscerri/ytdlnis/App.kt
index 958013c7..1fbf7f9a 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/App.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/App.kt
@@ -9,14 +9,11 @@ import androidx.work.Configuration
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
-import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
-import androidx.work.WorkQuery
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
-import com.google.android.gms.common.internal.Constants
import com.yausername.aria2c.Aria2c
import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL
@@ -50,21 +47,27 @@ class App : Application() {
}
}
- //init yt-dlp auto update with work request
- val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.UNMETERED)
- .setRequiresDeviceIdle(true)
- .build()
+ if (sharedPreferences.getBoolean("auto_update_ytdlp", true)){
+ //init yt-dlp auto update with work request
+ val constraints = Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.UNMETERED)
+ .setRequiresDeviceIdle(true)
+ .build()
+
+ val periodicWorkRequest = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS)
+ .setConstraints(constraints)
+ .build()
+
+ WorkManager.getInstance(this@App).enqueueUniquePeriodicWork(
+ "ytdlp-Updater",
+ ExistingPeriodicWorkPolicy.KEEP,
+ periodicWorkRequest
+ )
+ }else{
+ WorkManager.getInstance(this@App).cancelUniqueWork("ytdlp-Updater")
+ }
- val periodicWorkRequest = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS)
- .setConstraints(constraints)
- .build()
- WorkManager.getInstance(this@App).enqueueUniquePeriodicWork(
- "ytdlp-Updater",
- ExistingPeriodicWorkPolicy.KEEP,
- periodicWorkRequest
- )
}catch (e: Exception){
Looper.prepare().runCatching {
@@ -73,14 +76,6 @@ class App : Application() {
e.printStackTrace()
}
}
-
- WorkManager.initialize(
- this@App,
- Configuration.Builder()
- .setExecutor(Executors.newFixedThreadPool(
- sharedPreferences.getInt("concurrent_downloads", 1)))
- .build())
-
}
@Throws(YoutubeDLException::class)
private fun initLibraries() {
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 c7deb943..7ef835dc 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt
@@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.graphics.Color
import android.os.Handler
import android.os.Looper
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -106,14 +107,14 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
}
val formatDetailsChip = card.findViewById(R.id.format_note)
- val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
- formatDetailsText.append(" \t •\t ${item.container.uppercase().ifEmpty { item.format.container.uppercase() }}")
+ val sideDetails = mutableListOf()
+ sideDetails.add(item.format.format_note.uppercase())
+ sideDetails.add(item.container.uppercase().ifEmpty { item.format.container.uppercase() })
val fileSize = FileUtil.convertFileSize(item.format.filesize)
- if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize")
-
- formatDetailsChip.text = formatDetailsText
+ if (fileSize != "?") sideDetails.add(fileSize)
+ formatDetailsChip.text = sideDetails.joinToString(" · ")
//OUTPUT
val output = card.findViewById(R.id.output)
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 6e171ea0..1071d67f 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt
@@ -3,7 +3,6 @@ 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
@@ -11,10 +10,10 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.view.isVisible
+import androidx.paging.PagingDataAdapter
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
@@ -24,16 +23,18 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
-class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
+class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter(DIFF_CALLBACK) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
- private val checkedItems: ArrayList
+ val checkedItems: ArrayList
+ var inverted: Boolean
private val sharedPreferences: SharedPreferences
init {
checkedItems = ArrayList()
this.onItemClickListener = onItemClickListener
this.activity = activity
+ this.inverted = false
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
}
@@ -53,13 +54,16 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val card = holder.cardView
+ card.isVisible = item != null
+ if (item == null) return
+ card.tag = item.id.toString()
val uiHandler = Handler(Looper.getMainLooper())
val thumbnail = card.findViewById(R.id.downloads_image_view)
// THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
- val imageURL = item!!.thumb
+ val imageURL = item.thumb
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
@@ -127,7 +131,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
actionButton.setOnClickListener {
onItemClickListener.onActionButtonClick(item.id)
}
- if (checkedItems.contains(item.id)) {
+ if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) {
card.isChecked = true
card.strokeWidth = 5
} else {
@@ -135,67 +139,69 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
card.strokeWidth = 0
}
card.setOnClickListener {
- if (checkedItems.size > 0) {
- checkCard(card, item.id)
+ if (checkedItems.size > 0 || inverted) {
+ checkCard(card, item.id, position)
} else {
onItemClickListener.onCardClick(item.id)
}
}
card.setOnLongClickListener {
- checkCard(card, item.id)
+ checkCard(card, item.id, position)
true
}
}
@SuppressLint("NotifyDataSetChanged")
- fun clearCheckeditems() {
- for (i in 0 until itemCount){
- val item = getItem(i)
- if (checkedItems.find { it == item?.id } != null){
- checkedItems.remove(item?.id)
- notifyItemChanged(i)
- }
- }
-
+ fun clearCheckedItems() {
+ inverted = false
checkedItems.clear()
- }
-
- @SuppressLint("NotifyDataSetChanged")
- fun checkAll(items: List?){
- checkedItems.clear()
- checkedItems.addAll(items!!.map { it!!.id })
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
- fun invertSelected(items: List?){
- val invertedList = mutableListOf()
- items?.forEach {
- if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
- }
+ fun checkAll() {
checkedItems.clear()
- checkedItems.addAll(invertedList)
+ inverted = true
notifyDataSetChanged()
}
- private fun checkCard(card: MaterialCardView, itemID: Long) {
+ @SuppressLint("NotifyDataSetChanged")
+ fun invertSelected() {
+ inverted = !inverted
+ notifyDataSetChanged()
+ }
+
+ fun getSelectedObjectsCount(totalSize: Int) : Int{
+ return if (inverted){
+ totalSize - checkedItems.size
+ }else{
+ checkedItems.size
+ }
+ }
+
+
+
+ private fun checkCard(card: MaterialCardView, itemID: Long, position: Int) {
if (card.isChecked) {
card.strokeWidth = 0
- checkedItems.remove(itemID)
+ if (inverted) checkedItems.add(itemID)
+ else checkedItems.remove(itemID)
} else {
card.strokeWidth = 5
- checkedItems.add(itemID)
+ if (inverted) checkedItems.remove(itemID)
+ else checkedItems.add(itemID)
}
+
card.isChecked = !card.isChecked
- onItemClickListener.onCardSelect(itemID, card.isChecked)
+ onItemClickListener.onCardSelect(card.isChecked, position)
}
interface OnItemClickListener {
fun onActionButtonClick(itemID: Long)
fun onCardClick(itemID: Long)
- fun onCardSelect(itemID: Long, isChecked: Boolean)
+ fun onCardSelect(isChecked: Boolean, position: Int)
}
companion object {
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 b676fd0f..76719fce 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/PlaylistAdapter.kt
@@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.squareup.picasso.Picasso
+import org.w3c.dom.Text
import java.util.*
@@ -70,17 +71,9 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
- // TITLE ----------------------------------
- val itemTitle = card.findViewById(R.id.title)
- var title = item!!.title
- if (title.length > 100) {
- title = title.substring(0, 40) + "..."
- }
- itemTitle.text = title
-
-
- val duration = card.findViewById(R.id.duration)
- duration.text = item.duration
+ card.findViewById(R.id.title).text = item!!.title
+ card.findViewById(R.id.author).text = item.author
+ card.findViewById(R.id.duration).text = item.duration
// CHECKBOX ----------------------------------
val check = card.findViewById(R.id.checkBox)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt
index f4850317..5537c399 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/Converters.kt
@@ -13,16 +13,9 @@ import java.lang.reflect.Type
class Converters {
@TypeConverter
- fun stringToListOfFormats(value: String?): ArrayList {
- val listType: Type = object : TypeToken?>() {}.type
- return Gson().fromJson(value, listType)
- }
-
+ fun stringToListOfFormats(value: String) = Gson().fromJson(value, Array::class.java).toMutableList()
@TypeConverter
- fun listOfFormatsToString(list: ArrayList?): String {
- val gson = Gson()
- return gson.toJson(list)
- }
+ fun listOfFormatsToString(list: List?) = Gson().toJson(list).toString()
@TypeConverter
fun formatToString(format: Format): String = Gson().toJson(format)
@@ -53,13 +46,10 @@ class Converters {
fun stringToVideoPreferences(string: String): VideoPreferences = Gson().fromJson(string, VideoPreferences::class.java)
@TypeConverter
- fun stringToListOfChapters(value: String?): ArrayList? {
- val listType: Type = object : TypeToken?>() {}.type
- return Gson().fromJson(value, listType)
- }
+ fun stringToListOfChapters(value: String?) = Gson().fromJson(value, Array::class.java).toMutableList()
@TypeConverter
- fun listOfChaptersToString(list: ArrayList?): String {
+ fun listOfChaptersToString(list: List?): String {
val gson = Gson()
return gson.toJson(list)
}
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 ac349fc0..70357a75 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
@@ -1,9 +1,14 @@
package com.deniscerri.ytdlnis.database
import android.content.Context
+import androidx.paging.Pager
+import androidx.paging.PagingConfig
+import androidx.paging.PagingData
+import androidx.paging.PagingSource
import androidx.room.*
import com.deniscerri.ytdlnis.database.dao.*
import com.deniscerri.ytdlnis.database.models.*
+import kotlinx.coroutines.flow.Flow
@TypeConverters(Converters::class)
@Database(
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 15550964..c8fdeb4f 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,20 +1,26 @@
package com.deniscerri.ytdlnis.database.dao
+import androidx.paging.PagingSource
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.DownloadItem
+import com.deniscerri.ytdlnis.database.models.Format
+import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import kotlinx.coroutines.flow.Flow
@Dao
interface DownloadDao {
@Query("SELECT * FROM downloads ORDER BY status")
- fun getAllDownloads() : Flow>
+ fun getAllDownloads() : PagingSource
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
fun getActiveDownloads() : Flow>
@Query("SELECT COUNT(*) FROM downloads WHERE status='Active' or status='Paused'")
- fun getActiveDownloadsCount() : Flow
+ fun getActiveDownloadsCountFlow() : Flow
+
+ @Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
+ fun getDownloadsCountByStatus(status : List) : Flow
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
fun getActiveDownloadsList() : List
@@ -22,26 +28,41 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
fun getActiveAndQueuedDownloadsList() : List
+ @Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
+ fun getActiveAndQueuedDownloads() : Flow>
+
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' ORDER BY downloadStartTime, id")
- fun getQueuedDownloads() : Flow>
+ fun getQueuedDownloads() : PagingSource
+
+ @Query("SELECT format FROM downloads WHERE status='Queued' or status='QueuedPaused'")
+ fun getSelectedFormatFromQueued() : List
+
+ @Query("SELECT * FROM downloads WHERE downloadStartTime <= :currentTime and status='Queued' ORDER BY downloadStartTime, id LIMIT 20")
+ fun getQueuedDownloadsThatAreNotScheduledChunked(currentTime: Long) : Flow>
+
+ @Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' or status='Paused' ORDER BY downloadStartTime, id")
+ fun getQueuedAndPausedDownloads() : Flow>
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' ORDER BY downloadStartTime, id")
fun getQueuedDownloadsList() : List
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
- fun getCancelledDownloads() : Flow>
+ fun getCancelledDownloads() : PagingSource
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
fun getCancelledDownloadsList() : List
+ @Query("SELECT * FROM downloads WHERE status LIKE '%Paused%'")
+ fun getPausedDownloadsList() : List
+
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
- fun getErroredDownloads() : Flow>
+ fun getErroredDownloads() : PagingSource
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
fun getErroredDownloadsList() : List
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
- fun getSavedDownloads() : Flow>
+ fun getSavedDownloads() : PagingSource
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
fun getDownloadById(id: Long) : DownloadItem
@@ -52,6 +73,22 @@ interface DownloadDao {
@Query("SELECT status FROM downloads WHERE id=:id")
fun checkStatus(id: Long) : String
+ @Query("UPDATE downloads " +
+ "SET status = CASE " +
+ " WHEN status = 'Active' THEN 'Paused' " +
+ " WHEN status = 'Queued' THEN 'QueuedPaused' " +
+ " ELSE status " +
+ " END;")
+ fun pauseActiveAndQueued()
+
+ @Query("UPDATE downloads " +
+ "SET status = CASE " +
+ " WHEN status = 'Paused' THEN 'Active' " +
+ " WHEN status = 'QueuedPaused' THEN 'Queued' " +
+ " ELSE status " +
+ " END;")
+ fun unPauseActiveAndQueued()
+
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: DownloadItem) : Long
@@ -73,7 +110,7 @@ interface DownloadDao {
@Query("DELETE FROM downloads WHERE status='Saved'")
suspend fun deleteSaved()
- @Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued'")
+ @Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused'")
suspend fun cancelQueued()
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
@@ -82,6 +119,9 @@ interface DownloadDao {
@Upsert
suspend fun update(item: DownloadItem)
+ @Upsert
+ fun updateMultiple(items :List)
+
@Query("SELECT * FROM downloads ORDER BY id DESC LIMIT 1")
fun getLatest() : DownloadItem
@@ -94,4 +134,38 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE url=:url AND format=:format AND (status='Error' OR status='Cancelled') LIMIT 1")
fun getUnfinishedByURLAndFormat(url: String, format: String) : DownloadItem
+
+
+ @Query("SELECT COUNT(downloadStartTime) = 0 from downloads WHERE id in (:items) AND " +
+ "CASE :inverted " +
+ "WHEN :inverted = 'false' THEN downloadStartTime > :currentStartTime " +
+ "WHEN :inverted = 'true' THEN downloadStartTime < :currentStartTime ELSE downloadStartTime < -1 END")
+ fun checkAllQueuedItemsAreScheduledAfterNow(items: List, inverted: String, currentStartTime: Long) : Boolean
+
+
+ @Query("Select id from downloads where id not in (:list) and status in (:status)")
+ fun getDownloadIDsNotPresentInList(list: List, status: List) : List
+
+ @Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
+ fun resetScheduleTimeForItems(list: List)
+
+ @Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
+ fun reQueueDownloadItems(list: List)
+
+ @Transaction
+ fun putAtTopOfTheQueue(id: Long){
+ val downloads = getQueuedDownloadsList()
+ val newID = downloads.first().id
+
+ updateDownloadID(id, -id)
+ resetScheduleTimeForItems(listOf(-id))
+
+ downloads.reversed().dropWhile { it.id == id }.forEach {
+ updateDownloadID(it.id, it.id + 1)
+ }
+ updateDownloadID(-id, newID)
+ }
+
+ @Query("Update downloads set id=:newId where id=:id")
+ fun updateDownloadID(id: Long, newId: Long)
}
\ No newline at end of file
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 e0e38f8a..94bd2056 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
@@ -12,7 +12,7 @@ interface SearchHistoryDao {
fun getAllByKeyword(keyword: String) : List
@Insert(onConflict = OnConflictStrategy.IGNORE)
- suspend fun insert(new: SearchHistoryItem)
+ suspend fun insert(newItem: SearchHistoryItem)
@Query("DELETE FROM searchHistory")
suspend fun deleteAll()
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt
index 20e574d4..bc34fd41 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt
@@ -20,7 +20,7 @@ data class DownloadItem(
var container: String,
@ColumnInfo(defaultValue = "")
var downloadSections: String,
- val allFormats: ArrayList,
+ val allFormats: MutableList,
var downloadPath: String,
var website: String,
val downloadSize: String,
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/ResultItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/ResultItem.kt
index 8c44e532..4da30423 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/ResultItem.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/ResultItem.kt
@@ -15,9 +15,9 @@ data class ResultItem(
val thumb: String,
val website: String,
var playlistTitle: String,
- var formats: ArrayList,
+ var formats: MutableList,
@ColumnInfo(defaultValue = "")
var urls: String,
- var chapters: ArrayList?,
+ var chapters: MutableList?,
var creationTime: Long = System.currentTimeMillis() / 1000,
)
\ No newline at end of file
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 87c695c7..206acc30 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,19 +1,36 @@
package com.deniscerri.ytdlnis.database.repository
-import androidx.lifecycle.LiveData
-import com.deniscerri.ytdlnis.database.Converters
+import androidx.paging.Pager
+import androidx.paging.PagingConfig
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import kotlinx.coroutines.flow.Flow
+
class DownloadRepository(private val downloadDao: DownloadDao) {
- val allDownloads : Flow> = downloadDao.getAllDownloads()
+ val allDownloads : Pager = Pager(
+ config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
+ pagingSourceFactory = {downloadDao.getAllDownloads()}
+ )
val activeDownloads : Flow> = downloadDao.getActiveDownloads()
- val activeDownloadsCount : Flow = downloadDao.getActiveDownloadsCount()
- val queuedDownloads : Flow> = downloadDao.getQueuedDownloads()
- val cancelledDownloads : Flow> = downloadDao.getCancelledDownloads()
- val erroredDownloads : Flow> = downloadDao.getErroredDownloads()
- val savedDownloads : Flow> = downloadDao.getSavedDownloads()
+ val queuedDownloads : Pager = Pager(
+ config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
+ pagingSourceFactory = {downloadDao.getQueuedDownloads()}
+ )
+ val cancelledDownloads : Pager = Pager(
+ config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
+ pagingSourceFactory = {downloadDao.getCancelledDownloads()}
+ )
+ val erroredDownloads : Pager = Pager(
+ config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
+ pagingSourceFactory = {downloadDao.getErroredDownloads()}
+ )
+ val savedDownloads : Pager = Pager(
+ config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
+ pagingSourceFactory = {downloadDao.getSavedDownloads()}
+ )
+
+ val activeDownloadsCount : Flow = downloadDao.getActiveDownloadsCountFlow()
enum class Status {
Active, Paused, Queued, QueuedPaused, Error, Cancelled, Saved
@@ -27,8 +44,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
return downloadDao.insertAll(items)
}
- suspend fun delete(item: DownloadItem){
- downloadDao.delete(item.id)
+ suspend fun delete(id: Long){
+ downloadDao.delete(id)
}
suspend fun update(item: DownloadItem){
@@ -61,6 +78,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
return downloadDao.getCancelledDownloadsList()
}
+ fun getPausedDownloads() : List {
+ return downloadDao.getPausedDownloadsList()
+ }
+
fun getErroredDownloads() : List {
return downloadDao.getErroredDownloadsList()
}
@@ -81,20 +102,12 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.cancelQueued()
}
- fun getLastDownloadId() : Long {
- return downloadDao.getLastDownloadId()
+ fun pauseDownloads(){
+ downloadDao.pauseActiveAndQueued()
}
-
- fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long {
- val converters = Converters()
- val format = converters.formatToString(item.format)
- return try {
- val i = downloadDao.getUnfinishedByURLAndFormat(item.url, format)
- i.id
- }catch (e: Exception){
- 0L
- }
+ fun unPauseDownloads(){
+ downloadDao.unPauseActiveAndQueued()
}
}
\ 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 4d83af70..7e4b7cc8 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
@@ -12,23 +12,24 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
-import com.anggrayudi.storage.file.dataDirectory
+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.WebViewActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.serialization.json.Json
import java.io.File
+import java.util.Date
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
private val repository: CookieRepository
val items: LiveData>
- private val jsonFormat = Json { prettyPrint = true }
- private val cookieHeader = "# Netscape HTTP Cookie File\n" +
- "# WebView Generated by the YTDLnis app\n"
+ private val cookieHeader =
+ "# Netscape HTTP Cookie File\n" +
+ "# WebView Generated by the YTDLnis app\n" +
+ "# This is a generated file! Do not edit."
init {
val dao = DBManager.getInstance(application).cookieDao
@@ -72,8 +73,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
if (!hasCookies()) throw Exception("There is no cookies in the database!")
flush()
}
-
- val dbPath = File("/data/data/com.deniscerri.ytdl/").walkTopDown().find { it.name == "Cookies" }
+ val dbPath = File("/data/data/${BuildConfig.APPLICATION_ID}/").walkTopDown().find { it.name == "Cookies" }
?: throw Exception("Cookies File not found!")
SQLiteDatabase.openDatabase(
@@ -139,11 +139,12 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
var clip = clipboard.primaryClip!!.getItemAt(0).text
Log.e("Aaa", clip.toString())
- if (clip.startsWith(cookieHeader)){
+
+ if (clip.startsWith("# Netscape HTTP Cookie File")){
clip = clip.removePrefix(cookieHeader)
val cookie = CookieItem(
0,
- "Cookie Import [${System.currentTimeMillis()}]",
+ "Cookie Import at [${Date()}]",
clip.toString()
)
insert(cookie)
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 2145255e..66d54ad1 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
@@ -8,22 +8,20 @@ import android.content.res.Resources
import android.net.ConnectivityManager
import android.os.Looper
import android.util.DisplayMetrics
-import android.util.Log
import android.widget.Toast
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.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
import androidx.work.WorkManager
-import androidx.work.WorkRequest
-import androidx.work.workDataOf
+import androidx.work.await
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
@@ -44,18 +42,12 @@ import com.deniscerri.ytdlnis.work.DownloadWorker
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
import java.io.File
import java.util.Locale
-import java.util.UUID
import java.util.concurrent.TimeUnit
-import kotlin.random.Random
-import kotlin.random.nextInt
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
@@ -64,13 +56,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val sharedPreferences: SharedPreferences
private val commandTemplateDao: CommandTemplateDao
private val infoUtil : InfoUtil
- val allDownloads : LiveData>
- val queuedDownloads : LiveData>
+ val allDownloads : Flow>
+ val queuedDownloads : Flow>
val activeDownloads : LiveData>
val activeDownloadsCount : LiveData
- val cancelledDownloads : LiveData>
- val erroredDownloads : LiveData>
- val savedDownloads : LiveData>
+ val cancelledDownloads : Flow>
+ val erroredDownloads : Flow>
+ val savedDownloads : Flow>
private var bestVideoFormat : Format
private var bestAudioFormat : Format
@@ -81,6 +73,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val audioFormatIDPreference: List
private val resources : Resources
private var extraCommands: String = ""
+
+ private var audioContainer: String?
+ private var videoContainer: String?
+ private var videoCodec: String?
+
enum class Type {
audio, video, command
}
@@ -93,13 +90,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
infoUtil = InfoUtil(application)
- allDownloads = repository.allDownloads.asLiveData()
- queuedDownloads = repository.queuedDownloads.asLiveData()
+ allDownloads = repository.allDownloads.flow
+ queuedDownloads = repository.queuedDownloads.flow
activeDownloads = repository.activeDownloads.asLiveData()
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
- savedDownloads = repository.savedDownloads.asLiveData()
- cancelledDownloads = repository.cancelledDownloads.asLiveData()
- erroredDownloads = repository.erroredDownloads.asLiveData()
+ savedDownloads = repository.savedDownloads.flow
+ cancelledDownloads = repository.cancelledDownloads.flow
+ erroredDownloads = repository.erroredDownloads.flow
viewModelScope.launch(Dispatchers.IO){
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands = commandTemplateDao.getAllTemplatesAsExtraCommands().joinToString(" ")
@@ -107,8 +104,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
- formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",")
- audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",")
+ formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",").filter { it.isNotEmpty() }
+ audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
val confTmp = Configuration(application.resources.configuration)
confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!)
@@ -117,7 +114,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
- val videoContainer = sharedPreferences.getString("video_format", "Default")
+ videoContainer = sharedPreferences.getString("video_format", "Default")
defaultVideoFormats = mutableListOf()
videoFormatValues.forEach {
@@ -132,29 +129,55 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
)
defaultVideoFormats.add(tmp)
}
+ formatIDPreference.reversed().forEach {
+ val tmp = Format(
+ it,
+ videoContainer!!,
+ "",
+ "",
+ "",
+ 0,
+ it
+ )
+ defaultVideoFormats.add(tmp)
+ }
bestVideoFormat = defaultVideoFormats.last()
- val audioContainer = sharedPreferences.getString("audio_format", "mp3")
- bestAudioFormat = Format(
- "best",
- audioContainer!!,
- "",
- "",
- "",
- 0,
- "best"
- )
+ audioContainer = sharedPreferences.getString("audio_format", "mp3")
+ bestAudioFormat = if (audioFormatIDPreference.isEmpty()){
+ Format(
+ "best",
+ audioContainer!!,
+ "",
+ "",
+ "",
+ 0,
+ "best"
+ )
+ }else{
+ Format(
+ audioFormatIDPreference.first(),
+ audioContainer!!,
+ "",
+ "",
+ "",
+ 0,
+ audioFormatIDPreference.first()
+ )
+ }
+
+ videoCodec = sharedPreferences.getString("video_codec", "")
}
- fun deleteDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO) {
- repository.delete(item)
+ fun deleteDownload(id: Long) = viewModelScope.launch(Dispatchers.IO) {
+ repository.delete(id)
}
suspend fun updateDownload(item: DownloadItem){
if (sharedPreferences.getBoolean("incognito", false)){
if (item.status == DownloadRepository.Status.Cancelled.toString() || item.status == DownloadRepository.Status.Error.toString()){
- repository.delete(item)
+ repository.delete(item.id)
}
}else{
repository.update(item)
@@ -345,7 +368,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return cloneFormat (
try {
try{
- formats.first { it.format_note.contains("audio", ignoreCase = true) && audioFormatIDPreference.contains(it.format_id)}
+ formats.filter { it.format_note.contains("audio", ignoreCase = true)}.first {
+ audioFormatIDPreference.contains(it.format_id) ||
+ it.container == audioContainer
+ }
}catch (e: Exception){
formats.last { it.format_note.contains("audio", ignoreCase = true) }
}
@@ -358,24 +384,19 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Type.video -> {
return cloneFormat(
try {
- val theFormats = formats.ifEmpty { defaultVideoFormats }
- try {
- formats.first { !it.format_note.contains("audio", ignoreCase = true) && formatIDPreference.contains(it.format_id)}
- }catch (e: Exception){
- when (videoQualityPreference) {
- "worst" -> {
- theFormats.first {!it.format_note.contains("audio", ignoreCase = true) }
- }
- "best" -> {
- theFormats.last {!it.format_note.contains("audio", ignoreCase = true) }
- }
- else -> {
- try{
- theFormats.last {it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
- }catch (e: Exception){
- theFormats.last { !it.format_note.contains("audio", ignoreCase = true) }
- }
- }
+ val theFormats = formats.filter { !it.format_note.contains("audio", true) }.ifEmpty { defaultVideoFormats }
+ when (videoQualityPreference) {
+ "worst" -> {
+ theFormats.first()
+ }
+ else /*best*/ -> {
+ val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
+ requirements.add { it: Format -> formatIDPreference.contains(it.format_id) }
+ requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
+ requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
+ requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
+
+ theFormats.reversed().maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
}
}
}catch (e: Exception){
@@ -406,6 +427,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val audioFormats = resources.getStringArray(R.array.audio_formats)
val formats = mutableListOf()
val containerPreference = sharedPreferences.getString("audio_format", "")
+ audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
return formats
}
@@ -414,6 +436,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val videoFormats = resources.getStringArray(R.array.video_formats_values)
val formats = mutableListOf()
val containerPreference = sharedPreferences.getString("video_format", "")
+ formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
return formats
}
@@ -436,6 +459,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
repository.insert(item)
}
+ fun pauseDownloads() = viewModelScope.launch(Dispatchers.IO){
+ repository.pauseDownloads()
+ }
+
+ fun unPauseDownloads() = viewModelScope.launch(Dispatchers.IO){
+ repository.unPauseDownloads()
+ }
+
fun insertAll(items: List)= viewModelScope.launch(Dispatchers.IO){
items.forEach{
repository.insert(it)
@@ -466,6 +497,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return repository.getCancelledDownloads()
}
+ fun getPausedDownloads() : List {
+ return repository.getPausedDownloads()
+ }
+
fun getErrored() : List {
return repository.getErroredDownloads()
}
@@ -483,10 +518,24 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return repository.getActiveAndQueuedDownloads()
}
+ suspend fun resetScheduleTimeForItemsAndStartDownload(items: List) = CoroutineScope(Dispatchers.IO).launch {
+ dbManager.downloadDao.resetScheduleTimeForItems(items)
+ startDownloadWorker(emptyList())
+ }
+
+ suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{
+ dbManager.downloadDao.putAtTopOfTheQueue(id)
+ startDownloadWorker(emptyList())
+ }
+
+ suspend fun reQueueDownloadItems(items: List) = CoroutineScope(Dispatchers.IO).launch {
+ dbManager.downloadDao.reQueueDownloadItems(items)
+ startDownloadWorker(emptyList())
+ }
+
suspend fun queueDownloads(items: List) = CoroutineScope(Dispatchers.IO).launch {
val context = App.instance
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
- val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
val queuedItems = mutableListOf()
var exists = false
@@ -518,33 +567,50 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
}
+ if (queuedItems.isNotEmpty()){
+ startDownloadWorker(queuedItems)
+ }
+ }
+
+ private suspend fun startDownloadWorker(queuedItems: List) {
+ val context = App.instance
+ val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
val workManager = WorkManager.getInstance(context)
- queuedItems.forEach {
+ 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 = if (it.downloadStartTime != 0L){
- it.downloadStartTime - currentTime
- } else 0
- if (delay < 0L) delay = 1L
+ var delay = 1000L
+ if (queuedItems.isNotEmpty()){
+ delay = if (queuedItems[0].downloadStartTime != 0L){
+ queuedItems[0].downloadStartTime.minus(currentTime)
+ } else 0
+ if (delay <= 0L) delay = 1000L
+ }
+
val workConstraints = Constraints.Builder()
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
val workRequest = OneTimeWorkRequestBuilder()
- .setInputData(Data.Builder().putLong("id", it.id).build())
- .addTag(it.id.toString())
.addTag("download")
.setConstraints(workConstraints.build())
- .setInitialDelay(delay, TimeUnit.SECONDS)
- .build()
+ .setInitialDelay(delay, TimeUnit.MILLISECONDS)
+
+ if (delay > 1000L){
+ queuedItems.forEach {
+ workRequest.addTag(it.id.toString())
+ }
+ }
workManager.enqueueUniqueWork(
- it.id.toString(),
+ System.currentTimeMillis().toString(),
ExistingWorkPolicy.REPLACE,
- workRequest
+ workRequest.build()
)
- }
+ }
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
if (!allowMeteredNetworks && isCurrentNetworkMetered){
@@ -553,12 +619,29 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
}
- items.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
+ 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){}
}
}
+
+ fun getQueuedCollectedFileSize() : Long {
+ return dbManager.downloadDao.getSelectedFormatFromQueued().sumOf { it.filesize }
+ }
+
+ fun getTotalSize(status: List) : LiveData {
+ return dbManager.downloadDao.getDownloadsCountByStatus(status.map { it.toString() }).asLiveData()
+ }
+
+ fun checkAllQueuedItemsAreScheduledAfterNow(items: List, inverted: Boolean, currentStartTime: Long) : Boolean {
+ return dbManager.downloadDao.checkAllQueuedItemsAreScheduledAfterNow(items, inverted.toString(), currentStartTime)
+ }
+
+ fun getItemIDsNotPresentIn(items: List, status: List) : List {
+ return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() })
+ }
+
private fun updateDownloadItem(
downloadItem: DownloadItem,
infoUtil: InfoUtil,
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 0088aca4..9b0b2e08 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
@@ -28,10 +28,13 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
val items : LiveData>
val loadingItems = MutableLiveData()
private val sharedPreferences: SharedPreferences
+ var state: ResultsState = ResultsState.IDLE
+ enum class ResultsState {
+ PROCESSING, IDLE
+ }
init {
val dao = DBManager.getInstance(application).resultDao
- val commandDao = DBManager.getInstance(application).commandTemplateDao
repository = ResultRepository(dao, getApplication().applicationContext)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults.asLiveData()
@@ -61,6 +64,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
}
}
suspend fun parseQueries(inputQueries: List){
+ state = ResultsState.PROCESSING
if (inputQueries.size == 1){
parseQuery(inputQueries[0], true)
}else {
@@ -69,6 +73,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
inputQueries.forEach {
parseQuery(it, false)
}
+ state = ResultsState.IDLE
loadingItems.postValue(false)
}
}
@@ -97,7 +102,10 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
} catch (e: Exception) {
Log.e(tag, e.toString())
}
- if (resetResults) loadingItems.postValue(false)
+ if (resetResults) {
+ state = ResultsState.IDLE
+ loadingItems.postValue(false)
+ }
res
}
}
@@ -107,7 +115,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
val m = p.matcher(inputQuery)
if (m.find()) {
type = "YT_Video"
- if (inputQuery.contains("playlist?list=")) {
+ if (inputQuery.contains("playlist?list=") && !inputQuery.contains("music.youtu")) {
type = "YT_Playlist"
}
} else if (inputQuery.contains("http")) {
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 5dbe0619..8cf273bc 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt
@@ -20,10 +20,8 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
if (message != null) {
runCatching {
val notificationUtil = NotificationUtil(c)
- WorkManager.getInstance(c).cancelUniqueWork(id.toString())
- notificationUtil.cancelDownloadNotification(id)
YoutubeDL.getInstance().destroyProcessById(id.toString())
-
+ notificationUtil.cancelDownloadNotification(id)
val dbManager = DBManager.getInstance(c)
CoroutineScope(Dispatchers.IO).launch{
runCatching {
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 bc816fd5..b1fb0ecc 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/PauseDownloadNotificationReceiver.kt
@@ -3,8 +3,13 @@ package com.deniscerri.ytdlnis.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import com.deniscerri.ytdlnis.database.DBManager
+import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {
@@ -16,6 +21,14 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() {
notificationUtil.cancelDownloadNotification(id)
YoutubeDL.getInstance().destroyProcessById(id.toString())
notificationUtil.createResumeDownload(id, title, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
+ val dbManager = DBManager.getInstance(c)
+ CoroutineScope(Dispatchers.IO).launch{
+ runCatching {
+ val item = dbManager.downloadDao.getDownloadById(id.toLong())
+ item.status = DownloadRepository.Status.Paused.toString()
+ dbManager.downloadDao.update(item)
+ }
+ }
}
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
index 8255ff22..bbca7fe9 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
@@ -32,7 +32,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
-import com.deniscerri.ytdlnis.ui.downloadcard.SelectPlaylistItemsBottomSheetDialog
+import com.deniscerri.ytdlnis.ui.downloadcard.SelectPlaylistItemsDialog
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -95,10 +95,9 @@ class ShareActivity : BaseActivity() {
askPermissions()
val action = intent.action
- val type = intent.clipData
Log.e("aa", intent.toString())
- if (Intent.ACTION_SEND == action && type != null) {
- if (intent.getStringExtra(Intent.EXTRA_TEXT) == null){
+ if (Intent.ACTION_SEND == action || Intent.ACTION_VIEW == action) {
+ if (intent.getStringExtra(Intent.EXTRA_TEXT) == null && Intent.ACTION_SEND == action){
intent.setClass(this, MainActivity::class.java)
startActivity(intent)
finishAffinity()
@@ -107,7 +106,7 @@ class ShareActivity : BaseActivity() {
runCatching { supportFragmentManager.popBackStack() }
- quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false))
+ quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
val loadingBottomSheet = BottomSheetDialog(this)
loadingBottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
@@ -118,7 +117,11 @@ class ShareActivity : BaseActivity() {
this.finish()
}
- val inputQuery = intent.getStringExtra(Intent.EXTRA_TEXT)!!
+ val inputQuery = when(action){
+ Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
+ else -> intent.dataString!!
+ }
+
lifecycleScope.launch {
try {
val result = resultViewModel.getItemByURL(inputQuery)
@@ -166,7 +169,7 @@ class ShareActivity : BaseActivity() {
private fun showSelectPlaylistItems(it: List){
if (sharedPreferences.getBoolean("download_card", true)){
- val bottomSheet = SelectPlaylistItemsBottomSheetDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
+ val bottomSheet = SelectPlaylistItemsDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
bottomSheet.show(supportFragmentManager, "downloadPlaylistSheet")
}else{
lifecycleScope.launch(Dispatchers.IO){
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 512b4c38..37ec610f 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
@@ -6,6 +6,8 @@ import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.res.ColorStateList
import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.OvalShape
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -40,6 +42,8 @@ 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.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
+import com.devbrackets.android.exomedia.core.video.mp.NativeVideoDelegate
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
@@ -55,6 +59,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.util.*
@@ -146,6 +151,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
recyclerView = view.findViewById(R.id.recyclerViewHome)
recyclerView?.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
recyclerView?.adapter = homeAdapter
+ recyclerView?.enableFastScroll()
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
resultViewModel.items.observe(viewLifecycleOwner) {
@@ -251,12 +257,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
override fun onResume() {
super.onResume()
if(arguments?.getString("url") == null){
- resultViewModel.checkTrending()
+ if (resultViewModel.state == ResultViewModel.ResultsState.IDLE){
+ resultViewModel.checkTrending()
+ }else{
+ resultViewModel.loadingItems.postValue(true)
+ }
}else{
- arguments
arguments?.remove("url")
- arguments?.remove("showDownloadsWithUpdatedFormats")
}
+ arguments?.remove("showDownloadsWithUpdatedFormats")
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
lifecycleScope.launch {
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
@@ -533,7 +542,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
resultViewModel.deleteAll()
lifecycleScope.launch(Dispatchers.IO){
Thread.sleep(300)
- if(sharedPreferences!!.getBoolean("quick_download", false)){
+ if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){
if (queryList.size == 1 && queryList.first().matches("(https?://(?:www\\.|(?!www))[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|www\\.[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|https?://(?:www\\.|(?!www))[a-zA-Z\\d]+\\.\\S{2,}|www\\.[a-zA-Z\\d]+\\.\\S{2,})".toRegex())){
val resultItem = downloadViewModel.createEmptyResultItem(queryList.first())
if (sharedPreferences!!.getBoolean("download_card", true)) {
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 41ee3576..eb72b99f 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
@@ -37,7 +37,7 @@ import java.util.*
import java.util.regex.Pattern
-class AddExtraCommandsDialog(private val item: DownloadItem, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
+class AddExtraCommandsDialog(private val item: DownloadItem?, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
private lateinit var infoUtil: InfoUtil
private lateinit var sharedPreferences: SharedPreferences
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
@@ -86,9 +86,14 @@ class AddExtraCommandsDialog(private val item: DownloadItem, private val callbac
val text = view.findViewById(R.id.command)
val templates = view.findViewById