From 3e4d89577ad5737a0d9c1f92f29d85a2deebf7b7 Mon Sep 17 00:00:00 2001 From: zaednasr <75589932+zaednasr@users.noreply.github.com> Date: Sun, 19 Nov 2023 16:31:00 +0100 Subject: [PATCH] some changes - fixed log removing some lines - added MASTER channel in yt-dlp updates - made errored downloads as a separate notification channel - fix notification language conflict for portugal brasil in worker notification - kept state of download card when going in landscape, even while updating data - add crop thumbnail to adjust audio preferences - fix command templates creation showing extra command checkboxes even though extra command is disabled - fix preferred audio codec disrupting preferred audio id --- app/build.gradle | 4 +- .../com/deniscerri/ytdlnis/MainActivity.kt | 6 +- .../database/models/AudioPreferences.kt | 1 + .../database/repository/LogRepository.kt | 28 +++--- .../database/viewmodel/DownloadViewModel.kt | 20 ++--- .../database/viewmodel/ResultViewModel.kt | 43 +++++---- .../ui/downloadcard/DownloadAudioFragment.kt | 6 ++ .../downloadcard/DownloadBottomSheetDialog.kt | 76 +++++++++------- .../DownloadMultipleBottomSheetDialog.kt | 5 ++ .../ui/downloadcard/DownloadVideoFragment.kt | 3 + .../ytdlnis/ui/downloads/HistoryFragment.kt | 31 ++++++- .../ui/more/settings/MainSettingsFragment.kt | 1 + .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 87 ++++++++++--------- .../ytdlnis/util/NotificationUtil.kt | 22 +++-- .../com/deniscerri/ytdlnis/util/UiUtil.kt | 34 ++++++-- .../com/deniscerri/ytdlnis/util/UpdateUtil.kt | 10 ++- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 19 ++-- .../main/res/drawable/baseline_source_24.xml | 5 ++ app/src/main/res/layout/adjust_audio.xml | 40 +++++---- .../main/res/layout/already_exists_item.xml | 5 +- .../res/layout/create_command_template.xml | 2 + app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values/arrays.xml | 13 +++ app/src/main/res/values/strings.xml | 6 +- app/src/main/res/xml/updating_preferences.xml | 16 ++-- build.gradle | 2 +- 26 files changed, 316 insertions(+), 170 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_source_24.xml diff --git a/app/build.gradle b/app/build.gradle index 3a17bbe5..efd9650b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ plugins { def properties = new Properties() def versionMajor = 1 def versionMinor = 6 -def versionPatch = 8 -def versionBuild = 1 // bump for dogfood builds, public betas, etc. +def versionPatch = 9 +def versionBuild = 0 // bump for dogfood builds, public betas, etc. def versionExt = "" if (versionBuild > 0){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index ab3a0b47..b79c82b6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -7,20 +7,22 @@ import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager -import android.content.res.Configuration import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings import android.util.Log +import android.view.Gravity import android.view.View import android.view.WindowInsets import android.widget.CheckBox +import android.widget.FrameLayout import android.widget.TextView import android.widget.Toast import androidx.annotation.RequiresApi import androidx.constraintlayout.widget.ConstraintLayout +import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ActivityCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.forEach @@ -30,7 +32,6 @@ import androidx.fragment.app.FragmentContainerView import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController -import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import androidx.navigation.ui.setupWithNavController @@ -42,7 +43,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.ui.BaseActivity import com.deniscerri.ytdlnis.ui.HomeFragment -import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.ThemeUtil diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt index 2a562f9a..fdd7ac83 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/AudioPreferences.kt @@ -6,6 +6,7 @@ import kotlinx.parcelize.Parcelize @Parcelize data class AudioPreferences( var embedThumb: Boolean = true, + var cropThumb: Boolean? = null, var splitByChapters: Boolean = false, var sponsorBlockFilters: ArrayList = arrayListOf() ) : Parcelable diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt index 1b37b1a2..76a45df6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/LogRepository.kt @@ -41,25 +41,17 @@ class LogRepository(private val logDao: LogDao) { suspend fun update(line: String, id: Long){ runCatching { - val item = getItem(id) - val log = item.content - //clean duplicate progress + add newline - val lines = log.split("\n").toMutableList() - run loop@ { - for(i in lines.size - 1 downTo 0){ - val l = lines[i] - if(l.contains("\\[download]( *?)(\\d)(.*?)".toRegex())){ - lines[i] = "" - return@loop - } - } + val item = getItem(id) ?: return + val log = item.content ?: "" + val lines = log.split("\n") + //clean dublicate progress + add newline + var newLine = line + if (newLine.contains("[download")){ + newLine = "[download]" + line.split("[download]").last() } - val l = if (line.contains("[download]")) { - "[download]" + line.split("[download]").last() - }else { - line - } - item.content = lines.filter { it.isNotBlank() }.joinToString("\n") + "\n${l}" + + val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" + item.content = l logDao.update(item) } } 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 f412fc3b..fe2b53c1 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 @@ -233,6 +233,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val addChapters = sharedPreferences.getBoolean("add_chapters", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) + val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false) var type = getDownloadType(givenType, resultItem.url) if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video @@ -257,7 +258,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) - val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!)) + val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!)) val preferredAudioFormats = arrayListOf() for (f in resultItem.formats.sortedBy { it.format_id }){ @@ -391,6 +392,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val addChapters = sharedPreferences.getBoolean("add_chapters", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) + val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false) + val customFileNameTemplate = when(historyItem.type) { Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s") Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") @@ -418,7 +421,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application else -> "" } - val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!)) + val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!)) val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs) val downloadPath = File(historyItem.downloadPath) val path = if (downloadPath.exists()) downloadPath.parent else defaultPath @@ -625,6 +628,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application alarmScheduler.schedule() } + if (items.any { it.playlistTitle.isEmpty() } && items.size > 1){ + items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" } + } + items.forEach { if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString() val currentCommand = infoUtil.buildYoutubeDLRequest(it) @@ -679,14 +686,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } } - if (items.any { it.playlistTitle.isEmpty() }){ - items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" } - withContext(Dispatchers.IO){ - dao.updateMultiple(items) - } - } - - if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){ + if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){ startDownloadWorker(queuedItems) if(!useScheduler){ 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 7cb0b3e0..e3df1d87 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 @@ -29,6 +29,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.regex.Pattern @@ -195,50 +196,58 @@ class ResultViewModel(application: Application) : AndroidViewModel(application) suspend fun updateItemData(res: ResultItem){ if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){ updateResultDataJob = viewModelScope.launch(Dispatchers.IO) { - kotlin.runCatching { - updatingData.emit(true) - val result = parseQueries(listOf(res.url)) - updatingData.emit(false) - updateResultData.emit(result) - }.onFailure { + updatingData.emit(true) + val result = parseQueries(listOf(res.url)) + updatingData.emit(false) + updateResultData.emit(result) + } + } + + updateResultDataJob?.start() + updateResultDataJob?.invokeOnCompletion { + if (it != null){ + viewModelScope.launch(Dispatchers.IO) { updatingData.emit(false) updateResultData.emit(mutableListOf()) } } } - - updateResultDataJob?.start() } suspend fun cancelUpdateItemData(){ + updateResultDataJob?.cancel() updatingData.emit(false) updateResultData.emit(null) - updateResultDataJob?.cancel() } suspend fun cancelUpdateFormatsItemData(){ + updateFormatsResultDataJob?.cancel() updatingFormats.emit(false) updateFormatsResultData.emit(null) - updateFormatsResultDataJob?.cancel() } suspend fun updateFormatItemData(result: ResultItem){ - updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) { - kotlin.runCatching { + if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) { + updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) { updatingFormats.emit(true) val formats = infoUtil.getFormats(result.url) updatingFormats.emit(false) - if (formats.isNotEmpty()){ + if (formats.isNotEmpty() && isActive) { val res = getItemByURL(result.url) res.formats = formats.toMutableList() update(res) } updateFormatsResultData.emit(formats.toMutableList()) - }.onFailure { - updatingFormats.emit(false) - updateFormatsResultData.emit(mutableListOf()) + } + updateFormatsResultDataJob?.start() + updateFormatsResultDataJob?.invokeOnCompletion { + if (it != null){ + viewModelScope.launch(Dispatchers.IO) { + updatingFormats.emit(false) + updateFormatsResultData.emit(mutableListOf()) + } + } } } - updateFormatsResultDataJob?.start() } } \ No newline at end of file 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 ee8b7150..848b74d6 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 @@ -33,10 +33,13 @@ import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.card.MaterialCardView +import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE import com.google.gson.Gson import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @@ -258,6 +261,9 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private embedThumbClicked = { downloadItem.audioPreferences.embedThumb = it }, + cropThumbClicked = { + downloadItem.audioPreferences.cropThumb = it + }, splitByChaptersClicked = { downloadItem.audioPreferences.splitByChapters = it }, 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 ecbb5cb8..b0f4c73c 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 @@ -50,6 +50,7 @@ import com.google.android.material.tabs.TabLayout import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -90,6 +91,33 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao infoUtil = InfoUtil(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + + val res: ResultItem? + val dwl: DownloadItem? + + if (Build.VERSION.SDK_INT >= 33){ + res = arguments?.getParcelable("result", ResultItem::class.java) + dwl = arguments?.getParcelable("downloadItem", DownloadItem::class.java) + }else{ + res = arguments?.getParcelable("result") + dwl = arguments?.getParcelable("downloadItem") + } + type = arguments?.getSerializable("type") as Type + + if (res == null){ + dismiss() + return + } + result = res + currentDownloadItem = dwl + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + val downloadItem = getDownloadItem() + arguments?.putParcelable("result", result) + arguments?.putParcelable("downloadItem", downloadItem) + arguments?.putSerializable("type", downloadItem.type) } @SuppressLint("RestrictedApi", "InflateParams") @@ -98,26 +126,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null) dialog.setContentView(view) - if (Build.VERSION.SDK_INT >= 33){ - arguments?.getParcelable("result", ResultItem::class.java) - }else{ - arguments?.getParcelable("result") - }.apply { - if (this == null){ - dismiss() - return - }else{ - result = this - } - } - - type = arguments?.getSerializable("type") as Type - currentDownloadItem = if (Build.VERSION.SDK_INT >= 33){ - arguments?.getParcelable("downloadItem", DownloadItem::class.java) - }else{ - arguments?.getParcelable("downloadItem") - } - dialog.setOnShowListener { behavior = BottomSheetBehavior.from(view.parent as View) val displayMetrics = DisplayMetrics() @@ -297,16 +305,18 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } download!!.setOnClickListener { lifecycleScope.launch { - resultViewModel.cancelUpdateItemData() - resultViewModel.cancelUpdateFormatsItemData() + withContext(Dispatchers.IO){ + resultViewModel.cancelUpdateItemData() + resultViewModel.cancelUpdateFormatsItemData() + } + scheduleBtn.isEnabled = false + download.isEnabled = false + val item: DownloadItem = getDownloadItem() + runBlocking { + downloadViewModel.queueDownloads(listOf(item)) + } + dismiss() } - scheduleBtn.isEnabled = false - download.isEnabled = false - val item: DownloadItem = getDownloadItem() - runBlocking { - downloadViewModel.queueDownloads(listOf(item)) - } - dismiss() } download.setOnLongClickListener { @@ -371,7 +381,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } } - CoroutineScope(Dispatchers.IO).launch{ + lifecycleScope.launch{ downloadViewModel.uiState.collectLatest { res -> if (res.errorMessage != null) { withContext(Dispatchers.Main){ @@ -427,6 +437,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { lifecycleScope.launch { resultViewModel.updatingFormats.collectLatest { if (it){ + delay(500) runCatching { val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment f1.view?.findViewById(R.id.format_loading_progress)?.visibility = View.VISIBLE @@ -528,6 +539,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + } + private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{ return when(selectedTabPosition){ 0 -> { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index 426d88ce..807955d6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -412,6 +412,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure it.audioPreferences.embedThumb = enabled } }, + cropThumbClicked = {enabled -> + items.forEach { + it.audioPreferences.cropThumb = enabled + } + }, splitByChaptersClicked = {enabled -> items.forEach { it.audioPreferences.splitByChapters = enabled diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt index 37e927bb..cc958c87 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt @@ -34,12 +34,15 @@ import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil import com.google.android.material.card.MaterialCardView +import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE import com.google.android.material.textfield.TextInputLayout.EndIconMode import com.google.gson.Gson import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt index f14e7547..317c7aad 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt @@ -31,6 +31,7 @@ import androidx.core.os.bundleOf import androidx.core.view.forEach 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.GridLayoutManager @@ -57,7 +58,11 @@ import com.google.android.material.chip.ChipGroup import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext /** * A fragment representing a list of Items. @@ -172,8 +177,32 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{ } } } - downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] + + + lifecycleScope.launch{ + downloadViewModel.uiState.collectLatest { res -> + if (res.errorMessage != null) { + withContext(Dispatchers.Main){ + kotlin.runCatching { + UiUtil.handleDownloadsResponse( + requireActivity(), + requireActivity().lifecycleScope, + requireActivity().supportFragmentManager, + res, + downloadViewModel, + historyViewModel) + } + } + downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState( + errorMessage = null, + actions = null + ) + } + } + } + + initMenu() initChips() } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt index 60bb7083..8a515daf 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/MainSettingsFragment.kt @@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel +import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.UpdateUtil import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt index b148851d..778de788 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -437,47 +437,51 @@ class InfoUtil(private val context: Context) { } private fun getFormatsFromYTDL(url: String) : List { - val request = YoutubeDLRequest(url) - request.addOption("--print", "%(formats)s") - request.addOption("--print", "%(duration)s") - request.addOption("--skip-download") - request.addOption("-R", "1") - request.addOption("--socket-timeout", "5") + try { + val request = YoutubeDLRequest(url) + request.addOption("--print", "%(formats)s") + request.addOption("--print", "%(duration)s") + request.addOption("--skip-download") + request.addOption("-R", "1") + request.addOption("--compat-options", "manifest-filesize-approx") + request.addOption("--socket-timeout", "5") - if (sharedPreferences.getBoolean("use_cookies", false)){ - FileUtil.getCookieFile(context){ - request.addOption("--cookies", it) + if (sharedPreferences.getBoolean("use_cookies", false)){ + FileUtil.getCookieFile(context){ + request.addOption("--cookies", it) + } + + val useHeader = sharedPreferences.getBoolean("use_header", false) + val header = sharedPreferences.getString("useragent_header", "") + if (useHeader && !header.isNullOrBlank()){ + request.addOption("--add-header","User-Agent:${header}") + } } - val useHeader = sharedPreferences.getBoolean("use_header", false) - val header = sharedPreferences.getString("useragent_header", "") - if (useHeader && !header.isNullOrBlank()){ - request.addOption("--add-header","User-Agent:${header}") + val proxy = sharedPreferences.getString("proxy", "") + if (proxy!!.isNotBlank()) { + request.addOption("--proxy", proxy) } + + + + val res = YoutubeDL.getInstance().execute(request) + val results: Array = try { + val lineSeparator = System.getProperty("line.separator") + res.out.split(lineSeparator!!).toTypedArray() + } catch (e: Exception) { + arrayOf(res.out) + } + val json = results[0] + val jsonArray = JSONArray(json) + + return parseYTDLFormats(jsonArray) + }catch (e: Exception){ + return listOf() } - - val proxy = sharedPreferences.getString("proxy", "") - if (proxy!!.isNotBlank()) { - request.addOption("--proxy", proxy) - } - - - - val res = YoutubeDL.getInstance().execute(request) - val results: Array = try { - val lineSeparator = System.getProperty("line.separator") - res.out.split(lineSeparator!!).toTypedArray() - } catch (e: Exception) { - arrayOf(res.out) - } - val json = results[0] - val jsonArray = JSONArray(json) - - return parseYTDLFormats(jsonArray) } fun getFormatsMultiple(urls: List, progress: (progress: List) -> Unit){ - val urlsFile = File(context.cacheDir, "urls.txt") urlsFile.delete() urlsFile.createNewFile() @@ -575,6 +579,7 @@ class InfoUtil(private val context: Context) { request.addOption("-j") request.addOption("--skip-download") request.addOption("-R", "1") + request.addOption("--compat-options", "manifest-filesize-approx") request.addOption("--socket-timeout", "5") if (sharedPreferences.getBoolean("use_cookies", false)){ @@ -1016,7 +1021,7 @@ class InfoUtil(private val context: Context) { val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']" if(downloadItem.type != DownloadViewModel.Type.command){ - request.addOption("--trim-filenames", downDir.absolutePath.length + 120) + request.addOption("--trim-filenames", downDir.absolutePath.length + 117) if (downloadItem.SaveThumb) { request.addOption("--write-thumbnail") @@ -1161,7 +1166,8 @@ class InfoUtil(private val context: Context) { if (downloadItem.audioPreferences.embedThumb) { request.addOption("--embed-thumbnail") if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg") - if (sharedPreferences.getBoolean("crop_thumbnail", true)){ + val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true) + if (cropThumb){ try { val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt") val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"" @@ -1222,11 +1228,14 @@ class InfoUtil(private val context: Context) { val f = StringBuilder() if(!usingGenericFormat){ - if (preferredAudioCodec.isNotBlank()){ - f.append("$videof+$aCodecPref/") + if (audiof.isBlank()){ + f.append("$videof/bv/best") + }else{ + f.append("$videof+$audiof/") + if (preferredAudioCodec.isNotBlank()) + f.append("$videof+$aCodecPref/") + f.append("$videof/best") } - val aa = if (audiof.isNotBlank()) "+$audiof" else "" - f.append("$videof$aa/$videof/best") if (audiof.contains("+")){ request.addOption("--audio-multistreams") diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt index b64f3088..933a25f4 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -75,6 +75,14 @@ class NotificationUtil(var context: Context) { channel.description = description notificationManager.createNotificationChannel(channel) + //finished or errored downloads + name = resources.getString(R.string.errored_downloads) + description = + resources.getString(R.string.errored_download_notification_channel_description) + channel = NotificationChannel(DOWNLOAD_ERRORED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH) + channel.description = description + notificationManager.createNotificationChannel(channel) + //misc name = resources.getString(R.string.misc) description = "" @@ -264,7 +272,7 @@ class NotificationUtil(var context: Context) { logID: Long?, res: Resources ) { - val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID) + val notificationBuilder = getBuilder(DOWNLOAD_ERRORED_CHANNEL_ID) val bundle = Bundle() if (logID != null){ @@ -304,7 +312,7 @@ class NotificationUtil(var context: Context) { notificationBuilder.setContentIntent(errorPendingIntent) notificationBuilder.addAction(0, res.getString(R.string.logs), errorPendingIntent) } - notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build()) + notificationManager.notify(DOWNLOAD_ERRORED_NOTIFICATION_ID, notificationBuilder.build()) } @@ -540,14 +548,16 @@ class NotificationUtil(var context: Context) { const val DOWNLOAD_SERVICE_CHANNEL_ID = "1" const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2" const val DOWNLOAD_FINISHED_CHANNEL_ID = "3" + const val DOWNLOAD_WORKER_CHANNEL_ID = "5" + const val DOWNLOAD_MISC_CHANNEL_ID = "4" + const val DOWNLOAD_ERRORED_CHANNEL_ID = "6" + const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3 const val DOWNLOAD_RESUME_NOTIFICATION_ID = 40000 const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5 - const val FORMAT_UPDATING_NOTIFICATION_ID = 6 const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7 - const val DOWNLOAD_MISC_CHANNEL_ID = "4" - const val DOWNLOAD_MISC_NOTIFICATION_ID = 4 - const val DOWNLOAD_WORKER_CHANNEL_ID = "5" + const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 6 + private const val PROGRESS_MAX = 100 private const val PROGRESS_CURR = 0 } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt index 4394590b..501dfd2b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt @@ -36,6 +36,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.FileProvider import androidx.core.view.children +import androidx.core.view.isNotEmpty import androidx.core.view.isVisible import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.FragmentManager @@ -189,8 +190,13 @@ object UiUtil { override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun afterTextChanged(p0: Editable?) { - ok.isEnabled = - !(title.editText!!.text.isEmpty() || content.editText!!.text.isEmpty()) + ok.isEnabled = title.editText!!.text.isNotEmpty() && + content.editText!!.text.isNotEmpty() && + if (extraCommandsSwitch.isChecked){ + ((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked)) + }else{ + true + } } }) @@ -198,8 +204,14 @@ object UiUtil { override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun afterTextChanged(p0: Editable?) { - ok.isEnabled = - (title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()) + ok.isEnabled = title.editText!!.text.isNotEmpty() && + content.editText!!.text.isNotEmpty() && + if (extraCommandsSwitch.isChecked){ + ((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked)) + }else{ + true + } + if (content.editText!!.text.isNotEmpty()){ content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_all) }else{ @@ -241,11 +253,11 @@ object UiUtil { } extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b -> - ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked + ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty() } extraCommandsVideo.setOnCheckedChangeListener { compoundButton, b -> - ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked + ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty() } commandTemplateViewModel.shortcuts.observe(lifeCycle){ @@ -1210,6 +1222,7 @@ object UiUtil { context: Activity, items: List, embedThumbClicked: (Boolean) -> Unit, + cropThumbClicked: (Boolean) -> Unit, splitByChaptersClicked: (Boolean) -> Unit, filenameTemplateSet: (String) -> Unit, sponsorBlockItemsSet: (Array, List) -> Unit, @@ -1218,9 +1231,18 @@ object UiUtil { ){ val embedThumb = view.findViewById(R.id.embed_thumb) + val cropThumb = view.findViewById(R.id.crop_thumb) + embedThumb!!.isChecked = items.all { it.audioPreferences.embedThumb } + cropThumb.isVisible = embedThumb.isChecked embedThumb.setOnClickListener { embedThumbClicked(embedThumb.isChecked) + cropThumb.isVisible = embedThumb.isChecked + } + + cropThumb!!.isChecked = items.all { it.audioPreferences.cropThumb == true } + cropThumb.setOnClickListener { + cropThumbClicked(cropThumb.isChecked) } val splitByChapters = view.findViewById(R.id.split_by_chapters) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt index 3d6a290f..6fa3ddd1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt @@ -182,11 +182,15 @@ class UpdateUtil(var context: Context) { } updatingYTDL = true + val channelMap = mapOf( + "stable" to YoutubeDL.UpdateChannel._STABLE, + "nightly" to YoutubeDL.UpdateChannel._NIGHTLY, + "master" to YoutubeDL.UpdateChannel._MASTER, + ) + val channel = sharedPreferences.getString("ytdlp_source", "nightly") try { - YoutubeDL.getInstance().updateYoutubeDL( - context, if (sharedPreferences.getBoolean("nightly_ytdl", false) ) YoutubeDL.UpdateChannel._NIGHTLY else YoutubeDL.UpdateChannel._STABLE - ).apply { + YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply { updatingYTDL = false } }catch (e: Exception){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt index c01ef032..7cad603c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -102,6 +102,7 @@ class DownloadWorker( notificationUtil.notify(downloadItem.id.toInt(), notification) CoroutineScope(Dispatchers.IO).launch { + val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite() val request = infoUtil.buildYoutubeDLRequest(downloadItem) downloadItem.status = DownloadRepository.Status.Active.toString() @@ -142,8 +143,6 @@ class DownloadWorker( dao.update(downloadItem) } - val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite() - runCatching { YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id)) @@ -212,11 +211,17 @@ class DownloadWorker( //put download in history val incognito = sharedPreferences.getBoolean("incognito", false) if (!incognito) { - val unixtime = System.currentTimeMillis() / 1000 - val file = File(finalPaths?.first()!!) - downloadItem.format.filesize = file.length() - val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString) - historyDao.insert(historyItem) + if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) { + Looper.prepare().run { + Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() + } + }else{ + val unixTime = System.currentTimeMillis() / 1000 + val file = File(finalPaths?.first()!!) + downloadItem.format.filesize = file.length() + val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixTime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString) + historyDao.insert(historyItem) + } } notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) diff --git a/app/src/main/res/drawable/baseline_source_24.xml b/app/src/main/res/drawable/baseline_source_24.xml new file mode 100644 index 00000000..d75a1a09 --- /dev/null +++ b/app/src/main/res/drawable/baseline_source_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/adjust_audio.xml b/app/src/main/res/layout/adjust_audio.xml index df0f7d07..1004361a 100644 --- a/app/src/main/res/layout/adjust_audio.xml +++ b/app/src/main/res/layout/adjust_audio.xml @@ -33,6 +33,15 @@ android:checked="false" android:text="@string/embed_thumb"/> + + - - - @@ -103,6 +96,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/app/src/main/res/layout/already_exists_item.xml b/app/src/main/res/layout/already_exists_item.xml index 34a2d48d..b5e2c91f 100644 --- a/app/src/main/res/layout/already_exists_item.xml +++ b/app/src/main/res/layout/already_exists_item.xml @@ -6,7 +6,7 @@ android:layout_marginBottom="10dp" android:checkable="true" android:paddingHorizontal="20dp" - android:paddingVertical="5dp" + android:paddingBottom="5dp" android:clickable="true" android:focusable="true" android:background="?android:attr/selectableItemBackground" @@ -18,8 +18,9 @@ style="@style/Widget.Material3.Button.TonalButton.Icon" android:layout_width="0dp" android:layout_height="wrap_content" + app:cornerRadius="10dp" android:autoLink="all" - android:gravity="start" + android:gravity="center_vertical" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginEnd="10dp" app:layout_constraintEnd_toStartOf="@+id/already_exists_edit" diff --git a/app/src/main/res/layout/create_command_template.xml b/app/src/main/res/layout/create_command_template.xml index 50d99ee2..4b6f1856 100644 --- a/app/src/main/res/layout/create_command_template.xml +++ b/app/src/main/res/layout/create_command_template.xml @@ -133,6 +133,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="20dp" + android:visibility="gone" android:checked="true" android:text="@string/audio" /> @@ -141,6 +142,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" + android:visibility="gone" android:text="@string/video" /> diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 47ce374b..0ec0b5fa 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -308,7 +308,6 @@ ファイルのエクスポート スケジュール User-Agent ヘッダー - スキップ ライセンス 再度ダウンロードをしますか\? ダウンロードカードの変更 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 15414fe6..fb0ebfaa 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1081,4 +1081,17 @@ cookies + + @string/defaultValue + @string/update_ytdl_nightly + @string/update_ytdl_master + + + + + stable + nightly + master + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cb3da8b..0d7f32d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -232,7 +232,9 @@ Monochrome "Downloaded: " Finished Downloads - Notification for finished or faulty downloads + Notification for finished downloads + Errored Downloads + Notification for faulty downloads Quick Download Don\'t fetch data in the download card Remove Audio @@ -319,4 +321,6 @@ Ignore Modify Download Card Export File + yt-dlp Source + Master Version of yt-dlp \ No newline at end of file diff --git a/app/src/main/res/xml/updating_preferences.xml b/app/src/main/res/xml/updating_preferences.xml index e81f6ef4..19505f42 100644 --- a/app/src/main/res/xml/updating_preferences.xml +++ b/app/src/main/res/xml/updating_preferences.xml @@ -21,13 +21,15 @@ app:key="ytdl-version" app:title="@string/ytdl_version"/> - + +