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