From b1b5dbc608bb1ad14516656a2805f88a32beabf6 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sun, 25 Jun 2023 22:07:08 +0200 Subject: [PATCH] alot of stuff -added download now option when u highlight only scheduled items in the context menu -tappin on the errored notification will send you to errored tab, if you have logs disabled. If logs on it will send u to the log -added preferred format ordering. By ID, FILESIZE, CONTAINER. Formats grouped by container will also be sorted by filesize -fixed app not showing formats when u try to modify a current download item and its a different type (audio formats not showing if its a video type and vice versa) -fixed app showing generic formats in cases where the format length was the same as that of generic formats (silly mistake) -made the app store static strings for 'best' and 'worst' so that there isnt any confusion when u try to change the language and the stored downloads will have the other language's string -added collected filesize on top of the download queue -potentially fixed app not crashing when going to queued screen? idk -fixed app not moving files when its a fresh install and u havent tried to change the download path. Now the app will just straight up download in that path if it finds that it can write in it. If not, it will use the old method of moving through URI's -added download retries options --retries and --fragment-retries --- app/build.gradle | 17 ++-- .../database/dao/CommandTemplateDao.kt | 6 +- .../ytdlnis/database/dao/CookieDao.kt | 4 +- .../ytdlnis/database/dao/DownloadDao.kt | 16 ++-- .../ytdlnis/database/dao/HistoryDao.kt | 4 +- .../ytdlnis/database/dao/ResultDao.kt | 6 +- .../repository/CommandTemplateRepository.kt | 5 +- .../database/repository/CookieRepository.kt | 3 +- .../database/repository/DownloadRepository.kt | 15 ++-- .../database/repository/HistoryRepository.kt | 12 ++- .../database/repository/ResultRepository.kt | 9 +- .../viewmodel/CommandTemplateViewModel.kt | 5 +- .../database/viewmodel/CookieViewModel.kt | 3 +- .../database/viewmodel/DownloadViewModel.kt | 24 +++--- .../database/viewmodel/HistoryViewModel.kt | 3 +- .../database/viewmodel/ResultViewModel.kt | 7 +- .../ui/downloadcard/DownloadAudioFragment.kt | 14 +++- .../ui/downloadcard/DownloadVideoFragment.kt | 15 +++- .../FormatSelectionBottomSheetDialog.kt | 42 ++++++---- .../ui/downloads/DownloadQueueMainFragment.kt | 4 + .../ui/downloads/QueuedDownloadsFragment.kt | 26 +++++- .../com/deniscerri/ytdlnis/util/FileUtil.kt | 51 +++++------ .../ytdlnis/util/NotificationUtil.kt | 10 +++ .../deniscerri/ytdlnis/work/DownloadWorker.kt | 84 ++++++++++++------- .../main/res/drawable/baseline_sort_24.xml | 4 + app/src/main/res/layout/fragment_inqueue.xml | 33 ++++++++ app/src/main/res/menu/queued_menu_context.xml | 7 ++ app/src/main/res/values/arrays.xml | 16 +++- app/src/main/res/values/strings.xml | 3 + .../main/res/xml/downloading_preferences.xml | 8 ++ app/src/main/res/xml/updating_preferences.xml | 9 ++ 31 files changed, 315 insertions(+), 150 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_sort_24.xml create mode 100644 app/src/main/res/layout/fragment_inqueue.xml diff --git a/app/build.gradle b/app/build.gradle index c9e1065a..7434ed7f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ def properties = new Properties() def versionMajor = 1 def versionMinor = 6 def versionPatch = 3 -def versionBuild = 1 // bump for dogfood builds, public betas, etc. +def versionBuild = 2 // bump for dogfood builds, public betas, etc. def versionExt = "" if (versionBuild > 0){ @@ -64,7 +64,8 @@ android { buildTypes { release { minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' debuggable false signingConfig signingConfigs.debug } @@ -157,7 +158,7 @@ dependencies { androidTestImplementation "junit:junit:$junitVer" androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha13" + androidTestImplementation "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha16" androidTestImplementation "androidx.test:runner:1.5.2" androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation "androidx.test:rules:1.5.0" @@ -175,10 +176,10 @@ dependencies { implementation "androidx.work:work-runtime-ktx:$workVer" - implementation "androidx.room:room-runtime:2.5.1" - implementation "androidx.room:room-ktx:2.5.1" - kapt "androidx.room:room-compiler:2.5.1" - androidTestImplementation "androidx.room:room-testing:2.5.1" + implementation "androidx.room:room-runtime:2.5.2" + implementation "androidx.room:room-ktx:2.5.2" + kapt "androidx.room:room-compiler: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" @@ -190,7 +191,7 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.10" implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' - implementation "com.google.android.exoplayer:exoplayer:2.18.5" + implementation "com.google.android.exoplayer:exoplayer:2.18.7" implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4' implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" implementation "com.github.Irineu333:Highlight-KT:1.0.4" diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt index c94d4853..9d3a9143 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CommandTemplateDao.kt @@ -1,9 +1,9 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.TemplateShortcut +import kotlinx.coroutines.flow.Flow @Dao interface CommandTemplateDao { @@ -11,10 +11,10 @@ interface CommandTemplateDao { fun getAllTemplates() : List @Query("SELECT * FROM commandTemplates ORDER BY id DESC") - fun getAllTemplatesLiveData() : LiveData> + fun getAllTemplatesFlow() : Flow> @Query("SELECT * FROM templateShortcuts ORDER BY id DESC") - fun getAllShortcutsLiveData() : LiveData> + fun getAllShortcutsFlow() : Flow> @Query("SELECT * FROM templateShortcuts ORDER BY id DESC") fun getAllShortcuts() : List diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt index b640af75..f570b5e7 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt @@ -1,8 +1,8 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.CookieItem +import kotlinx.coroutines.flow.Flow @Dao interface CookieDao { @@ -10,7 +10,7 @@ interface CookieDao { fun getAllCookies() : List @Query("SELECT * FROM cookies ORDER BY id DESC") - fun getAllCookiesLiveData() : LiveData> + fun getAllCookiesFlow() : Flow> @Query("SELECT EXISTS(SELECT * FROM cookies WHERE url=:url LIMIT 1)") fun checkIfExistsWithSameURL(url: String) : Boolean 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 55cdc014..4dc8f4fa 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,20 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.DownloadItem +import kotlinx.coroutines.flow.Flow @Dao interface DownloadDao { @Query("SELECT * FROM downloads ORDER BY status") - fun getAllDownloads() : LiveData> + fun getAllDownloads() : Flow> @Query("SELECT * FROM downloads WHERE status='Active'") - fun getActiveDownloads() : LiveData> + fun getActiveDownloads() : Flow> @Query("SELECT COUNT(*) FROM downloads WHERE status='Active'") - fun getActiveDownloadsCount() : LiveData + fun getActiveDownloadsCount() : Flow @Query("SELECT * FROM downloads WHERE status='Active'") fun getActiveDownloadsList() : List @@ -23,25 +23,25 @@ interface DownloadDao { fun getActiveAndQueuedDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") - fun getQueuedDownloads() : LiveData> + fun getQueuedDownloads() : Flow> @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") fun getQueuedDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC") - fun getCancelledDownloads() : LiveData> + fun getCancelledDownloads() : Flow> @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC") fun getCancelledDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC") - fun getErroredDownloads() : LiveData> + fun getErroredDownloads() : Flow> @Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC") fun getErroredDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC") - fun getProcessingDownloads() : LiveData> + fun getProcessingDownloads() : Flow> @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") fun getDownloadById(id: Long) : DownloadItem diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt index 1d12d37d..cf21c4f2 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/HistoryDao.kt @@ -1,8 +1,8 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.HistoryItem +import kotlinx.coroutines.flow.Flow @Dao interface HistoryDao { @@ -27,7 +27,7 @@ interface HistoryDao { @Query("SELECT * FROM history") - fun getAllHistory() : LiveData> + fun getAllHistory() : Flow> @Query("SELECT * FROM history") fun getAllHistoryList() : List diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt index 66b4a861..5bebde4f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt @@ -1,16 +1,16 @@ package com.deniscerri.ytdlnis.database.dao -import androidx.lifecycle.LiveData import androidx.room.* import com.deniscerri.ytdlnis.database.models.ResultItem +import kotlinx.coroutines.flow.Flow @Dao interface ResultDao { @Query("SELECT * FROM results") - fun getResults() : LiveData> + fun getResults() : Flow> @Query("SELECT COUNT(id) FROM results") - fun getCount() : LiveData + fun getCount() : Flow @Query("SELECT COUNT(id) FROM results") fun getCountInt() :Int diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CommandTemplateRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CommandTemplateRepository.kt index 31721162..f64a08d7 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CommandTemplateRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CommandTemplateRepository.kt @@ -4,10 +4,11 @@ import androidx.lifecycle.LiveData import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.TemplateShortcut +import kotlinx.coroutines.flow.Flow class CommandTemplateRepository(private val commandDao: CommandTemplateDao) { - val items : LiveData> = commandDao.getAllTemplatesLiveData() - val shortcuts : LiveData> = commandDao.getAllShortcutsLiveData() + val items : Flow> = commandDao.getAllTemplatesFlow() + val shortcuts : Flow> = commandDao.getAllShortcutsFlow() fun getAll() : List { return commandDao.getAllTemplates() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt index 1bc0b44a..2c042fd4 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt @@ -3,9 +3,10 @@ package com.deniscerri.ytdlnis.database.repository import androidx.lifecycle.LiveData import com.deniscerri.ytdlnis.database.dao.CookieDao import com.deniscerri.ytdlnis.database.models.CookieItem +import kotlinx.coroutines.flow.Flow class CookieRepository(private val cookieDao: CookieDao) { - val items : LiveData> = cookieDao.getAllCookiesLiveData() + val items : Flow> = cookieDao.getAllCookiesFlow() fun getAll() : List { return cookieDao.getAllCookies() 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 006a6eb1..7b01a504 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 @@ -4,15 +4,16 @@ import androidx.lifecycle.LiveData import com.deniscerri.ytdlnis.database.Converters 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 : LiveData> = downloadDao.getAllDownloads() - val activeDownloads : LiveData> = downloadDao.getActiveDownloads() - val activeDownloadsCount : LiveData = downloadDao.getActiveDownloadsCount() - val queuedDownloads : LiveData> = downloadDao.getQueuedDownloads() - val cancelledDownloads : LiveData> = downloadDao.getCancelledDownloads() - val erroredDownloads : LiveData> = downloadDao.getErroredDownloads() - val processingDownloads : LiveData> = downloadDao.getProcessingDownloads() + val allDownloads : Flow> = 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 processingDownloads : Flow> = downloadDao.getProcessingDownloads() enum class Status { Active, Queued, Error, Processing, Cancelled diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt index ba31c176..0517d3ef 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt @@ -4,9 +4,11 @@ import androidx.lifecycle.LiveData import com.deniscerri.ytdlnis.database.dao.HistoryDao import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.util.FileUtil +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collectLatest class HistoryRepository(private val historyDao: HistoryDao) { - val items : LiveData> = historyDao.getAllHistory() + val items : Flow> = historyDao.getAllHistory() enum class HistorySort{ DESC, ASC } @@ -59,9 +61,11 @@ class HistoryRepository(private val historyDao: HistoryDao) { } suspend fun clearDeletedHistory(){ - items.value?.forEach { item -> - if (!FileUtil.exists(item.downloadPath)){ - historyDao.delete(item.id) + items.collectLatest { + it.forEach { item -> + if (!FileUtil.exists(item.downloadPath)){ + historyDao.delete(item.id) + } } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt index e34381b2..a501c140 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt @@ -2,18 +2,15 @@ package com.deniscerri.ytdlnis.database.repository import android.content.Context import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.dao.ResultDao -import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.util.InfoUtil +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) { +class ResultRepository(private val resultDao: ResultDao, private val context: Context) { private val tag: String = "ResultRepository" - val allResults : LiveData> = resultDao.getResults() + val allResults : Flow> = resultDao.getResults() var itemCount = MutableStateFlow(-1) suspend fun insert(it: ResultItem){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt index f5b2f628..3c6c8c48 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt @@ -5,6 +5,7 @@ import android.content.ClipboardManager import android.content.Context.CLIPBOARD_SERVICE import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.models.CommandTemplate @@ -27,8 +28,8 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi init { val dao = DBManager.getInstance(application).commandTemplateDao repository = CommandTemplateRepository(dao) - items = repository.items - shortcuts = repository.shortcuts + items = repository.items.asLiveData() + shortcuts = repository.shortcuts.asLiveData() } fun getTemplate(itemId: Long): CommandTemplate { 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 a80df524..20966e99 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 @@ -10,6 +10,7 @@ import android.util.Log import android.webkit.CookieManager import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.models.CookieItem @@ -31,7 +32,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a init { val dao = DBManager.getInstance(application).cookieDao repository = CookieRepository(dao) - items = repository.items + items = repository.items.asLiveData() } fun getAll(): List { 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 72d7f831..cc4b5063 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 @@ -11,6 +11,7 @@ import android.util.DisplayMetrics import android.widget.Toast import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import androidx.work.Constraints @@ -77,13 +78,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application commandTemplateDao = DBManager.getInstance(application).commandTemplateDao infoUtil = InfoUtil(application) - allDownloads = repository.allDownloads - queuedDownloads = repository.queuedDownloads - activeDownloads = repository.activeDownloads - activeDownloadsCount = repository.activeDownloadsCount - processingDownloads = repository.processingDownloads - cancelledDownloads = repository.cancelledDownloads - erroredDownloads = repository.erroredDownloads + allDownloads = repository.allDownloads.asLiveData() + queuedDownloads = repository.queuedDownloads.asLiveData() + activeDownloads = repository.activeDownloads.asLiveData() + activeDownloadsCount = repository.activeDownloadsCount.asLiveData() + processingDownloads = repository.processingDownloads.asLiveData() + cancelledDownloads = repository.cancelledDownloads.asLiveData() + erroredDownloads = repository.erroredDownloads.asLiveData() videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString() formatIDPreference = sharedPreferences.getString("format_id", "").toString() @@ -95,15 +96,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application resources = Resources(application.assets, metrics, confTmp) - val videoFormat = resources.getStringArray(R.array.video_formats) val videoFormatValues = resources.getStringArray(R.array.video_formats_values) var videoContainer = sharedPreferences.getString("video_format", "Default") if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue) defaultVideoFormats = mutableListOf() - videoFormat.forEachIndexed { index , it -> + videoFormatValues.forEach { val tmp = Format( - videoFormatValues[index], + it, videoContainer!!, "", "", @@ -125,7 +125,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application "", "", 0, - resources.getString(R.string.best_quality) + "best" ) } @@ -381,7 +381,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } fun getGenericVideoFormats() : MutableList{ - val videoFormats = resources.getStringArray(R.array.video_formats) + val videoFormats = resources.getStringArray(R.array.video_formats_values) val formats = mutableListOf() var containerPreference = sharedPreferences.getString("video_format", "Default") if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt index 405bb0a3..5e3bf31f 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.models.HistoryItem @@ -28,7 +29,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) init { val dao = DBManager.getInstance(application).historyDao repository = HistoryRepository(dao) - allItems = repository.items + allItems = repository.items.asLiveData() _items.addSource(allItems){ filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!) 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 920dd7d7..416bbd99 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 @@ -3,10 +3,10 @@ package com.deniscerri.ytdlnis.database.viewmodel import android.app.Application import android.content.SharedPreferences import android.util.Log -import androidx.compose.runtime.MutableState import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.App @@ -17,7 +17,6 @@ import com.deniscerri.ytdlnis.database.models.SearchHistoryItem import com.deniscerri.ytdlnis.database.repository.ResultRepository import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.regex.Pattern @@ -33,9 +32,9 @@ class ResultViewModel(application: Application) : AndroidViewModel(application) init { val dao = DBManager.getInstance(application).resultDao val commandDao = DBManager.getInstance(application).commandTemplateDao - repository = ResultRepository(dao, commandDao, getApplication().applicationContext) + repository = ResultRepository(dao, getApplication().applicationContext) searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao) - items = repository.allResults + items = repository.allResults.asLiveData() loadingItems.postValue(false) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt index 1729bbf8..1ba49443 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt @@ -76,7 +76,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { downloadItem = withContext(Dispatchers.IO) { - if (currentDownloadItem != null && currentDownloadItem!!.type == Type.audio){ + if (currentDownloadItem != null){ val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java) Gson().fromJson(string, DownloadItem::class.java) }else{ @@ -126,7 +126,17 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr if (free == "?") freeSpace.visibility = View.GONE var formats = mutableListOf() - formats.addAll(resultItem.formats.filter { it.format_note.contains("audio", ignoreCase = true) }) + if (currentDownloadItem == null) { + formats.addAll(resultItem.formats.filter { it.format_note.contains("audio", ignoreCase = true) }) + }else{ + //if its updating a present downloaditem and its the wrong category + if (currentDownloadItem!!.type != Type.audio){ + downloadItem.type = Type.audio + downloadItem.format = + downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) } + .maxByOrNull { it.filesize }!! + } + } if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }) val containers = requireContext().resources.getStringArray(R.array.audio_containers) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt index 96d87cfb..bf48c5d2 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt @@ -77,7 +77,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { downloadItem = withContext(Dispatchers.IO){ - if (currentDownloadItem != null && currentDownloadItem!!.type == Type.video){ + if (currentDownloadItem != null){ val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java) Gson().fromJson(string, DownloadItem::class.java) }else{ @@ -127,8 +127,19 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr freeSpace.text = String.format( getString(R.string.freespace) + ": " + free) if (free == "?") freeSpace.visibility = View.GONE + var formats = mutableListOf() - formats.addAll(resultItem.formats) + if (currentDownloadItem == null) { + formats.addAll(resultItem.formats) + }else{ + //if its updating a present downloaditem and its the wrong category + if (currentDownloadItem!!.type != Type.video){ + downloadItem.type = Type.video + downloadItem.format = + downloadItem.allFormats.filter { it.vcodec.isNotEmpty() } + .maxByOrNull { it.filesize }!! + } + } if (formats.isEmpty()) formats.addAll(downloadItem.allFormats) val containers = requireContext().resources.getStringArray(R.array.video_containers) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt index b6c19f64..d2516c17 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt @@ -15,9 +15,7 @@ import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.Format -import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type -import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil import com.facebook.shimmer.ShimmerFrameLayout @@ -27,6 +25,7 @@ import com.google.android.material.card.MaterialCardView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import okhttp3.internal.format import java.util.* @@ -45,6 +44,11 @@ class FormatSelectionBottomSheetDialog(private val items: List, p private lateinit var videoTitle : TextView private lateinit var audioTitle : TextView + private lateinit var sortBy : FormatSorting + enum class FormatSorting { + filesize, container, id + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) infoUtil = InfoUtil(requireActivity().applicationContext) @@ -61,6 +65,8 @@ class FormatSelectionBottomSheetDialog(private val items: List, p val view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null) dialog.setContentView(view) + sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!) + dialog.setOnShowListener { behavior = BottomSheetBehavior.from(view.parent as View) val displayMetrics = DisplayMetrics() @@ -78,10 +84,7 @@ class FormatSelectionBottomSheetDialog(private val items: List, p okBtn = view.findViewById(R.id.format_ok) shimmers.visibility = View.GONE - val hasGenericFormats = when(items.first()!!.type){ - Type.audio -> formats.first().size == resources.getStringArray(R.array.audio_formats).size - else -> formats.first().size == resources.getStringArray(R.array.video_formats).size - } + val hasGenericFormats = formats.first().isEmpty() || formats.last().any { it.format_id == "best" } if (items.size > 1){ if (!hasGenericFormats){ @@ -131,10 +134,10 @@ class FormatSelectionBottomSheetDialog(private val items: List, p infoUtil.getFormats(items.first()!!.url) } res.filter { it.format_note != "storyboard" } - if(items.first()?.type == Type.audio){ - chosenFormats = res.filter { it.format_note.contains("audio", ignoreCase = true) } + chosenFormats = if(items.first()?.type == Type.audio){ + res.filter { it.format_note.contains("audio", ignoreCase = true) } }else{ - chosenFormats = res + res } if (chosenFormats.isEmpty()) throw Exception() //playlist format filtering @@ -216,7 +219,14 @@ class FormatSelectionBottomSheetDialog(private val items: List, p } } private fun addFormatsToView(){ - val canMultiSelectAudio = items.first()?.type == Type.video && chosenFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null + //sort + val finalFormats: List = when(sortBy){ + FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value } + FormatSorting.id -> chosenFormats.sortedBy { it.format_id } + FormatSorting.filesize -> chosenFormats + } + + val canMultiSelectAudio = items.first()?.type == Type.video && finalFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null videoFormatList.removeAllViews() audioFormatList.removeAllViews() @@ -226,7 +236,7 @@ class FormatSelectionBottomSheetDialog(private val items: List, p audioTitle.visibility = View.GONE okBtn.visibility = View.GONE }else{ - if (chosenFormats.count { it.vcodec.isBlank() || it.vcodec == "none" } == 0){ + if (finalFormats.count { it.vcodec.isBlank() || it.vcodec == "none" } == 0){ audioFormatList.visibility = View.GONE audioTitle.visibility = View.GONE videoTitle.visibility = View.GONE @@ -239,8 +249,8 @@ class FormatSelectionBottomSheetDialog(private val items: List, p } } - for (i in chosenFormats.lastIndex downTo 0){ - val format = chosenFormats[i] + for (i in finalFormats.lastIndex downTo 0){ + val format = finalFormats[i] val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null) formatItem.tag = "${format.format_id}${format.format_note}" UiUtil.populateFormatCard(formatItem as MaterialCardView, format, null) @@ -250,7 +260,7 @@ class FormatSelectionBottomSheetDialog(private val items: List, p val clickedCard = (clickedformat as MaterialCardView) if (format.vcodec.isNotBlank() && format.vcodec != "none") { if (clickedCard.isChecked) { - listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null))) + listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null))) dismiss() } videoFormatList.forEach { (it as MaterialCardView).isChecked = false } @@ -269,14 +279,14 @@ class FormatSelectionBottomSheetDialog(private val items: List, p } }else{ if (items.size == 1){ - listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null))) + listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null))) }else{ val selectedFormats = mutableListOf() formatCollection.forEach { selectedFormats.add(it.first{ f -> f.format_id == format.format_id}) } if (selectedFormats.isEmpty()) { - items.forEach { + items.forEach { _ -> selectedFormats.add(format) } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt index d761dc39..d134e5b8 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt @@ -102,6 +102,10 @@ class DownloadQueueMainFragment : Fragment(){ }) mainActivity.hideBottomNavigation() initMenu() + + if (arguments?.getString("tab") != null){ + tabLayout.selectTab(tabLayout.getTabAt(3)) + } } private fun initMenu() { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt index 89ef0c88..924e27de 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/QueuedDownloadsFragment.kt @@ -1,5 +1,6 @@ package com.deniscerri.ytdlnis.ui.downloads +import android.annotation.SuppressLint import android.app.Activity import android.content.DialogInterface import android.graphics.Canvas @@ -59,6 +60,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi private lateinit var queuedRecyclerView : RecyclerView private lateinit var queuedDownloads : GenericDownloadAdapter private lateinit var notificationUtil: NotificationUtil + private lateinit var fileSize: TextView private var selectedObjects: ArrayList? = null private var actionMode : ActionMode? = null private lateinit var items : MutableList @@ -68,7 +70,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi container: ViewGroup?, savedInstanceState: Bundle? ): View? { - fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) + fragmentView = inflater.inflate(R.layout.fragment_inqueue, container, false) activity = getActivity() notificationUtil = NotificationUtil(requireContext()) downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] @@ -77,9 +79,10 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi return fragmentView } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - + fileSize = view.findViewById(R.id.filesize) queuedDownloads = GenericDownloadAdapter( this, @@ -97,6 +100,11 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi downloadViewModel.queuedDownloads.observe(viewLifecycleOwner) { items = it.toMutableList() + if (it.isEmpty()) fileSize.visibility = View.GONE + else{ + fileSize.visibility = View.VISIBLE + fileSize.text = "${getString(R.string.file_size)}: ~ ${FileUtil.convertFileSize(it.sumOf { i -> i.format.filesize })}" + } queuedDownloads.submitList(it) } } @@ -232,6 +240,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi override fun onCardSelect(itemID: Long, isChecked: Boolean) { val item = items.find { it.id == itemID } + val now = System.currentTimeMillis() if (isChecked) { selectedObjects!!.add(item!!) if (actionMode == null){ @@ -248,6 +257,9 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi actionMode?.finish() } } + if (actionMode != null){ + actionMode!!.menu.getItem(1).isVisible = selectedObjects!!.all { it.downloadStartTime > now } + } } private fun removeItem(itemID: Long){ @@ -311,6 +323,16 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi deleteDialog.show() true } + R.id.download -> { + for (obj in selectedObjects!!){ + WorkManager.getInstance(requireContext()).cancelUniqueWork(obj.id.toInt().toString()) + } + selectedObjects!!.forEach { it.downloadStartTime = 0L } + lifecycleScope.launch(Dispatchers.IO) { + downloadViewModel.queueDownloads(selectedObjects!!) + } + true + } R.id.select_all -> { queuedDownloads.checkAll(items) selectedObjects?.clear() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt index c2f4af2c..88466186 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt @@ -90,39 +90,28 @@ object FileUtil { if(it.name.contains(".part-Frag")) return@forEach - //sending to main or SD CARD - if (currentDirectory.exists() && Build.VERSION.SDK_INT >= 26){ - if (Build.VERSION.SDK_INT >= 26 ){ - Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING) - }else{ - it.renameTo(destFile) - } - fileList.add(destFile) - }else{ - //sending to USB OTG - val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*" - destFile = File(currentDirectory.absolutePath + "/${it.name}") + val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*" + destFile = File(currentDirectory.absolutePath + "/${it.name}") - val dest = Uri.parse(destDir).run { - DocumentsContract.buildDocumentUriUsingTree( - this, - DocumentsContract.getTreeDocumentId(this) - ) - } - val destUri = DocumentsContract.createDocument( - context.contentResolver, - dest, - mimeType, - it.name - ) ?: return@forEach - - val inputStream = it.inputStream() - val outputStream = - context.contentResolver.openOutputStream(destUri) ?: return@forEach - inputStream.copyTo(outputStream) - inputStream.closeQuietly() - outputStream.closeQuietly() + val dest = Uri.parse(destDir).run { + DocumentsContract.buildDocumentUriUsingTree( + this, + DocumentsContract.getTreeDocumentId(this) + ) } + val destUri = DocumentsContract.createDocument( + context.contentResolver, + dest, + mimeType, + it.name + ) ?: return@forEach + + val inputStream = it.inputStream() + val outputStream = + context.contentResolver.openOutputStream(destUri) ?: return@forEach + inputStream.copyTo(outputStream) + inputStream.closeQuietly() + outputStream.closeQuietly() fileList.add(destFile) }catch (e: java.lang.Exception) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt index 030ef3c7..23434314 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -245,6 +245,15 @@ class NotificationUtil(var context: Context) { .setArguments(bundle) .createPendingIntent() + val tabBundle = Bundle() + tabBundle.putString("tab", "error") + + val errorTabPendingIntent = NavDeepLinkBuilder(context) + .setGraph(R.navigation.nav_graph) + .setDestination(R.id.downloadQueueMainFragment) + .setArguments(tabBundle) + .createPendingIntent() + notificationBuilder .setContentTitle("${context.getString(R.string.failed_download)}: $title") .setContentText(error) @@ -255,6 +264,7 @@ class NotificationUtil(var context: Context) { R.drawable.ic_launcher_foreground_large ) ) + .setContentIntent(errorTabPendingIntent) .setPriority(NotificationCompat.PRIORITY_MAX) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .clearActions() 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 0087ad86..36f04bd1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.work import android.content.Context +import android.os.Build import android.os.Handler import android.os.Looper import android.util.Log @@ -91,6 +92,16 @@ class DownloadWorker( tempFileDir.delete() tempFileDir.mkdirs() + var useCacheFolder = true + val downloadLocationFile = File(downloadLocation) + if (downloadLocationFile.canWrite()) { + File(downloadLocation).mkdirs() + if(Build.VERSION.SDK_INT > 23) request.addOption("-P", "temp:" + tempFileDir.absolutePath) + useCacheFolder = false + } + val pathUsed = if (useCacheFolder) tempFileDir.absolutePath else downloadLocation + + val aria2 = sharedPreferences.getBoolean("aria2", false) if (aria2) { request.addOption("--downloader", "libaria2c.so") @@ -99,6 +110,13 @@ class DownloadWorker( val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1) if (concurrentFragments > 1) request.addOption("-N", concurrentFragments) } + + val retries = sharedPreferences.getString("--retries", "")!! + val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!! + + if(retries.isNotEmpty()) request.addOption("retries", retries) + if(fragmentRetries.isNotEmpty()) request.addOption("fragment-retries", fragmentRetries) + val limitRate = sharedPreferences.getString("limit_rate", "") if (limitRate != "") request.addOption("-r", limitRate!!) if(downloadItem.type != DownloadViewModel.Type.command){ @@ -215,9 +233,9 @@ class DownloadWorker( if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ request.addOption("--split-chapters") - request.addOption("-P", tempFileDir.absolutePath) + request.addOption("-P", pathUsed) }else{ - request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s") + request.addOption("-o", pathUsed + "/${downloadItem.customFileNameTemplate}.%(ext)s") } } @@ -281,9 +299,9 @@ class DownloadWorker( if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ request.addOption("--split-chapters") - request.addOption("-P", tempFileDir.absolutePath) + request.addOption("-P", pathUsed) }else{ - request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s") + request.addOption("-o", pathUsed + "/${downloadItem.customFileNameTemplate}.%(ext)s") } } @@ -294,7 +312,7 @@ class DownloadWorker( writeText(downloadItem.format.format_note) }.absolutePath ) - request.addOption("-P", tempFileDir.absolutePath) + request.addOption("-P", pathUsed) } } @@ -327,33 +345,43 @@ class DownloadWorker( } } }.onSuccess { - //move file from internal to set download directory - setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) - var finalPaths : List? - try { - finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p -> - setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) - } - if (finalPaths.isNotEmpty()){ - setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads)) - }else{ - finalPaths = listOf(context.getString(R.string.unfound_file)) - } - }catch (e: Exception){ - finalPaths = listOf(context.getString(R.string.unfound_file)) - e.printStackTrace() - handler.postDelayed({ - Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() - }, 1000) - } - val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao) + var finalPaths: List + if (useCacheFolder){ + //move file from internal to set download directory + setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) + try { + finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p -> + setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) + } + if (finalPaths.isNotEmpty()){ + setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads)) + }else{ + finalPaths = listOf(context.getString(R.string.unfound_file)) + } + }catch (e: Exception){ + finalPaths = listOf(context.getString(R.string.unfound_file)) + e.printStackTrace() + handler.postDelayed({ + Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + }, 1000) + } + }else{ + val now = System.currentTimeMillis() + finalPaths = downloadLocationFile + .walkTopDown() + .filter { it.isFile && it.absolutePath.contains(downloadItem.title) && it.lastModified() > now - 10000000} + .map { it.absolutePath } + .toList() + + if (finalPaths.isEmpty()) finalPaths = listOf(context.getString(R.string.unfound_file)) + } //put download in history val incognito = sharedPreferences.getBoolean("incognito", false) if (!incognito) { val unixtime = System.currentTimeMillis() / 1000 - val file = File(finalPaths?.first()!!) + val file = File(finalPaths.first()!!) downloadItem.format.filesize = file.length() val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id) runBlocking { @@ -363,9 +391,7 @@ class DownloadWorker( notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) notificationUtil.createDownloadFinished( - downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else listOf( - finalPaths!!.maxByOrNull { it.length }!! - ), + downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID ) diff --git a/app/src/main/res/drawable/baseline_sort_24.xml b/app/src/main/res/drawable/baseline_sort_24.xml new file mode 100644 index 00000000..193dbd79 --- /dev/null +++ b/app/src/main/res/drawable/baseline_sort_24.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/layout/fragment_inqueue.xml b/app/src/main/res/layout/fragment_inqueue.xml new file mode 100644 index 00000000..7f5954f2 --- /dev/null +++ b/app/src/main/res/layout/fragment_inqueue.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/queued_menu_context.xml b/app/src/main/res/menu/queued_menu_context.xml index 713b5dbb..eecfffeb 100644 --- a/app/src/main/res/menu/queued_menu_context.xml +++ b/app/src/main/res/menu/queued_menu_context.xml @@ -8,6 +8,13 @@ android:title="@string/remove_results" android:icon="@drawable/baseline_delete_24" app:showAsAction="ifRoom" /> + + + - @string/worst_quality - @string/best_quality + worst + best + + + + @string/file_size + @string/container + ID + + + + filesize + container + id diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49592f2c..87347608 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,4 +279,7 @@ Piped Instance Write a PIPED API Server, the app can use for YouTube queries and formats Use Custom Audio Quality + Retries + Fragment Retries + Format Order \ No newline at end of file diff --git a/app/src/main/res/xml/downloading_preferences.xml b/app/src/main/res/xml/downloading_preferences.xml index bdef0624..d151dd3c 100644 --- a/app/src/main/res/xml/downloading_preferences.xml +++ b/app/src/main/res/xml/downloading_preferences.xml @@ -104,6 +104,14 @@ app:summary="@string/log_downloads_summary" app:title="@string/log_downloads" /> + + + + + +