From 9353e08ee92a2db7a96e2d7a345b27df66cc1298 Mon Sep 17 00:00:00 2001
From: deniscerri <64997243+deniscerri@users.noreply.github.com>
Date: Sun, 22 Oct 2023 14:51:23 +0200
Subject: [PATCH] other stuff
- formats auto updating as soon as the download card opens if auto-update is on
- added preferred audio format always in the video tab
- made app post downloads for queue in chunks
- made app always save logs in case it fails, and if succeeds and logs are off it deletes it
- fixed app navigating to home screen when cancelling download card in history screen
- added a button to skip incoming app update so it wont bother you anymore
- fixed settings not restoring some fields
- fixed crunchyroll not working with cookies
- added search for command templates
- added sort filtering for command templates
- added all shortcuts inside filename template creation dialog. Long click them to see the explanation
- added preference to hide elements from the download card
- made avc1 and m4a as preferred codecs for noobs
- create dialog when download item exists. U can edit the download item right from there or access the history item to view the file
---
app/build.gradle | 12 +-
app/proguard-rules.pro | 15 +-
app/src/main/AndroidManifest.xml | 15 +-
.../com/deniscerri/ytdlnis/MainActivity.kt | 1 +
.../deniscerri/ytdlnis/database/DBManager.kt | 4 +
.../database/dao/CommandTemplateDao.kt | 20 +
.../ytdlnis/database/dao/HistoryDao.kt | 2 +-
.../repository/CommandTemplateRepository.kt | 14 +
.../database/repository/HistoryRepository.kt | 22 +-
.../database/repository/ResultRepository.kt | 27 +-
.../viewmodel/CommandTemplateViewModel.kt | 48 +-
.../database/viewmodel/DownloadViewModel.kt | 99 ++--
.../database/viewmodel/HistoryViewModel.kt | 19 +-
.../database/viewmodel/ResultViewModel.kt | 85 ++-
.../ytdlnis/receiver/ShareActivity.kt | 4 -
.../ytdlnis/ui/ErrorDialogActivity.kt | 59 ---
.../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 62 ++-
.../downloadcard/CutVideoBottomSheetDialog.kt | 9 +-
.../ui/downloadcard/DownloadAudioFragment.kt | 96 ++--
.../downloadcard/DownloadBottomSheetDialog.kt | 89 +++-
.../downloadcard/DownloadCommandFragment.kt | 139 ++---
.../DownloadMultipleBottomSheetDialog.kt | 16 +-
.../ui/downloadcard/DownloadVideoFragment.kt | 136 +++--
.../ui/downloads/ActiveDownloadsFragment.kt | 2 -
.../downloads/CancelledDownloadsFragment.kt | 6 +-
.../ui/downloads/ErroredDownloadsFragment.kt | 11 +-
.../ytdlnis/ui/downloads/HistoryFragment.kt | 169 +-----
.../ui/downloads/SavedDownloadsFragment.kt | 2 +-
.../ui/more/CommandTemplatesFragment.kt | 168 +++++-
.../ytdlnis/ui/more/MoreFragment.kt | 9 -
.../ytdlnis/ui/more/WebViewActivity.kt | 2 -
.../more/settings/FolderSettingsFragment.kt | 39 +-
.../more/settings/GeneralSettingsFragment.kt | 15 +-
.../ui/more/settings/MainSettingsFragment.kt | 53 +-
.../com/deniscerri/ytdlnis/util/InfoUtil.kt | 500 ++++++++----------
.../com/deniscerri/ytdlnis/util/UiUtil.kt | 487 ++++++++++++++---
.../com/deniscerri/ytdlnis/util/UpdateUtil.kt | 8 +
.../deniscerri/ytdlnis/work/DownloadWorker.kt | 21 +-
.../main/res/layout/already_exists_card.xml | 9 +
.../main/res/layout/already_exists_item.xml | 43 ++
.../layout/command_templates_sort_sheet.xml | 68 +++
.../res/layout/filename_template_dialog.xml | 61 +++
app/src/main/res/layout/format_item.xml | 16 +
.../res/layout/format_select_bottom_sheet.xml | 2 +
.../res/layout/fragment_command_templates.xml | 94 ++--
...oad_queue.xml => generic_recyclerview.xml} | 0
.../history_item_details_bottom_sheet.xml | 37 +-
app/src/main/res/layout/webview_activity.xml | 93 ++--
.../main/res/menu/command_templates_menu.xml | 8 +
app/src/main/res/navigation/nav_graph.xml | 8 +-
app/src/main/res/values/arrays.xml | 107 ++++
app/src/main/res/values/strings.xml | 8 +
.../main/res/xml/downloading_preferences.xml | 11 +
app/src/main/res/xml/folders_preference.xml | 7 +-
app/src/main/res/xml/general_preferences.xml | 9 +
.../main/res/xml/processing_preferences.xml | 9 +-
app/src/main/res/xml/root_preferences.xml | 4 +-
app/src/main/res/xml/updating_preferences.xml | 5 +
build.gradle | 8 +-
gradle/wrapper/gradle-wrapper.properties | 6 +-
60 files changed, 1998 insertions(+), 1100 deletions(-)
delete mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt
create mode 100644 app/src/main/res/layout/already_exists_card.xml
create mode 100644 app/src/main/res/layout/already_exists_item.xml
create mode 100644 app/src/main/res/layout/command_templates_sort_sheet.xml
create mode 100644 app/src/main/res/layout/filename_template_dialog.xml
rename app/src/main/res/layout/{fragment_generic_download_queue.xml => generic_recyclerview.xml} (100%)
diff --git a/app/build.gradle b/app/build.gradle
index 76376731..c10a2840 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 = 8
-def versionBuild = 2 // bump for dogfood builds, public betas, etc.
+def versionBuild = 3 // bump for dogfood builds, public betas, etc.
def versionExt = ""
if (versionBuild > 0){
@@ -134,7 +134,7 @@ dependencies {
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'
+ implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.test.ext:junit-ktx:1.1.5'
implementation 'androidx.compose.ui:ui-android:1.5.3'
testImplementation "junit:junit:$junitVer"
@@ -157,10 +157,10 @@ dependencies {
implementation "androidx.room:room-runtime:2.5.2"
implementation "androidx.room:room-ktx:2.5.2"
ksp "androidx.room:room-compiler:2.5.2"
- implementation 'androidx.paging:paging-runtime-ktx:3.2.0'
+ implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
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.lifecycle:lifecycle-runtime-ktx:2.6.2'
implementation "androidx.compose.runtime:runtime:$composeVer"
androidTestImplementation 'com.google.truth:truth:1.1.5'
@@ -172,7 +172,7 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
- implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
// For media playback using ExoPlayer
@@ -188,5 +188,5 @@ dependencies {
implementation "com.anggrayudi:storage:1.5.5"
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
- implementation 'androidx.compose.material3:material3-android:1.2.0-alpha08'
+ implementation 'androidx.compose.material3:material3-android:1.2.0-alpha09'
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f8362908..c961e1c6 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -76,4 +76,17 @@
-dontwarn com.google.android.exoplayer2.**
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
+-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
+
+-keep class com.deniscerri.** { *; }
+-keep class org.apache.commons.compress.archivers.zip.** { *; }
+
+# Keep `Companion` object fields of serializable classes.
+# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
+-if @kotlinx.serialization.Serializable class **
+-keepclassmembers class <1> {
+ static <1>$Companion Companion;
+}
+
+-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
+-dontobfuscate
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1daf5700..45d2930c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,10 +4,11 @@
-
+
-
+
@@ -212,16 +213,6 @@
-
-
-
-
+
+
+ @Query("SELECT * FROM commandTemplates WHERE (title LIKE '%'||:query||'%' OR content LIKE '%'||:query||'%') ORDER BY " +
+ "CASE WHEN :sort = 'ASC' THEN title COLLATE NOCASE END ASC," +
+ "CASE WHEN :sort = 'DESC' THEN title COLLATE NOCASE END DESC," +
+ "CASE WHEN :sort = '' THEN title COLLATE NOCASE END DESC ")
+ fun getCommandsSortedByTitle(query : String, sort : String) : List
+
+ @Query("SELECT * FROM commandTemplates WHERE (title LIKE '%'||:query||'%' OR content LIKE '%'||:query||'%') ORDER BY " +
+ "CASE WHEN :sort = 'ASC' THEN length(content) END ASC," +
+ "CASE WHEN :sort = 'DESC' THEN length(content) END DESC," +
+ "CASE WHEN :sort = '' THEN length(content) END DESC ")
+ fun getCommandsSortedByContentLength(query : String, sort : String) : List
+
@Query("SELECT * FROM commandTemplates ORDER BY id DESC")
fun getAllTemplates() : List
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 e6bb8e6a..beb39c4b 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
@@ -33,7 +33,7 @@ interface HistoryDao {
fun getAllHistoryList() : List
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
- suspend fun getHistoryItem(id: Int) : HistoryItem
+ fun getHistoryItem(id: Long) : HistoryItem
@Query("SELECT * FROM history WHERE url=:url")
fun getAllHistoryByURL(url: String) : List
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 f64a08d7..f79b2f98 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
@@ -1,8 +1,10 @@
package com.deniscerri.ytdlnis.database.repository
import androidx.lifecycle.LiveData
+import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.CommandTemplate
+import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import kotlinx.coroutines.flow.Flow
@@ -10,10 +12,22 @@ class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
val items : Flow> = commandDao.getAllTemplatesFlow()
val shortcuts : Flow> = commandDao.getAllShortcutsFlow()
+ enum class CommandTemplateSortType {
+ DATE, TITLE, LENGTH
+ }
+
fun getAll() : List {
return commandDao.getAllTemplates()
}
+ fun getFiltered(query : String, sortType: CommandTemplateSortType, sort: DBManager.SORTING) : List {
+ return when(sortType){
+ CommandTemplateSortType.DATE -> commandDao.getCommandsSortedByID(query, sort.toString())
+ CommandTemplateSortType.TITLE -> commandDao.getCommandsSortedByTitle(query, sort.toString())
+ CommandTemplateSortType.LENGTH -> commandDao.getCommandsSortedByContentLength(query, sort.toString())
+ }
+ }
+
fun getTotalNumber() : Int {
return commandDao.getTotalNumber()
}
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 0517d3ef..19362428 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
@@ -1,23 +1,20 @@
package com.deniscerri.ytdlnis.database.repository
-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
+import com.deniscerri.ytdlnis.database.DBManager.SORTING
class HistoryRepository(private val historyDao: HistoryDao) {
val items : Flow> = historyDao.getAllHistory()
- enum class HistorySort{
- DESC, ASC
- }
enum class HistorySortType {
DATE, TITLE, AUTHOR, FILESIZE
}
- suspend fun getItem(id: Int) : HistoryItem {
+ fun getItem(id: Long) : HistoryItem {
return historyDao.getHistoryItem(id)
}
@@ -25,7 +22,11 @@ class HistoryRepository(private val historyDao: HistoryDao) {
return historyDao.getAllHistoryList()
}
- fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) : List {
+ fun getAllByURL(url: String) : List {
+ return historyDao.getAllHistoryByURL(url)
+ }
+
+ fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) : List {
return when(sortType){
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
@@ -33,8 +34,8 @@ class HistoryRepository(private val historyDao: HistoryDao) {
HistorySortType.FILESIZE -> {
val items = historyDao.getHistorySortedByID(query, format, site, sort.toString())
when(sort){
- HistorySort.DESC -> items.sortedByDescending { it.format.filesize }
- HistorySort.ASC -> items.sortedBy { it.format.filesize }
+ SORTING.DESC -> items.sortedByDescending { it.format.filesize }
+ SORTING.ASC -> items.sortedBy { it.format.filesize }
}
}
}
@@ -44,8 +45,11 @@ class HistoryRepository(private val historyDao: HistoryDao) {
historyDao.insert(item)
}
- suspend fun delete(item: HistoryItem){
+ suspend fun delete(item: HistoryItem, deleteFile: Boolean){
historyDao.delete(item.id)
+ if (deleteFile){
+ FileUtil.deleteFile(item.downloadPath)
+ }
}
suspend fun deleteAll(){
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 4d8d8589..7abe1f8c 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
@@ -42,7 +42,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
return res
}
- suspend fun getOne(inputQuery: String, resetResults: Boolean) : ArrayList{
+ suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean) : ArrayList{
val infoUtil = InfoUtil(context)
val v = infoUtil.getYoutubeVideo(inputQuery)
if (resetResults) {
@@ -81,22 +81,17 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
suspend fun getDefault(inputQuery: String, resetResults: Boolean) : ArrayList {
val infoUtil = InfoUtil(context)
- try {
- val items = infoUtil.getFromYTDL(inputQuery)
- if (resetResults) {
- deleteAll()
- itemCount.value = items.size
- }else{
- items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
- }
- items.forEach {
- resultDao.insert(it!!)
- }
- return items
- } catch (e: Exception) {
- Log.e(tag, e.toString())
+ val items = infoUtil.getFromYTDL(inputQuery)
+ if (resetResults) {
+ deleteAll()
+ itemCount.value = items.size
+ }else{
+ items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
}
- return arrayListOf()
+ items.forEach {
+ resultDao.insert(it!!)
+ }
+ return items
}
suspend fun delete(item: 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 3c6c8c48..69421429 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,13 +5,17 @@ import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
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.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
+import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository
+import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -21,15 +25,55 @@ import kotlinx.serialization.json.Json
class CommandTemplateViewModel(private val application: Application) : AndroidViewModel(application) {
private val repository: CommandTemplateRepository
- val items: LiveData>
+ val sortOrder = MutableLiveData(DBManager.SORTING.DESC)
+ val sortType = MutableLiveData(CommandTemplateRepository.CommandTemplateSortType.DATE)
+ private val queryFilter = MutableLiveData("")
+ private var _items = MediatorLiveData>()
+ val allItems: LiveData>
+
val shortcuts : LiveData>
private val jsonFormat = Json { prettyPrint = true }
init {
val dao = DBManager.getInstance(application).commandTemplateDao
repository = CommandTemplateRepository(dao)
- items = repository.items.asLiveData()
+ allItems = repository.items.asLiveData()
shortcuts = repository.shortcuts.asLiveData()
+
+ _items.addSource(allItems){
+ filter(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
+ }
+
+ _items.addSource(sortType){
+ filter(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
+ }
+
+ _items.addSource(queryFilter){
+ filter(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
+ }
+ }
+
+ fun getFilteredList() : LiveData>{
+ return _items
+ }
+
+ fun setSorting(sort: CommandTemplateRepository.CommandTemplateSortType){
+ if (sortType.value != sort){
+ sortOrder.value = DBManager.SORTING.DESC
+ }else{
+ sortOrder.value = if (sortOrder.value == DBManager.SORTING.DESC) {
+ DBManager.SORTING.ASC
+ } else DBManager.SORTING.DESC
+ }
+ sortType.value = sort
+ }
+
+ private fun filter(query : String, sortType: CommandTemplateRepository.CommandTemplateSortType, sort: DBManager.SORTING) = viewModelScope.launch(Dispatchers.IO){
+ _items.postValue(repository.getFiltered(query, sortType, sort))
+ }
+
+ fun setQueryFilter(filter: String){
+ queryFilter.value = filter
}
fun getTemplate(itemId: Long): CommandTemplate {
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 cc4125d7..47d716c3 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
@@ -2,17 +2,17 @@ package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import android.content.Context
-import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
import android.net.ConnectivityManager
+import android.os.Bundle
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.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
@@ -40,7 +40,7 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.VideoPreferences
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
-import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
+import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.work.AlarmScheduler
@@ -49,8 +49,11 @@ import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
import java.io.File
import java.util.Locale
import java.util.concurrent.TimeUnit
@@ -85,7 +88,22 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private var videoCodec: String?
private var audioCodec: String?
private val dao: DownloadDao
- private val historyDao: HistoryDao
+ private val historyRepository: HistoryRepository
+
+ data class DownloadsUiState(
+ var errorMessage: Pair?,
+ //action title, type, data
+ var actions: MutableList?>>?
+ )
+
+ enum class DownloadsAction {
+ DOWNLOAD_ANYWAY
+ }
+
+ val uiState: MutableStateFlow = MutableStateFlow(DownloadsUiState(
+ errorMessage = null,
+ actions = null
+ ))
enum class Type {
auto, audio, video, command
@@ -100,8 +118,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
init {
dbManager = DBManager.getInstance(application)
dao = dbManager.downloadDao
- historyDao = dbManager.historyDao
repository = DownloadRepository(dao)
+ historyRepository = HistoryRepository(dbManager.historyDao)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
infoUtil = InfoUtil(application)
@@ -178,6 +196,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return repository.getItemByID(id)
}
+ fun getHistoryItemById(id: Long) : HistoryItem? {
+ return historyRepository.getItem(id)
+ }
+
fun getDownloadType(t: Type? = null, url: String) : Type {
var type = t
@@ -249,6 +271,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
}
+ if (preferredAudioFormats.isEmpty()){
+ preferredAudioFormats.add(getFormat(resultItem.formats, Type.audio).format_id)
+ }
+
val videoPreferences = VideoPreferences(
embedSubs,
addChapters, false,
@@ -565,10 +591,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
startDownloadWorker(emptyList())
}
- suspend fun queueDownloads(items: List) = CoroutineScope(Dispatchers.IO).launch {
+ suspend fun queueDownloads(items: List, ignoreDuplicate : Boolean = false) {
val context = App.instance
val alarmScheduler = AlarmScheduler(context)
- val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
+ val activeAndQueuedDownloads = withContext(Dispatchers.IO){
+ repository.getActiveAndQueuedDownloads()
+ }
val queuedItems = mutableListOf()
val existing = mutableListOf()
@@ -578,30 +606,43 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
alarmScheduler.schedule()
}
-
items.forEach {
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
- if (activeAndQueuedDownloads.firstOrNull{d ->
- d.id = 0
- d.logID = null
- d.customFileNameTemplate = it.customFileNameTemplate
- d.status = DownloadRepository.Status.Queued.toString()
- d.toString() == it.toString()
- } != null) {
+ val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
+ d.id = 0
+ d.logID = null
+ d.customFileNameTemplate = it.customFileNameTemplate
+ d.status = DownloadRepository.Status.Queued.toString()
+ d.toString() == it.toString()
+ }
+ if (existingDownload != null && !ignoreDuplicate) {
+ it.id = existingDownload.id
existing.add(it)
}else{
//check if downloaded and file exists
- val history = historyDao.getAllHistoryByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
- if (history.any { h -> h.command.replace("-P \"(.*?)\"".toRegex(), "") == parsedCurrentCommand.replace("-P \"(.*?)\"".toRegex(), "") }){
+ val history = withContext(Dispatchers.IO){
+ historyRepository.getAllByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
+ }
+
+ val existingHistory = history.firstOrNull {
+ h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
+ }
+
+ if (existingHistory != null && !ignoreDuplicate){
+ it.id = existingHistory.id
existing.add(it)
}else{
if (it.id == 0L){
- val id = runBlocking {repository.insert(it)}
+ val id = withContext(Dispatchers.IO){
+ repository.insert(it)
+ }
it.id = id
}else if (it.status == DownloadRepository.Status.Queued.toString()){
- repository.update(it)
+ withContext(Dispatchers.IO){
+ repository.update(it)
+ }
}
queuedItems.add(it)
@@ -610,13 +651,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
if (existing.isNotEmpty()){
- val intent = Intent(context, ErrorDialogActivity::class.java)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
- intent.putExtra("title", context.resources.getString(R.string.download_already_exists))
- intent.putExtra("message",
- "${existing.size}/${items.size}\n" +
- existing.mapIndexed { index, downloadItem -> "${index+1}. ${downloadItem.title}" }.joinToString("\n"))
- context.startActivity(intent)
+ uiState.update { u -> u.copy(
+ errorMessage = Pair(R.string.download_already_exists, R.string.download_already_exists_summary),
+ actions = mutableListOf(
+ Triple(R.string.download, DownloadsAction.DOWNLOAD_ANYWAY, existing),
+ )
+ )
+ }
}
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
@@ -664,10 +705,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
.setConstraints(workConstraints.build())
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
- if (delay > 1000L){
- queuedItems.forEach {
- workRequest.addTag(it.id.toString())
- }
+ queuedItems.forEach {
+ workRequest.addTag(it.id.toString())
}
workManager.enqueueUniqueWork(
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 5e3bf31f..9a1e6db5 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
@@ -10,7 +10,7 @@ import androidx.lifecycle.viewModelScope
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
-import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySort
+import com.deniscerri.ytdlnis.database.DBManager.SORTING
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.Dispatchers
@@ -18,7 +18,7 @@ import kotlinx.coroutines.launch
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
private val repository : HistoryRepository
- val sortOrder = MutableLiveData(HistorySort.DESC)
+ val sortOrder = MutableLiveData(SORTING.DESC)
val sortType = MutableLiveData(HistorySortType.DATE)
val websiteFilter = MutableLiveData("")
private val queryFilter = MutableLiveData("")
@@ -55,11 +55,11 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
fun setSorting(sort: HistorySortType){
if (sortType.value != sort){
- sortOrder.value = HistorySort.DESC
+ sortOrder.value = SORTING.DESC
}else{
- sortOrder.value = if (sortOrder.value == HistorySort.DESC) {
- HistorySort.ASC
- } else HistorySort.DESC
+ sortOrder.value = if (sortOrder.value == SORTING.DESC) {
+ SORTING.ASC
+ } else SORTING.DESC
}
sortType.value = sort
}
@@ -76,7 +76,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
formatFilter.value = filter
}
- private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) = viewModelScope.launch(Dispatchers.IO){
+ private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) = viewModelScope.launch(Dispatchers.IO){
_items.postValue(repository.getFiltered(query, format, site, sortType, sort))
}
@@ -89,10 +89,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
}
fun delete(item: HistoryItem, deleteFile: Boolean) = viewModelScope.launch(Dispatchers.IO){
- repository.delete(item)
- if (deleteFile){
- FileUtil.deleteFile(item.downloadPath)
- }
+ repository.delete(item, deleteFile)
}
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
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 32862b43..d3f9918a 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
@@ -17,7 +17,11 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.repository.ResultRepository
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
+import com.deniscerri.ytdlnis.util.UiUtil
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
@@ -27,19 +31,28 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
val repository : ResultRepository
private val searchHistoryRepository : SearchHistoryRepository
val items : LiveData>
- val loadingItems = MutableLiveData()
- private val sharedPreferences: SharedPreferences
- var state: ResultsState = ResultsState.IDLE
- enum class ResultsState {
- PROCESSING, IDLE
+ data class ResultsUiState(
+ var processing: Boolean,
+ var errorMessage: Pair?,
+ var actions: MutableList>?
+ )
+
+ enum class ResultAction {
+ COPY_LOG
}
+ val uiState: MutableStateFlow = MutableStateFlow(ResultsUiState(
+ processing = false,
+ errorMessage = null,
+ actions = null
+ ))
+ private val sharedPreferences: SharedPreferences
+
init {
val dao = DBManager.getInstance(application).resultDao
repository = ResultRepository(dao, getApplication().applicationContext)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults.asLiveData()
- loadingItems.postValue(false)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
}
@@ -64,54 +77,38 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
deleteAll()
}
}
- suspend fun parseQueries(inputQueries: List) : MutableList{
- state = ResultsState.PROCESSING
- return if (inputQueries.size == 1){
- parseQuery(inputQueries[0], true)
- }else {
+ suspend fun parseQueries(inputQueries: List) : List {
+ if (inputQueries.size > 1){
repository.itemCount.value = inputQueries.size
- loadingItems.postValue(true)
- val items = mutableListOf()
- inputQueries.forEach {
- items.addAll(parseQuery(it, false))
- }
- state = ResultsState.IDLE
- loadingItems.postValue(false)
- items
}
- }
+ val resetResults = inputQueries.size == 1
- suspend fun parseQuery(inputQuery: String, resetResults: Boolean) : ArrayList {
- if (resetResults)
- loadingItems.postValue(true)
- val type = getQueryType(inputQuery)
- var res = arrayListOf()
+ uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
return withContext(Dispatchers.IO){
- try {
- when (type) {
- "Search" -> {
- res = repository.search(inputQuery, resetResults)
- }
- "YT_Video" -> {
- res = repository.getOne(inputQuery, resetResults)
- }
- "YT_Playlist" -> {
- res = repository.getPlaylist(inputQuery, resetResults)
- }
- "Default" -> {
- res = repository.getDefault(inputQuery, resetResults)
+ val res = mutableListOf()
+ inputQueries.forEach { inputQuery ->
+ val type = getQueryType(inputQuery)
+ try {
+ when (type) {
+ "Search" -> res.addAll(repository.search(inputQuery, resetResults))
+ "YT_Video" -> res.addAll(repository.getYoutubeVideo(inputQuery, resetResults))
+ "YT_Playlist" -> res.addAll(repository.getPlaylist(inputQuery, resetResults))
+ else -> res.addAll(repository.getDefault(inputQuery, resetResults))
}
+ } catch (e: Exception) {
+ uiState.update {it.copy(
+ processing = false,
+ errorMessage = Pair(R.string.no_results, e.message.toString()),
+ actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
+ )}
+ Log.e(tag, e.toString())
}
- } catch (e: Exception) {
- Log.e(tag, e.toString())
- }
- if (resetResults) {
- state = ResultsState.IDLE
- loadingItems.postValue(false)
}
+ uiState.update {it.copy(processing = false)}
res
}
}
+
private fun getQueryType(inputQuery: String) : String {
var type = "Search"
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
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 f583aab8..217d87d3 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
@@ -14,10 +14,7 @@ import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.util.Patterns
-import android.view.Window
import android.view.WindowManager
-import android.widget.TextView
-import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
@@ -35,7 +32,6 @@ import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt
deleted file mode 100644
index 57db0860..00000000
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.deniscerri.ytdlnis.ui
-
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.DialogInterface
-import android.graphics.drawable.ColorDrawable
-import android.os.Build
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowCompat
-import com.deniscerri.ytdlnis.R
-import com.deniscerri.ytdlnis.util.ThemeUtil
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.elevation.SurfaceColors
-
-open class ErrorDialogActivity : BaseActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- val title = intent.extras?.getString("title") ?: ""
- val message = intent.extras?.getString("message") ?: ""
-
- ThemeUtil.updateTheme(this)
- WindowCompat.setDecorFitsSystemWindows(window, false)
- ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets ->
- v.setPadding(0, 0, 0, 0)
- insets
- }
- window.run {
- setBackgroundDrawable(ColorDrawable(0))
- setLayout(
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.MATCH_PARENT
- )
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
- } else {
- setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
- }
- }
-
- val errDialog = MaterialAlertDialogBuilder(this)
- .setTitle(title)
- .setMessage(message)
- .setIcon(R.drawable.ic_cancel)
- .setPositiveButton(getString(R.string.copy_log)) { _: DialogInterface?, _: Int ->
- val clipboard: ClipboardManager =
- getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- clipboard.setText(message)
- this.finish()
- }.setOnDismissListener {
- this.finish()
- }
- this.runOnUiThread {
- errDialog.show()
- }
- super.onCreate(savedInstanceState)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
index b67af116..f6574240 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
@@ -40,6 +40,7 @@ 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
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
@@ -54,6 +55,8 @@ import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
@@ -105,6 +108,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
activity = getActivity()
mainActivity = activity as MainActivity?
quickLaunchSheet = false
+ infoUtil = InfoUtil(requireContext())
return fragmentView
}
@@ -136,6 +140,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
searchSuggestionsLinearLayout = view.findViewById(R.id.search_suggestions_linear_layout)
searchHistory = view.findViewById(R.id.search_history_scroll_view)
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
+ homeFabs = view.findViewById(R.id.home_fabs)
+ downloadFabs = homeFabs!!.findViewById(R.id.download_selected_coordinator)
+ downloadAllFabCoordinator = homeFabs!!.findViewById(R.id.download_all_coordinator)
runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) }
@@ -174,30 +181,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
quickLaunchSheet = true
}
-
- resultViewModel.loadingItems.observe(viewLifecycleOwner){
- loadingItems = it
- if (it){
- recyclerView?.setPadding(0,0,0,0)
- shimmerCards!!.startShimmer()
- shimmerCards!!.visibility = VISIBLE
- }else{
- recyclerView?.setPadding(0,0,0,100)
- shimmerCards!!.stopShimmer()
- shimmerCards!!.visibility = GONE
- if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
- downloadAllFabCoordinator!!.visibility = VISIBLE
- }else{
- downloadAllFabCoordinator!!.visibility = GONE
- }
- }
- }
-
initMenu()
-
- homeFabs = view.findViewById(R.id.home_fabs)
- downloadFabs = homeFabs!!.findViewById(R.id.download_selected_coordinator)
- downloadAllFabCoordinator = homeFabs!!.findViewById(R.id.download_all_coordinator)
val downloadSelectedFab = downloadFabs!!.findViewById(R.id.download_selected_fab)
downloadSelectedFab.tag = "downloadSelected"
downloadSelectedFab.setOnClickListener(this)
@@ -239,15 +223,40 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
}
+ lifecycleScope.launch {
+ launch{
+ resultViewModel.uiState.collectLatest { res ->
+ if (res.errorMessage != null){
+ kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res) }
+ resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
+ }
+
+ loadingItems = res.processing
+ if (res.processing){
+ recyclerView?.setPadding(0,0,0,0)
+ shimmerCards!!.startShimmer()
+ shimmerCards!!.visibility = VISIBLE
+ }else{
+ recyclerView?.setPadding(0,0,0,100)
+ shimmerCards!!.stopShimmer()
+ shimmerCards!!.visibility = GONE
+ if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
+ downloadAllFabCoordinator!!.visibility = VISIBLE
+ }else{
+ downloadAllFabCoordinator!!.visibility = GONE
+ }
+ }
+ }
+ }
+ }
+
}
override fun onResume() {
super.onResume()
if(arguments?.getString("url") == null){
- if (resultViewModel.state == ResultViewModel.ResultsState.IDLE){
+ if (!resultViewModel.uiState.value.processing){
resultViewModel.checkTrending()
- }else{
- resultViewModel.loadingItems.postValue(true)
}
}else{
arguments?.remove("url")
@@ -302,7 +311,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
- infoUtil = InfoUtil(requireContext())
searchView!!.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.SHOWN) {
val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch")
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt
index 8b18d986..bf0939af 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt
@@ -30,6 +30,7 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.util.InfoUtil
+import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior
@@ -496,10 +497,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
}
chip.setOnLongClickListener {
- val deleteDialog = MaterialAlertDialogBuilder(requireContext())
- deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + chip.text + "\"!")
- deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
- deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
+ UiUtil.showGenericDeleteDialog(requireContext(), chip.text.toString(), accepted = {
player.seekTo(0)
player.pause()
chipGroup.removeView(chip)
@@ -509,8 +507,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
player.stop()
dismiss()
}
- }
- deleteDialog.show()
+ })
true
}
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 196422d2..5874f9c7 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
@@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.app.Activity
import android.content.Intent
+import android.content.SharedPreferences
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@@ -11,11 +12,13 @@ import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
+import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.view.isEmpty
+import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@@ -52,6 +55,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
lateinit var downloadItem : DownloadItem
lateinit var title : TextInputLayout
lateinit var author : TextInputLayout
+ lateinit var preferences: SharedPreferences
+ lateinit var shownFields: List
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -63,6 +68,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext())
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
+ preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList()
return fragmentView
}
@@ -82,6 +89,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
try {
title = view.findViewById(R.id.title_textinput)
+ title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
if (title.editText?.text?.isEmpty() == true){
title.editText!!.setText(downloadItem.title)
}
@@ -100,6 +108,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
}
author = view.findViewById(R.id.author_textinput)
+ author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
if (author.editText?.text?.isEmpty() == true){
author.editText!!.setText(downloadItem.author)
}
@@ -158,9 +167,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
if (currentDownloadItem!!.type != Type.audio){
downloadItem.type = Type.audio
runCatching {
- downloadItem.format =
- downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
- .maxByOrNull { it.filesize }!!
+ downloadItem.format = downloadViewModel.getFormat(downloadItem.allFormats, Type.audio)
}.onFailure {
downloadItem.format = genericAudioFormats.last()
}
@@ -172,6 +179,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
var containerPreference = sharedPreferences.getString("audio_format", "Default")
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
val container = view.findViewById(R.id.downloadContainer)
+ container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
val containerAutoCompleteTextView =
view.findViewById(R.id.container_textview)
@@ -190,6 +198,10 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
resultItem?.formats?.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) })
if (resultItem != null){
resultViewModel.update(resultItem!!)
+ kotlin.runCatching {
+ val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
+ f1.updateUI(resultItem)
+ }
}
}
}
@@ -229,44 +241,50 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
if (containers[index] == getString(R.string.defaultValue)) downloadItem.container = ""
}
- UiUtil.configureAudio(
- view,
- requireActivity(),
- listOf(downloadItem),
- embedThumbClicked = {
- downloadItem.audioPreferences.embedThumb = it
- },
- splitByChaptersClicked = {
- downloadItem.audioPreferences.splitByChapters = it
- },
- filenameTemplateSet = {
- downloadItem.customFileNameTemplate = it
- },
- sponsorBlockItemsSet = { values, checkedItems ->
- downloadItem.audioPreferences.sponsorBlockFilters.clear()
- for (i in checkedItems.indices) {
- if (checkedItems[i]) {
- downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
- }
- }
- },
- cutClicked = {cutVideoListener ->
- if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
- val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
- bottomSheet.show(parentFragmentManager, "cutVideoSheet")
- }
- },
- extraCommandsClicked = {
- val callback = object : ExtraCommandsListener {
- override fun onChangeExtraCommand(c: String) {
- downloadItem.extraCommands = c
- }
- }
- val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
- bottomSheetDialog.show(parentFragmentManager, "extraCommands")
+ view.findViewById(R.id.adjust).apply {
+ visibility = if (shownFields.contains("adjust_audio")) View.VISIBLE else View.GONE
+ if (isVisible){
+ UiUtil.configureAudio(
+ view,
+ requireActivity(),
+ listOf(downloadItem),
+ embedThumbClicked = {
+ downloadItem.audioPreferences.embedThumb = it
+ },
+ splitByChaptersClicked = {
+ downloadItem.audioPreferences.splitByChapters = it
+ },
+ filenameTemplateSet = {
+ downloadItem.customFileNameTemplate = it
+ },
+ sponsorBlockItemsSet = { values, checkedItems ->
+ downloadItem.audioPreferences.sponsorBlockFilters.clear()
+ for (i in checkedItems.indices) {
+ if (checkedItems[i]) {
+ downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
+ }
+ }
+ },
+ cutClicked = {cutVideoListener ->
+ if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
+ val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
+ bottomSheet.show(parentFragmentManager, "cutVideoSheet")
+ }
+ },
+ extraCommandsClicked = {
+ val callback = object : ExtraCommandsListener {
+ override fun onChangeExtraCommand(c: String) {
+ downloadItem.extraCommands = c
+ }
+ }
+
+ val bottomSheetDialog = AddExtraCommandsDialog(downloadItem, callback)
+ bottomSheetDialog.show(parentFragmentManager, "extraCommands")
+ }
+ )
}
- )
+ }
}catch (e : Exception){
e.printStackTrace()
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt
index ed0d8a82..c4ee9de7 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadBottomSheetDialog.kt
@@ -1,11 +1,8 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
-import android.app.ActivityManager
import android.app.Dialog
-import android.content.Context
import android.content.DialogInterface
-import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle
@@ -20,14 +17,12 @@ import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.core.content.edit
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
-import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
+import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
@@ -45,9 +40,10 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.collectLatest
import java.text.SimpleDateFormat
import java.util.*
@@ -205,6 +201,11 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
val scheduleBtn = view.findViewById(R.id.bottomsheet_schedule_button)
+ scheduleBtn.visibility = if(sharedPreferences.getStringSet("modify_download_card", setOf())!!.contains("schedule")){
+ View.VISIBLE
+ }else{
+ View.GONE
+ }
val download = view.findViewById