From 2371ab734dd198dce67c1bb324d6056c2b9b5460 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Denis=20=C3=87erri?=
<64997243+deniscerri@users.noreply.github.com>
Date: Sat, 21 Jan 2023 16:51:22 +0100
Subject: [PATCH] added other download cards and recyclerview for cards to be
queued for download
---
.idea/assetWizardSettings.xml | 4 +-
.../1.json | 24 +-
.../main/java/com/deniscerri/ytdlnis/App.kt | 16 +-
.../deniscerri/ytdlnis/DownloaderService.kt | 17 +-
.../ConfigureMultipleDownloadsAdapter.kt | 125 +++++++
.../ytdlnis/database/dao/DownloadDao.kt | 5 +-
.../ytdlnis/database/models/DownloadItem.kt | 4 +-
.../database/repository/DownloadRepository.kt | 16 +-
.../ytdlnis/ui/settings/SettingsFragment.kt | 10 +
.../deniscerri/ytdlnis/work/DownloadWorker.kt | 326 +++++++++---------
.../res/drawable/ic_concurrent_downloads.xml | 6 +
app/src/main/res/layout/fragment_home.xml | 3 -
.../home_download_audio_bottom_sheet.xml | 90 -----
...> home_download_multiple_bottom_sheet.xml} | 31 +-
.../layout/home_download_multiple_card.xml | 163 +++++++++
.../res/layout/home_download_settings.xml | 60 ++++
.../home_download_settings_components.xml | 242 +++++++++++++
.../home_download_single_bottom_sheet.xml | 237 +------------
.../home_download_video_bottom_sheet.xml | 188 ----------
app/src/main/res/values-tr-rTR/strings.xml | 5 -
app/src/main/res/values/strings.xml | 2 +
app/src/main/res/xml/root_preferences.xml | 11 +
22 files changed, 850 insertions(+), 735 deletions(-)
create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt
create mode 100644 app/src/main/res/drawable/ic_concurrent_downloads.xml
delete mode 100644 app/src/main/res/layout/home_download_audio_bottom_sheet.xml
rename app/src/main/res/layout/{home_download_all_bottom_sheet.xml => home_download_multiple_bottom_sheet.xml} (76%)
create mode 100644 app/src/main/res/layout/home_download_multiple_card.xml
create mode 100644 app/src/main/res/layout/home_download_settings.xml
create mode 100644 app/src/main/res/layout/home_download_settings_components.xml
delete mode 100644 app/src/main/res/layout/home_download_video_bottom_sheet.xml
diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml
index a53c62e8..4f9550fb 100644
--- a/.idea/assetWizardSettings.xml
+++ b/.idea/assetWizardSettings.xml
@@ -320,7 +320,7 @@
@@ -331,7 +331,7 @@
diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json
index 5fe6d706..cc350343 100644
--- a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json
+++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/1.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
- "identityHash": "6c48acc2155f9a895971bd84c84b7d4e",
+ "identityHash": "5939be553c41327f4f64e1d62d008968",
"entities": [
{
"tableName": "results",
@@ -198,7 +198,7 @@
},
{
"tableName": "downloads",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `audioQualityId` TEXT NOT NULL, `videoQualityId` TEXT NOT NULL, `customTemplateId` INTEGER NOT NULL, `audioFormat` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `formatDesc` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `workID` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `audioQualityId` TEXT NOT NULL, `videoQualityId` TEXT NOT NULL, `customTemplateId` INTEGER NOT NULL, `audioFormat` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `formatDesc` TEXT NOT NULL, `removeAudio` INTEGER NOT NULL DEFAULT 0, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `embedSubs` INTEGER NOT NULL, `addChapters` INTEGER NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `workID` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT)",
"fields": [
{
"fieldPath": "url",
@@ -303,6 +303,24 @@
"affinity": "TEXT",
"notNull": true
},
+ {
+ "fieldPath": "embedSubs",
+ "columnName": "embedSubs",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "addChapters",
+ "columnName": "addChapters",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "SaveThumb",
+ "columnName": "SaveThumb",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
{
"fieldPath": "status",
"columnName": "status",
@@ -368,7 +386,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6c48acc2155f9a895971bd84c84b7d4e')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5939be553c41327f4f64e1d62d008968')"
]
}
}
\ 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 c8cddd94..708c5679 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/App.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/App.kt
@@ -9,6 +9,8 @@ import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.preference.PreferenceManager
+import androidx.work.Configuration
+import androidx.work.WorkManager
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.color.DynamicColors
@@ -23,12 +25,12 @@ import io.reactivex.observers.DisposableCompletableObserver
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import java.io.File
+import java.util.concurrent.Executors
class App : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
- notificationUtil = NotificationUtil(this)
createNotificationChannels()
PreferenceManager.setDefaultValues(
this,
@@ -37,6 +39,15 @@ class App : Application() {
R.xml.root_preferences,
false
)
+
+ WorkManager.initialize(
+ this@App,
+ Configuration.Builder()
+ .setExecutor(Executors.newFixedThreadPool(
+ getSharedPreferences("root_preferences", MODE_PRIVATE)
+ .getInt("concurrent_downloads", 1)))
+ .build())
+
configureRxJavaErrorHandler()
Completable.fromAction { initLibraries() }.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -79,12 +90,11 @@ class App : Application() {
}
private fun createNotificationChannels() {
+ val notificationUtil = NotificationUtil(this)
notificationUtil.createNotificationChannel()
}
companion object {
private const val TAG = "App"
- @SuppressLint("StaticFieldLeak")
- lateinit var notificationUtil: NotificationUtil
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt b/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt
index e031014e..a956a58c 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt
@@ -9,10 +9,7 @@ import android.os.Build
import android.os.IBinder
import android.os.SystemClock
import android.util.Log
-import androidx.work.ExistingWorkPolicy
-import androidx.work.OneTimeWorkRequestBuilder
-import androidx.work.WorkManager
-import androidx.work.workDataOf
+import androidx.work.*
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.service.DownloadInfo
import com.deniscerri.ytdlnis.service.IDownloaderListener
@@ -32,6 +29,7 @@ import io.reactivex.schedulers.Schedulers
import java.io.File
import java.util.*
import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.Executors
import java.util.regex.Pattern
@@ -43,7 +41,6 @@ class DownloaderService : Service() {
private val downloadInfo = DownloadInfo()
private var downloadQueue = LinkedList()
private val compositeDisposable = CompositeDisposable()
- private val notificationUtil = App.notificationUtil
private var context: Context? = null
var downloadProcessID = "processID"
private var downloadNotificationID = 0
@@ -59,11 +56,11 @@ class DownloaderService : Service() {
if (!downloadQueue.isEmpty()) {
title = downloadQueue.peek()?.title
}
- notificationUtil.updateDownloadNotification(
- downloadNotificationID,
- line!!, progress.toInt(), downloadQueue.size, title,
- notificationChannelID
- )
+// notificationUtil.updateDownloadNotification(
+// downloadNotificationID,
+// line!!, progress.toInt(), downloadQueue.size, title,
+// notificationChannelID
+// )
try {
for (activity in activities.keys) {
activity.runOnUiThread {
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt
new file mode 100644
index 00000000..f93e54f6
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ConfigureMultipleDownloadsAdapter.kt
@@ -0,0 +1,125 @@
+package com.deniscerri.ytdlnis.adapter
+
+import android.app.Activity
+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.core.content.ContextCompat
+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.google.android.material.button.MaterialButton
+import com.google.android.material.card.MaterialCardView
+import com.squareup.picasso.Picasso
+
+class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) :
+ ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
+ private val onItemClickListener: OnItemClickListener
+ private val activity: Activity
+
+ init {
+ this.onItemClickListener = onItemClickListener
+ this.activity = activity
+ }
+
+ class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) :
+ RecyclerView.ViewHolder(itemView) {
+ val cardView: MaterialCardView
+
+ init {
+ cardView = itemView.findViewById(R.id.download_multiple_card_view)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val cardView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.home_download_multiple_card, parent, false)
+ return ViewHolder(cardView, onItemClickListener)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = getItem(position)
+ val card = holder.cardView
+ // THUMBNAIL ----------------------------------
+ val thumbnail = card.findViewById(R.id.result_image_view)
+ val imageURL = item!!.thumb
+ if (imageURL.isNotEmpty()) {
+ val uiHandler = Handler(Looper.getMainLooper())
+ uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
+ thumbnail.setColorFilter(Color.argb(70, 0, 0, 0))
+ } else {
+ val uiHandler = Handler(Looper.getMainLooper())
+ uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
+ thumbnail.setColorFilter(Color.argb(70, 0, 0, 0))
+ }
+
+ // TITLE ----------------------------------
+ val itemTitle = card.findViewById(R.id.result_title)
+ var title = item.title
+ if (title.length > 100) {
+ title = title.substring(0, 40) + "..."
+ }
+ itemTitle.text = title
+
+ // Author ----------------------------------
+ val author = card.findViewById(R.id.author)
+ author.text = item.author
+
+ // Format
+ val format = card.findViewById(R.id.format)
+ format.text = item.ext
+
+ // Quality
+ val quality = card.findViewById(R.id.quality)
+ quality.text = item.formatDesc
+
+ // File Size
+ val fileSize = card.findViewById(R.id.filesize)
+ fileSize.text = item.filesize
+
+ // Type Icon Button
+ val btn = card.findViewById(R.id.download_type)
+
+ when(item.type) {
+ "audio" -> {
+ btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_music)
+ }
+ "video" -> {
+ btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_video)
+ }
+ "command" -> {
+ btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_baseline_keyboard_arrow_right_24)
+ }
+ }
+
+ card.setOnClickListener {
+ onItemClickListener.onCardClick(item.id!!)
+ }
+ }
+
+
+ interface OnItemClickListener {
+ fun onCardClick(itemID: Int)
+ }
+
+ companion object {
+ private val DIFF_CALLBACK: DiffUtil.ItemCallback =
+ object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
+ return oldItem.id === newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
+ return oldItem.url == newItem.url
+ }
+ }
+ }
+}
\ No newline at end of file
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 ea049c8b..34dd371d 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
@@ -15,8 +15,11 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloads() : LiveData>
+ @Query("SELECT * FROM downloads WHERE status='Queued'")
+ fun getQueuedDownloads() : List
+
@Query("SELECT * FROM downloads WHERE workID=:workID")
- fun getDownloadsByWorkId(workID: Int) : List
+ fun getDownloadByWorkId(workID: Int) : DownloadItem
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
fun getDownloadById(id: Int) : DownloadItem
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 073609b2..75848851 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
@@ -27,8 +27,10 @@ data class DownloadItem(
val embedSubs: Boolean,
val addChapters: Boolean,
val SaveThumb: Boolean,
+ var ext: String,
+ var filesize: String,
@ColumnInfo(defaultValue = "Queued")
- val status: String,
+ var status: String,
val workID: Int
){
@PrimaryKey(autoGenerate = true)
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 0005803d..b06bbfc3 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
@@ -8,6 +8,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
val allDownloads : LiveData> = downloadDao.getAllDownloads()
val activeDownloads : LiveData> = downloadDao.getActiveDownloads()
+ enum class status {
+ Active, Queued, Errored
+ }
+
suspend fun insert(item: DownloadItem){
downloadDao.insert(item)
}
@@ -20,9 +24,17 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.update(item)
}
- fun getDownloadsByWorkID(workID: Int) : List{
- return downloadDao.getDownloadsByWorkId(workID)
+ fun getDownloadByWorkID(workID: Int) : DownloadItem{
+ return downloadDao.getDownloadByWorkId(workID)
}
+ fun getQueuedDownloads() : List{
+ return downloadDao.getQueuedDownloads()
+ }
+
+ suspend fun setDownloadStatus(item: DownloadItem, status: status){
+ item.status = status.toString()
+ update(item);
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/settings/SettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/settings/SettingsFragment.kt
index ee8936d3..4ff4aece 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/settings/SettingsFragment.kt
@@ -20,6 +20,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var downloadCard: SwitchPreferenceCompat? = null
private var apiKey: EditTextPreference? = null
private var concurrentFragments: SeekBarPreference? = null
+ private var concurrentDownloads: SeekBarPreference? = null
private var limitRate: EditTextPreference? = null
private var aria2: SwitchPreferenceCompat? = null
private var sponsorblockFilters: MultiSelectListPreference? = null
@@ -55,6 +56,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
downloadCard = findPreference("download_card")
apiKey = findPreference("api_key")
concurrentFragments = findPreference("concurrent_fragments")
+ concurrentDownloads = findPreference("concurrent_downloads")
limitRate = findPreference("limit_rate")
aria2 = findPreference("aria2")
sponsorblockFilters = findPreference("sponsorblock_filter")
@@ -83,6 +85,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.putBoolean("download_card", downloadCard!!.isChecked)
editor.putString("api_key", apiKey!!.text)
editor.putInt("concurrent_fragments", concurrentFragments!!.value)
+ editor.putInt("concurrent_downloads", concurrentDownloads!!.value)
editor.putString("limit_rate", limitRate!!.text)
editor.putBoolean("aria2", aria2!!.isChecked)
editor.putStringSet("sponsorblock_filters", sponsorblockFilters!!.values)
@@ -159,6 +162,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.apply()
true
}
+ concurrentDownloads!!.onPreferenceChangeListener =
+ Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
+ val value = newValue.toString().toInt()
+ editor.putInt("concurrent_downloads", value)
+ editor.apply()
+ true
+ }
limitRate!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
editor.putString("limit_rate", newValue.toString())
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
index b7f7015e..345b1836 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
@@ -9,21 +9,21 @@ import android.os.SystemClock
import android.provider.DocumentsContract
import android.util.Log
import android.widget.Toast
-import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
+import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
-import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
+import kotlinx.coroutines.runBlocking
import java.io.File
import java.util.regex.Pattern
@@ -31,8 +31,8 @@ import java.util.regex.Pattern
class DownloadWorker(
private val context: Context,
workerParams: WorkerParameters
-) : CoroutineWorker(context, workerParams) {
- override suspend fun doWork(): Result {
+) : Worker(context, workerParams) {
+ override fun doWork(): Result {
workID = inputData.getInt("workID", SystemClock.uptimeMillis().toInt())
val notificationUtil = NotificationUtil(context)
val dbManager = DBManager.getInstance(context)
@@ -41,183 +41,182 @@ class DownloadWorker(
val commandTemplateDao = dbManager.commandTemplateDao
val historyDao = dbManager.historyDao
- queue = repository.getDownloadsByWorkID(workID) as MutableList
-
+ val downloadItem = repository.getDownloadByWorkID(workID)
+ runBlocking{
+ repository.setDownloadStatus(downloadItem, DownloadRepository.status.Active)
+ }
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
- val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, queue[0].title, workID, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
+ val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, workID, NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
val foregroundInfo = ForegroundInfo(workID, notification)
- setForeground(foregroundInfo)
+ setForegroundAsync(foregroundInfo)
- while (queue.isNotEmpty()){
- val item = dao.getDownloadById(queue[0].id!!)
- queue.removeFirst()
- val url = item.url
- val request = YoutubeDLRequest(url)
- val type = item.type
- val downloadLocation = item.downloadPath
+
+ val url = downloadItem.url
+ val request = YoutubeDLRequest(url)
+ val type = downloadItem.type
+ val downloadLocation = downloadItem.downloadPath
- val tempFolder = StringBuilder(context.cacheDir.absolutePath + """/${item.title}##${item.type}""")
- when(type){
- "audio" -> tempFolder.append("##${item.audioQualityId}")
- "video" -> tempFolder.append("##${item.videoQualityId}")
- "command" -> tempFolder.append("##${item.customTemplateId}")
+ val tempFolder = StringBuilder(context.cacheDir.absolutePath + """/${downloadItem.title}##${downloadItem.type}""")
+ when(type){
+ "audio" -> tempFolder.append("##${downloadItem.audioQualityId}")
+ "video" -> tempFolder.append("##${downloadItem.videoQualityId}")
+ "command" -> tempFolder.append("##${downloadItem.customTemplateId}")
+ }
+ var tempFileDir = File(tempFolder.toString())
+ tempFileDir.delete()
+ tempFileDir.mkdir()
+
+ val sharedPreferences = context.getSharedPreferences("root_preferences",
+ Service.MODE_PRIVATE
+ )
+ val aria2 = sharedPreferences.getBoolean("aria2", false)
+ if (aria2) {
+ request.addOption("--downloader", "libaria2c.so")
+ request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
+ } else {
+ val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
+ if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
+ }
+ val limitRate = sharedPreferences.getString("limit_rate", "")
+ if (limitRate != "") request.addOption("-r", limitRate!!)
+ val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
+ if (writeThumbnail) {
+ request.addOption("--write-thumbnail")
+ request.addOption("--convert-thumbnails", "png")
+ }
+ request.addOption("--no-mtime")
+ val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
+ if (sponsorBlockFilters!!.isNotEmpty()) {
+ val filters = java.lang.String.join(",", sponsorBlockFilters)
+ request.addOption("--sponsorblock-remove", filters)
+ }
+
+ when(type){
+ "audio" -> {
+ request.addOption("-x")
+ var audioQualityId : String = downloadItem.audioQualityId
+ if (audioQualityId == "0") audioQualityId = "ba"
+ var format = downloadItem.audioFormat
+ if (format.isEmpty()){
+ format = sharedPreferences.getString("audio_format", "")!!
+ }
+ request.addOption("-f", audioQualityId)
+ request.addOption("--audio-format", format)
+ request.addOption("--embed-metadata")
+
+ val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
+ if (embedThumb) {
+ request.addOption("--embed-thumbnail")
+ request.addOption("--convert-thumbnails", "png")
+ try {
+ val config = File(context.cacheDir, "config" + downloadItem.title + "##" + downloadItem.audioQualityId + ".txt")
+ val configData =
+ "--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
+ config.writeText(configData)
+ request.addOption("--config", config.absolutePath)
+ } catch (ignored: Exception) {}
+ }
+ request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
+ request.addCommands(listOf("--replace-in-metadata", "title", ".*.", downloadItem.title))
+ request.addCommands(listOf("--replace-in-metadata", "uploader", ".*.", downloadItem.author))
+
+ if (downloadItem.playlistTitle.isNotEmpty()) {
+ request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
+ request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s")
+ } else {
+ request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
+ }
+ request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
}
- var tempFileDir = File(tempFolder.toString())
- tempFileDir.delete()
- tempFileDir.mkdir()
+ "video" -> {
+ if (downloadLocation == context.getString(R.string.video_path)){
+ tempFileDir = File(context.getString(R.string.video_path))
+ }
- val sharedPreferences = context.getSharedPreferences("root_preferences",
- Service.MODE_PRIVATE
- )
- val aria2 = sharedPreferences.getBoolean("aria2", false)
- if (aria2) {
- request.addOption("--downloader", "libaria2c.so")
- request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
- } else {
- val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
- if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
- }
- val limitRate = sharedPreferences.getString("limit_rate", "")
- if (limitRate != "") request.addOption("-r", limitRate!!)
- val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
- if (writeThumbnail) {
- request.addOption("--write-thumbnail")
- request.addOption("--convert-thumbnails", "png")
- }
- request.addOption("--no-mtime")
- val sponsorBlockFilters = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
- if (sponsorBlockFilters!!.isNotEmpty()) {
- val filters = java.lang.String.join(",", sponsorBlockFilters)
- request.addOption("--sponsorblock-remove", filters)
- }
-
- when(type){
- "audio" -> {
- request.addOption("-x")
- var audioQualityId : String = item.audioQualityId
- if (audioQualityId == "0") audioQualityId = "ba"
- var format = item.audioFormat
- if (format.isEmpty()){
- format = sharedPreferences.getString("audio_format", "")!!
- }
- request.addOption("-f", audioQualityId)
- request.addOption("--audio-format", format)
- request.addOption("--embed-metadata")
+ val addChapters = sharedPreferences.getBoolean("add_chapters", false)
+ if (addChapters) {
+ request.addOption("--sponsorblock-mark", "all")
+ }
+ val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
+ if (embedSubs) {
+ request.addOption("--embed-subs", "")
+ }
+ var videoQualityId = downloadItem.videoQualityId
+ val audioQualityId = downloadItem.audioQualityId
+ if (videoQualityId.isEmpty()) videoQualityId = "bestvideo"
+ val formatArgument = StringBuilder(videoQualityId)
+ if (videoQualityId != "worst"){
+ if (audioQualityId != "0") formatArgument.append("+", audioQualityId, "/best")
+ }
+ request.addOption("-f", formatArgument.toString())
+ var format = downloadItem.videoFormat
+ if (format.isNotEmpty()) {
+ format = sharedPreferences.getString("video_format", "")!!
+ if (format != "DEFAULT") request.addOption("--merge-output-format", format)
+ }
+ if (format != "webm") {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
request.addOption("--embed-thumbnail")
- request.addOption("--convert-thumbnails", "png")
- try {
- val config = File(context.cacheDir, "config" + item.title + "##" + item.audioQualityId + ".txt")
- val configData =
- "--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
- config.writeText(configData)
- request.addOption("--config", config.absolutePath)
- } catch (ignored: Exception) {}
- }
- request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")
- request.addCommands(listOf("--replace-in-metadata", "title", ".*.", item.title))
- request.addCommands(listOf("--replace-in-metadata", "uploader", ".*.", item.author))
-
- if (item.playlistTitle.isNotEmpty()) {
- request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
- request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s")
- } else {
- request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
- }
- request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
- }
- "video" -> {
- if (downloadLocation == context.getString(R.string.video_path)){
- tempFileDir = File(context.getString(R.string.video_path))
- }
-
- val addChapters = sharedPreferences.getBoolean("add_chapters", false)
- if (addChapters) {
- request.addOption("--sponsorblock-mark", "all")
- }
- val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
- if (embedSubs) {
- request.addOption("--embed-subs", "")
- }
- var videoQualityId = item.videoQualityId
- val audioQualityId = item.audioQualityId
- if (videoQualityId.isEmpty()) videoQualityId = "bestvideo"
- val formatArgument = StringBuilder(videoQualityId)
- if (videoQualityId != "worst"){
- if (audioQualityId != "0") formatArgument.append("+", audioQualityId, "/best")
- }
- request.addOption("-f", formatArgument.toString())
- var format = item.videoFormat
- if (format.isNotEmpty()) {
- format = sharedPreferences.getString("video_format", "")!!
- if (format != "DEFAULT") request.addOption("--merge-output-format", format)
- }
-
- if (format != "webm") {
- val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
- if (embedThumb) {
- request.addOption("--embed-thumbnail")
- }
- }
- request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
- }
- "command" -> {
- val commandRegex = "\"([^\"]*)\"|(\\S+)"
- val command = commandTemplateDao.getTemplateById(item.customTemplateId)
- val m = Pattern.compile(commandRegex).matcher(command.content)
- while (m.find()) {
- if (m.group(1) != null) {
- request.addOption(m.group(1)!!)
- } else {
- request.addOption(m.group(2)!!)
- }
}
}
+ request.addOption("-o", tempFileDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
}
-
- runCatching {
- YoutubeDL.getInstance().execute(request, item.id.toString()){ progress, _, line ->
- setProgressAsync(workDataOf("progress" to progress.toInt()))
- var title: String? = context.getString(R.string.download_start)
- if (queue.isNotEmpty()) {
- title = queue[0].title
+ "command" -> {
+ val commandRegex = "\"([^\"]*)\"|(\\S+)"
+ val command = commandTemplateDao.getTemplateById(downloadItem.customTemplateId)
+ val m = Pattern.compile(commandRegex).matcher(command.content)
+ while (m.find()) {
+ if (m.group(1) != null) {
+ request.addOption(m.group(1)!!)
+ } else {
+ request.addOption(m.group(2)!!)
}
- notificationUtil.updateDownloadNotification(
- workID,
- line, progress.toInt(), queue.size, title,
- NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
- )
}
- }.onSuccess {
- //move file from internal to set download directory
- moveFile(tempFileDir.absoluteFile, downloadLocation){ progress ->
- setProgressAsync(workDataOf("progress" to progress))
- }
- //put download in history
- val incognito = sharedPreferences.getBoolean("incognito", false)
- if (!incognito) {
- val unixtime = System.currentTimeMillis() / 1000
- val historyItem = HistoryItem(item.url, item.title, item.author, item.duration, item.thumb, item.type, unixtime, item.downloadPath, item.website)
- historyDao.insert(historyItem)
- }
-
- }.onFailure {
- tempFileDir.delete()
- if (BuildConfig.DEBUG) {
- Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
- Log.e(DownloadWorker.TAG, context.getString(R.string.failed_download), it)
- }
- notificationUtil.updateDownloadNotification(
- workID,
- context.getString(R.string.failed_download), 0, 0, item.title,
- NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
- )
- return Result.failure()
}
}
+
+ runCatching {
+ YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
+ setProgressAsync(workDataOf("progress" to progress.toInt()))
+ val title: String = downloadItem.title
+ val queue = dao.getQueuedDownloads()
+ notificationUtil.updateDownloadNotification(
+ workID,
+ line, progress.toInt(), queue.size, title,
+ NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
+ )
+ }
+ }.onSuccess {
+ //move file from internal to set download directory
+ moveFile(tempFileDir.absoluteFile, downloadLocation){ progress ->
+ setProgressAsync(workDataOf("progress" to progress))
+ }
+ //put download in history
+ val incognito = sharedPreferences.getBoolean("incognito", false)
+ if (!incognito) {
+ val unixtime = System.currentTimeMillis() / 1000
+ val historyItem = HistoryItem(downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, downloadItem.downloadPath, downloadItem.website)
+ runBlocking {
+ historyDao.insert(historyItem)
+ }
+ }
+ }.onFailure {
+ tempFileDir.delete()
+ if (BuildConfig.DEBUG) {
+ Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
+ Log.e(TAG, context.getString(R.string.failed_download), it)
+ }
+ notificationUtil.updateDownloadNotification(
+ workID,
+ context.getString(R.string.failed_download), 0, 0, downloadItem.title,
+ NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
+ )
+ return Result.failure()
+ }
+
return Result.success()
}
@@ -247,7 +246,6 @@ class DownloadWorker(
companion object {
var workID: Int = 0
const val TAG = "DownloadWorker"
- lateinit var queue: MutableList
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_concurrent_downloads.xml b/app/src/main/res/drawable/ic_concurrent_downloads.xml
new file mode 100644
index 00000000..68410f71
--- /dev/null
+++ b/app/src/main/res/drawable/ic_concurrent_downloads.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index 668ade1d..b3c40927 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -165,7 +165,4 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/home_download_audio_bottom_sheet.xml b/app/src/main/res/layout/home_download_audio_bottom_sheet.xml
deleted file mode 100644
index 0547e925..00000000
--- a/app/src/main/res/layout/home_download_audio_bottom_sheet.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/home_download_all_bottom_sheet.xml b/app/src/main/res/layout/home_download_multiple_bottom_sheet.xml
similarity index 76%
rename from app/src/main/res/layout/home_download_all_bottom_sheet.xml
rename to app/src/main/res/layout/home_download_multiple_bottom_sheet.xml
index 8c986512..6b040e7d 100644
--- a/app/src/main/res/layout/home_download_all_bottom_sheet.xml
+++ b/app/src/main/res/layout/home_download_multiple_bottom_sheet.xml
@@ -7,6 +7,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
+
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent" />
diff --git a/app/src/main/res/layout/home_download_multiple_card.xml b/app/src/main/res/layout/home_download_multiple_card.xml
new file mode 100644
index 00000000..0268b849
--- /dev/null
+++ b/app/src/main/res/layout/home_download_multiple_card.xml
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/home_download_settings.xml b/app/src/main/res/layout/home_download_settings.xml
new file mode 100644
index 00000000..2696c541
--- /dev/null
+++ b/app/src/main/res/layout/home_download_settings.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/home_download_settings_components.xml b/app/src/main/res/layout/home_download_settings_components.xml
new file mode 100644
index 00000000..82bcb4ef
--- /dev/null
+++ b/app/src/main/res/layout/home_download_settings_components.xml
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/home_download_single_bottom_sheet.xml b/app/src/main/res/layout/home_download_single_bottom_sheet.xml
index 6ee9fb59..a65eeacf 100644
--- a/app/src/main/res/layout/home_download_single_bottom_sheet.xml
+++ b/app/src/main/res/layout/home_download_single_bottom_sheet.xml
@@ -23,242 +23,7 @@
android:layout_height="wrap_content"
android:text="@string/download" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/app/src/main/res/layout/home_download_video_bottom_sheet.xml b/app/src/main/res/layout/home_download_video_bottom_sheet.xml
deleted file mode 100644
index a3627b71..00000000
--- a/app/src/main/res/layout/home_download_video_bottom_sheet.xml
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml
index 1f99c652..790ecdb1 100644
--- a/app/src/main/res/values-tr-rTR/strings.xml
+++ b/app/src/main/res/values-tr-rTR/strings.xml
@@ -1,6 +1,5 @@
- YTDLnis
Ara
Tümünü Kaldır
Clear Results
@@ -65,9 +64,6 @@
Çalma listesinden indirmek için bir aralık seçin (her şeyi indirmemek için).
Son dizin ilkinden daha küçük olamaz.
İlk dizin son dizinden büyük olamaz.
- /storage/emulated/0/Download
- /storage/emulated/0/Download
- /storage/emulated/0/Download
Eşzamanlı olarak indirilecek DASH/HLS yerel video parçalarının sayısı
Eşzamanlı Parçalar
İndirme
@@ -110,7 +106,6 @@
En yeni
En eski
- ytdlnis-TRENDING
İndiriliyor
İndirmeleri İptal Et
İndirilmekte olan tüm öğeleri kaldırın ve indirme hizmetini durdurun?
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1cfa7a2a..1cbfd51b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -158,4 +158,6 @@
Adjust video
Remove Audio
Schedule
+ Concurrent Downloads
+ Number of downloads executing at the same time
\ No newline at end of file
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index a8fdd26a..0cde0153 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -56,6 +56,17 @@
app:summary="@string/concurrent_fragments_summary"
app:title="@string/concurrent_fragments" />
+
+