From fba26b50d0eb8d7967ce86626fa095d9e6e1c66c Mon Sep 17 00:00:00 2001 From: zaednasr <75589932+zaednasr@users.noreply.github.com> Date: Sun, 17 Dec 2023 21:02:04 +0100 Subject: [PATCH] 1.7.0 - added subtitle language codes suggestions in the settings page - made the extractor chips in history page Sentence case - added a changelog screen where you can see recent releases and you can download the apks from it too - prevented app from crashing when trying to backup from a corrupted backup - added uploader_id as fallback for author data fetching in yt-dlp in case others are empty - fixed null pointer exception when running the update multiple items formats worker - added the seconds where the cut starts on downloads with cuts in them - made autonumbers be normal numbers instead of being 5 digits - fixed filename templates in cut files and added the index in the beginning and fixed bugs if the users left the template as empty - added 240p as a generic format --- app/build.gradle | 4 +- .../com/deniscerri/ytdlnis/MainActivity.kt | 4 - .../ytdlnis/database/models/GithubRelease.kt | 3 + .../database/repository/ResultRepository.kt | 2 +- .../database/viewmodel/DownloadViewModel.kt | 38 ++-- .../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 175 +++++++++--------- .../ytdlnis/ui/adapter/HistoryAdapter.kt | 2 +- .../DownloadMultipleBottomSheetDialog.kt | 4 +- .../FormatSelectionBottomSheetDialog.kt | 34 ++-- .../ytdlnis/ui/downloads/HistoryFragment.kt | 4 +- .../more/settings/GeneralSettingsFragment.kt | 10 +- .../ui/more/settings/MainSettingsFragment.kt | 73 ++++---- .../settings/ProcessingSettingsFragment.kt | 16 ++ .../more/settings/UpdateSettingsFragment.kt | 80 ++++++-- .../com/deniscerri/ytdlnis/util/Extensions.kt | 1 + .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 155 +++++++++------- .../com/deniscerri/ytdlnis/util/UiUtil.kt | 21 ++- .../com/deniscerri/ytdlnis/util/UpdateUtil.kt | 88 ++++++++- .../work/UpdatePlaylistFormatsWorker.kt | 2 +- .../res/drawable/baseline_code_off_24.xml | 5 + app/src/main/res/layout/changelog_item.xml | 62 +++++++ app/src/main/res/values/arrays.xml | 2 + app/src/main/res/values/strings.xml | 3 + .../main/res/xml/processing_preferences.xml | 2 +- app/src/main/res/xml/root_preferences.xml | 4 - app/src/main/res/xml/updating_preferences.xml | 172 +++++++++-------- 26 files changed, 624 insertions(+), 342 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_code_off_24.xml create mode 100644 app/src/main/res/layout/changelog_item.xml diff --git a/app/build.gradle b/app/build.gradle index efd9650b..ecae5878 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ plugins { def properties = new Properties() def versionMajor = 1 -def versionMinor = 6 -def versionPatch = 9 +def versionMinor = 7 +def versionPatch = 0 def versionBuild = 0 // bump for dogfood builds, public betas, etc. def versionExt = "" diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index b79c82b6..06b927e1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -339,10 +339,6 @@ class MainActivity : BaseActivity() { window.statusBarColor = getColor(android.R.color.transparent) incognitoHeader.visibility = View.GONE } - //check logs option to show in tablet navbar - if (navigationView is NavigationView){ - (navigationView as NavigationView).menu.findItem(R.id.downloadLogListFragment).isVisible = preferences.getBoolean("log_downloads", false) - } } override fun onNewIntent(intent: Intent) { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/GithubRelease.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/GithubRelease.kt index 47e579b8..32f20714 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/GithubRelease.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/GithubRelease.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.database.models import com.google.gson.annotations.SerializedName +import java.util.Date data class GithubRelease( @SerializedName(value = "html_url") @@ -9,6 +10,8 @@ data class GithubRelease( var tag_name: String, @SerializedName(value = "body") var body: String, + @SerializedName(value = "published_at") + var published_at: Date, @SerializedName(value = "assets") var assets: List ) 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 362552ba..5b5e6af5 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 @@ -64,7 +64,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co val infoUtil = InfoUtil(context) val items : ArrayList = arrayListOf() do { - val tmp = infoUtil.getPlaylist(query, nextPageToken) + val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "") items.addAll(tmp.videos) val tmpToken = tmp.nextPageToken if (tmpToken.isEmpty()) break 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 a62933eb..a6a55891 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 @@ -354,25 +354,35 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } fun switchDownloadType(list: List, type: Type) : List{ - val updatedDownloadPath : String = when(type){ - Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!! - Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!! - Type.command -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!! - else -> "" - } list.forEach { val format = getFormat(it.allFormats, type) it.format = format - val currentDownloadPath = when(it.type){ - Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath()) - Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath()) - Type.command -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath()) - else -> "" - } - if (it.downloadPath == currentDownloadPath) it.downloadPath = updatedDownloadPath + var updatedDownloadPath: String = "" + var container: String = "" + + when(type){ + Type.audio -> { + updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!! + container = sharedPreferences.getString("audio_format", "")!! + } + Type.video -> { + updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!! + container = sharedPreferences.getString("audio_format", "")!! + } + Type.command -> { + updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!! + container = "" + } + else -> { + updatedDownloadPath = "" + } + } + + it.downloadPath = updatedDownloadPath it.type = type + it.container = container } return list } @@ -443,7 +453,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application requirements.add {it: Format -> audioFormatIDPreference.contains(it.format_id)} sharedPreferences.getString("audio_language", "")?.apply { - if (this.isNotEmpty()){ + if (this.isNotBlank()){ requirements.add { it: Format -> it.lang == this } } } 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 a8a8af50..56289ed2 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -275,10 +275,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene val items = mutableListOf() ids?.forEach { items.add(downloadViewModel.getItemByID(it)) + downloadViewModel.deleteDownload(it) + } + withContext(Dispatchers.Main){ + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf( + Pair("downloads", items) + )) } - findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf( - Pair("downloads", items) - )) } } @@ -449,96 +452,100 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene @SuppressLint("InflateParams") private fun updateSearchViewItems(searchQuery: Editable?, linkYouCopied: View?) = lifecycleScope.launch(Dispatchers.Main) { - searchSuggestionsLinearLayout!!.visibility = GONE - searchHistoryLinearLayout!!.visibility = GONE - searchSuggestionsLinearLayout!!.post { - searchSuggestionsLinearLayout!!.removeAllViews() - } - searchHistoryLinearLayout!!.post { - searchHistoryLinearLayout!!.removeAllViews() - } - - linkYouCopied!!.visibility = GONE - - if (searchView!!.editText.text.isEmpty()){ - searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) - }else{ - searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0) - } - val history = withContext(Dispatchers.IO){ - resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) } - } - val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){ - infoUtil!!.getSearchSuggestions(searchQuery.toString()) - }else{ - emptyList() - } - - history.forEach { s -> - val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null) - val textView = v.findViewById(R.id.suggestion_text) - textView.text = s - textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0) - Handler(Looper.getMainLooper()).post { - searchHistoryLinearLayout!!.addView( - v - ) + lifecycleScope.launch { + searchSuggestionsLinearLayout!!.visibility = GONE + searchHistoryLinearLayout!!.visibility = GONE + searchSuggestionsLinearLayout!!.post { + searchSuggestionsLinearLayout!!.removeAllViews() } - textView.setOnClickListener { - searchView!!.setText(s) - initSearch(searchView!!) + searchHistoryLinearLayout!!.post { + searchHistoryLinearLayout!!.removeAllViews() } - textView.setOnLongClickListener { - val deleteDialog = MaterialAlertDialogBuilder(requireContext()) - deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!") - deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } - deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> - searchHistoryLinearLayout!!.removeView(v) - resultViewModel.removeSearchQueryFromHistory(s) + + linkYouCopied!!.visibility = GONE + + if (searchView!!.editText.text.isEmpty()){ + searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) + }else{ + searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0) + } + val history = withContext(Dispatchers.IO){ + resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) } + } + val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){ + withContext(Dispatchers.IO){ + infoUtil!!.getSearchSuggestions(searchQuery.toString()) } - deleteDialog.show() - true + }else{ + emptyList() } - val mb = v.findViewById(R.id.set_search_query_button) - mb.setOnClickListener { - searchView!!.editText.setText(s) - searchView!!.editText.setSelection(searchView!!.editText.length()) - } - } - searchHistoryLinearLayout!!.isVisible = history.isNotEmpty() - if (linkYouCopied.findViewById(R.id.suggestion_text).text.isNotEmpty()){ - linkYouCopied.visibility = VISIBLE - } + history.forEach { s -> + val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null) + val textView = v.findViewById(R.id.suggestion_text) + textView.text = s + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0) + Handler(Looper.getMainLooper()).post { + searchHistoryLinearLayout!!.addView( + v + ) + } + textView.setOnClickListener { + searchView!!.setText(s) + initSearch(searchView!!) + } + textView.setOnLongClickListener { + val deleteDialog = MaterialAlertDialogBuilder(requireContext()) + deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!") + deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } + deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> + searchHistoryLinearLayout!!.removeView(v) + resultViewModel.removeSearchQueryFromHistory(s) + } + deleteDialog.show() + true + } - suggestions.forEach { s -> - val v = LayoutInflater.from(fragmentContext) - .inflate(R.layout.search_suggestion_item, null) - val textView = v.findViewById(R.id.suggestion_text) - textView.text = s - Handler(Looper.getMainLooper()).post { - searchSuggestionsLinearLayout!!.addView( - v - ) + val mb = v.findViewById(R.id.set_search_query_button) + mb.setOnClickListener { + searchView!!.editText.setText(s) + searchView!!.editText.setSelection(searchView!!.editText.length()) + } } - textView.setOnClickListener { - searchView!!.setText(s) - initSearch(searchView!!) + searchHistoryLinearLayout!!.isVisible = history.isNotEmpty() + if (linkYouCopied.findViewById(R.id.suggestion_text).text.isNotEmpty()){ + linkYouCopied.visibility = VISIBLE } - val mb = v.findViewById(R.id.set_search_query_button) - mb.setOnClickListener { - searchView!!.editText.setText(s) - searchView!!.editText.setSelection(searchView!!.editText.length()) - } - } - searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty() - if (Patterns.WEB_URL.matcher(searchView!!.editText.text).matches()){ - providersChipGroup?.visibility = GONE - chipGroupDivider?.visibility = GONE - }else{ - providersChipGroup?.visibility = VISIBLE - chipGroupDivider?.visibility = VISIBLE + suggestions.forEach { s -> + val v = LayoutInflater.from(fragmentContext) + .inflate(R.layout.search_suggestion_item, null) + val textView = v.findViewById(R.id.suggestion_text) + textView.text = s + Handler(Looper.getMainLooper()).post { + searchSuggestionsLinearLayout!!.addView( + v + ) + } + textView.setOnClickListener { + searchView!!.setText(s) + initSearch(searchView!!) + } + val mb = v.findViewById(R.id.set_search_query_button) + mb.setOnClickListener { + searchView!!.editText.setText(s) + searchView!!.editText.setSelection(searchView!!.editText.length()) + } + } + searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty() + + if (Patterns.WEB_URL.matcher(searchView!!.editText.text).matches()){ + providersChipGroup?.visibility = GONE + chipGroupDivider?.visibility = GONE + }else{ + providersChipGroup?.visibility = VISIBLE + chipGroupDivider?.visibility = VISIBLE + } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt index 06973fb4..69c2b654 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/HistoryAdapter.kt @@ -128,7 +128,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit } else if (item.type == DownloadViewModel.Type.video) { if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video) }else{ - btn.setImageResource(R.drawable.ic_terminal) + if (filePresent) btn.setImageResource(R.drawable.ic_terminal) else btn.setImageResource(R.drawable.baseline_code_off_24) } if (btn.hasOnClickListeners()) btn.setOnClickListener(null) btn.isClickable = filePresent 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 eb193f55..d719047f 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 @@ -253,9 +253,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure val ids = mutableListOf() runBlocking { items.map { it.status = DownloadRepository.Status.Saved.toString() } - items.forEach { - ids.add(downloadViewModel.repository.insert(it)) - } + ids.addAll(downloadViewModel.repository.insertAll(items)) } val id = System.currentTimeMillis().toInt() val workRequest = OneTimeWorkRequestBuilder() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt index 3f6792c7..4e7f77d4 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt @@ -16,6 +16,7 @@ import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.Format import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type +import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.UiUtil import com.facebook.shimmer.ShimmerFrameLayout @@ -147,19 +148,30 @@ class FormatSelectionBottomSheetDialog(private val items: List, p updateFormatsJob = launch(Dispatchers.IO) { //simple download if (items.size == 1) { - val res = infoUtil.getFormats(items.first()!!.url) - res.filter { it.format_note != "storyboard" } - chosenFormats = if (items.first()?.type == Type.audio) { - res.filter { it.format_note.contains("audio", ignoreCase = true) } - } else { - res - } - if (chosenFormats.isEmpty()) throw Exception() + kotlin.runCatching { + val res = infoUtil.getFormats(items.first()!!.url) + res.filter { it.format_note != "storyboard" } + chosenFormats = if (items.first()?.type == Type.audio) { + res.filter { it.format_note.contains("audio", ignoreCase = true) } + } else { + res + } + if (chosenFormats.isEmpty()) throw Exception() - formats = listOf(res) - withContext(Dispatchers.Main){ - listener.onFormatsUpdated(formats) + formats = listOf(res) + withContext(Dispatchers.Main){ + listener.onFormatsUpdated(formats) + } + }.onFailure { err -> + withContext(Dispatchers.Main){ + UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState( + false, + Pair(R.string.no_results, err.message.toString()), + mutableListOf(Pair(R.string.copy_log, ResultViewModel.ResultAction.COPY_LOG)) + ), closed = {}) + } } + //list format filtering }else{ var progress = "0/${items.size}" 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 8f991b46..9d846116 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 @@ -26,6 +26,8 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.appcompat.widget.SearchView +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.forEach @@ -410,7 +412,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{ val w = websites[i] if (w == "null" || w.isEmpty()) continue val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip - tmp.text = w + tmp.text = w.replaceFirstChar { if (it.isLowerCase()) it.titlecase(java.util.Locale.getDefault()) else it.toString() } tmp.id = i tmp.setOnClickListener { Log.e(TAG, tmp.isChecked.toString()) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt index f936f4ef..aa8cd069 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt @@ -1,19 +1,15 @@ package com.deniscerri.ytdlnis.ui.more.settings -import android.app.Activity import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri -import android.os.Build import android.os.Bundle import android.os.PowerManager import android.provider.Settings -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.core.app.ActivityCompat import androidx.core.os.LocaleListCompat import androidx.preference.ListPreference import androidx.preference.Preference @@ -84,6 +80,8 @@ class GeneralSettingsFragment : BaseSettingsFragment() { } ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) val intent = Intent(requireContext(), MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) this.startActivity(intent) requireActivity().finishAffinity() true @@ -93,6 +91,8 @@ class GeneralSettingsFragment : BaseSettingsFragment() { Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) val intent = Intent(requireContext(), MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) this.startActivity(intent) requireActivity().finishAffinity() true @@ -101,6 +101,8 @@ class GeneralSettingsFragment : BaseSettingsFragment() { Preference.OnPreferenceChangeListener { _: Preference?, _: Any -> ThemeUtil.updateTheme(requireActivity() as AppCompatActivity) val intent = Intent(requireContext(), MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) this.startActivity(intent) requireActivity().finishAffinity() 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 8a515daf..a72e38f8 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 @@ -9,6 +9,8 @@ import android.os.Bundle import android.os.Environment import android.util.LayoutDirection import android.util.Log +import android.view.View +import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.edit @@ -36,10 +38,12 @@ 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.UiUtil import com.deniscerri.ytdlnis.util.UpdateUtil import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.gson.Gson +import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonParser @@ -66,7 +70,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() { private lateinit var cookieViewModel: CookieViewModel private lateinit var commandTemplateViewModel: CommandTemplateViewModel - private var version: Preference? = null override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) @@ -149,21 +152,40 @@ class MainSettingsFragment : PreferenceFragmentCompat() { getString(R.string.ok) ) { _: DialogInterface?, _: Int -> lifecycleScope.launch(Dispatchers.IO) { + if (checkedItems.all { !it }){ + withContext(Dispatchers.Main){ + Snackbar.make(requireView(), R.string.select_backup_categories, Snackbar.LENGTH_SHORT).show() + } + return@launch + } val json = JsonObject() json.addProperty("app", "YTDLnis_backup") for (i in 0 until checkedItems.size) { if (checkedItems[i]) { - when(values[i]){ - "settings" -> json.add("settings", backupSettings(preferences)) - "downloads" -> json.add("downloads", backupHistory()) - "queued" -> json.add("queued", backupQueuedDownloads() ) - "cancelled" -> json.add("cancelled", backupCancelledDownloads() ) - "errored" -> json.add("errored", backupErroredDownloads() ) - "saved" -> json.add("saved", backupSavedDownloads() ) - "cookies" -> json.add("cookies", backupCookies() ) - "templates" -> json.add("templates", backupCommandTemplates() ) - "shortcuts" -> json.add("shortcuts", backupShortcuts() ) - "searchHistory" -> json.add("search_history", backupSearchHistory() ) + runCatching { + when(values[i]){ + "settings" -> json.add("settings", backupSettings(preferences)) + "downloads" -> json.add("downloads", backupHistory()) + "queued" -> json.add("queued", backupQueuedDownloads() ) + "cancelled" -> json.add("cancelled", backupCancelledDownloads() ) + "errored" -> json.add("errored", backupErroredDownloads() ) + "saved" -> json.add("saved", backupSavedDownloads() ) + "cookies" -> json.add("cookies", backupCookies() ) + "templates" -> json.add("templates", backupCommandTemplates() ) + "shortcuts" -> json.add("shortcuts", backupShortcuts() ) + "searchHistory" -> json.add("search_history", backupSearchHistory() ) + } + }.onFailure {err -> + withContext(Dispatchers.Main){ + val snack = Snackbar.make(requireView(), err.message ?: requireContext().getString(R.string.errored), Snackbar.LENGTH_LONG) + val snackbarView: View = snack.view + val snackTextView = snackbarView.findViewById(com.google.android.material.R.id.snackbar_text) as TextView + snackTextView.maxLines = 9999999 + snack.setAction(android.R.string.copy){ + UiUtil.copyToClipboard(err.message ?: requireContext().getString(R.string.errored), requireActivity()) + } + snack.show() + } } } } @@ -176,8 +198,12 @@ class MainSettingsFragment : PreferenceFragmentCompat() { Calendar.DAY_OF_MONTH)} [${currentTime.get(Calendar.MILLISECOND)}.json") saveFile.delete() saveFile.createNewFile() - saveFile.writeText(Gson().toJson(json)) - Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG).show() + saveFile.writeText(GsonBuilder().setPrettyPrinting().create().toJson(json)) + val s = Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG) + s.setAction(R.string.Open_File){ + UiUtil.openFileIntent(requireContext(), saveFile.absolutePath) + } + s.show() } } @@ -201,25 +227,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() { true } - version = findPreference("version") - val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir - - version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}]" - - version!!.onPreferenceClickListener = - Preference.OnPreferenceClickListener { - lifecycleScope.launch{ - withContext(Dispatchers.IO){ - updateUtil!!.updateApp{ msg -> - lifecycleScope.launch(Dispatchers.Main){ - Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show() - } - } - } - - } - true - } } private fun backupSettings(preferences: SharedPreferences) : JsonArray { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt index 49ac0849..81a17928 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/ProcessingSettingsFragment.kt @@ -3,22 +3,38 @@ package com.deniscerri.ytdlnis.ui.more.settings import android.os.Bundle import androidx.preference.EditTextPreference import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.util.UiUtil class ProcessingSettingsFragment : BaseSettingsFragment() { override val title: Int = R.string.processing override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.processing_preferences, rootKey) + val prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) + val editor = prefs.edit() val preferredFormatID : EditTextPreference? = findPreference("format_id") val preferredFormatIDAudio : EditTextPreference? = findPreference("format_id_audio") + val subtitleLanguages : Preference? = findPreference("subs_lang") preferredFormatID?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.video)}]" preferredFormatID?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]" preferredFormatIDAudio?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.audio)}]" preferredFormatIDAudio?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]" + + subtitleLanguages?.summary = prefs.getString("subs_lang", "en.*,.*-orig")!! + subtitleLanguages?.setOnPreferenceClickListener { + UiUtil.showSubtitleLanguagesDialog(requireActivity(), prefs.getString("subs_lang", "en.*,.*-orig")!!){ + editor.putString("subs_lang", it) + editor.apply() + subtitleLanguages.summary = it + } + true + } } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt index 311d0533..027e35b6 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/UpdateSettingsFragment.kt @@ -1,15 +1,20 @@ package com.deniscerri.ytdlnis.ui.more.settings import android.os.Bundle +import android.view.View +import android.widget.TextView import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceManager +import com.deniscerri.ytdlnis.BuildConfig import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UpdateUtil import com.google.android.material.snackbar.Snackbar import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class UpdateSettingsFragment : BaseSettingsFragment() { @@ -17,6 +22,8 @@ class UpdateSettingsFragment : BaseSettingsFragment() { private var updateYTDL: Preference? = null private var ytdlVersion: Preference? = null private var updateUtil: UpdateUtil? = null + private var version: Preference? = null + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -39,33 +46,44 @@ class UpdateSettingsFragment : BaseSettingsFragment() { Snackbar.make(requireView(), requireContext().getString(R.string.ytdl_updating_started), Snackbar.LENGTH_LONG).show() - when (updateUtil!!.updateYoutubeDL()) { - YoutubeDL.UpdateStatus.DONE -> { - Snackbar.make(requireView(), - requireContext().getString(R.string.ytld_update_success), - Snackbar.LENGTH_LONG).show() + runCatching { + when (updateUtil!!.updateYoutubeDL()) { + YoutubeDL.UpdateStatus.DONE -> { + Snackbar.make(requireView(), + requireContext().getString(R.string.ytld_update_success), + Snackbar.LENGTH_LONG).show() - YoutubeDL.getInstance().version(context)?.let { - editor.putString("ytdl-version", it) - editor.apply() - ytdlVersion!!.summary = it + YoutubeDL.getInstance().version(context)?.let { + editor.putString("ytdl-version", it) + editor.apply() + ytdlVersion!!.summary = it + } + } + YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(), + requireContext().getString(R.string.you_are_in_latest_version), + Snackbar.LENGTH_LONG).show() + else -> { + Snackbar.make(requireView(), + requireContext().getString(R.string.errored), + Snackbar.LENGTH_LONG).show() } } - YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(), - requireContext().getString(R.string.you_are_in_latest_version), - Snackbar.LENGTH_LONG).show() - else -> { - Snackbar.make(requireView(), - requireContext().getString(R.string.errored), - Snackbar.LENGTH_LONG).show() + }.onFailure { + val msg = it.message ?: requireContext().getString(R.string.errored) + val snackBar = Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG) + snackBar.setAction(android.R.string.copy){ + UiUtil.copyToClipboard(msg, requireActivity()) } + val snackbarView: View = snackBar.view + val snackTextView = snackbarView.findViewById(com.google.android.material.R.id.snackbar_text) as TextView + snackTextView.maxLines = 9999999 + snackBar.show() } } true } - val pipedInstance = findPreference("piped_instance") - pipedInstance?.setOnPreferenceClickListener { + findPreference("piped_instance")?.setOnPreferenceClickListener { UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){ editor.putString("piped_instance", it) editor.apply() @@ -73,6 +91,32 @@ class UpdateSettingsFragment : BaseSettingsFragment() { true } + findPreference("changelog")?.setOnPreferenceClickListener { + lifecycleScope.launch(Dispatchers.IO) { + updateUtil?.showChangeLog(requireActivity()) + } + true + } + + + version = findPreference("version") + val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir + version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}]" + version!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + lifecycleScope.launch{ + withContext(Dispatchers.IO){ + updateUtil!!.updateApp{ msg -> + lifecycleScope.launch(Dispatchers.Main){ + Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show() + } + } + } + + } + true + } + } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt index 192521b3..05545220 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt @@ -13,6 +13,7 @@ import android.util.DisplayMetrics import android.view.MotionEvent import android.view.View import android.widget.EditText +import android.widget.ScrollView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.neo.highlight.core.Highlight 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 335392f1..6800e47a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -100,7 +100,7 @@ class InfoUtil(private val context: Context) { } @Throws(JSONException::class) - fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple { + fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple { try{ val items = arrayListOf() // -------------- PIPED API FUNCTION ------------------- @@ -117,7 +117,7 @@ class InfoUtil(private val context: Context) { kotlin.runCatching { val obj = dataArray.getJSONObject(i) val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url")) - itm?.playlistTitle = res.getString("name") + itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName } itm?.playlistURL = "https://www.youtube.com/playlist?list=$id" itm?.playlistIndex = (i+1) items.add(itm) @@ -425,65 +425,61 @@ class InfoUtil(private val context: Context) { val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?") val m = p.matcher(url) val formatSource = sharedPreferences.getString("formats_source", "yt-dlp") - return try { - if(m.find() && formatSource == "piped"){ + if (m.find() && formatSource == "piped"){ + return try { val id = getIDFromYoutubeURL(url) val res = genericRequest("$pipedURL/streams/$id") if (res.length() == 0) getFromYTDL(url)[0]!! val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id") item!!.formats - }else{ - getFormatsFromYTDL(url) + }catch(e: Exception) { + if (e is CancellationException) throw e + return getFormatsFromYTDL(url) } - }catch (e: Exception){ - if (e is CancellationException) throw e - getFormatsFromYTDL(url) + }else{ + return getFormatsFromYTDL(url) } } private fun getFormatsFromYTDL(url: String) : List { - 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") + 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) - } - - val useHeader = sharedPreferences.getBoolean("use_header", false) - val header = sharedPreferences.getString("useragent_header", "") - if (useHeader && !header.isNullOrBlank()){ - request.addOption("--add-header","User-Agent:${header}") - } + if (sharedPreferences.getBoolean("use_cookies", false)){ + FileUtil.getCookieFile(context){ + request.addOption("--cookies", it) } - val proxy = sharedPreferences.getString("proxy", "") - if (proxy!!.isNotBlank()) { - request.addOption("--proxy", proxy) + 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 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){ @@ -548,8 +544,9 @@ class InfoUtil(private val context: Context) { urls.forEach { val id = getIDFromYoutubeURL(it) val res = genericRequest("$pipedURL/streams/$id") - val vid = createVideoFromPipedJSON(res, it) - progress(vid!!.formats) + createVideoFromPipedJSON(res, it)?.apply { + progress(this.formats) + } } } @@ -629,6 +626,9 @@ class InfoUtil(private val context: Context) { author = if (jsonObject.has("channel")) jsonObject.getString("channel") else "" if (author.isEmpty() || author == "null"){ author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else "" + if (author.isEmpty() || author == "null"){ + author = if (jsonObject.has("uploader_id")) jsonObject.getString("uploader_id") else "" + } } } var duration = "" @@ -791,6 +791,9 @@ class InfoUtil(private val context: Context) { author = if (jsonObject.has("channel")) jsonObject.getString("channel") else "" if (author.isEmpty() || author == "null"){ author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else "" + if (author.isEmpty() || author == "null"){ + author = if (jsonObject.has("uploader_id")) jsonObject.getString("uploader_id") else "" + } } } @@ -837,7 +840,6 @@ class InfoUtil(private val context: Context) { fun getSearchSuggestions(query: String): ArrayList { val url = "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&client=firefox&q=$query" - // invidousURL + "search/suggestions?q=" + query val res = genericArrayRequest(url) Log.e("aa", res.toString()) if (res.length() == 0) return ArrayList() @@ -1086,8 +1088,10 @@ class InfoUtil(private val context: Context) { if(downloadItem.title.isNotBlank()){ request.addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title)) } + + request.addOption("--parse-metadata", "%(channel,uploader,creator,artist|null)s:%(uploader)s") + request.addOption("--parse-metadata", "%(uploader)s:%(album_artist)s") if (downloadItem.author.isNotBlank()){ - request.addOption("--parse-metadata", "%(artist,uploader,creator,channel|null)s:%(uploader)s") request.addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author)) request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$","")) } @@ -1104,9 +1108,13 @@ class InfoUtil(private val context: Context) { request.addOption("--force-keyframes-at-cuts") } } - filenameTemplate += " %(section_title|)s " + if (filenameTemplate.isBlank()){ + filenameTemplate = "%(section_title& {}|)s %(title)s" + }else{ + filenameTemplate += " %(section_title& {}|)s " + } if (downloadItem.downloadSections.split(";").size > 1){ - filenameTemplate += "%(autonumber)s" + filenameTemplate = "%(autonumber)d. %(section_start& {}|)s $filenameTemplate" } request.addOption("--output-na-placeholder", " ") } @@ -1145,7 +1153,7 @@ class InfoUtil(private val context: Context) { val preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!! val aCodecPrefIndex = context.getStringArray(R.array.audio_codec_values).indexOf(preferredAudioCodec) - val aCodecPref = context.getStringArray(R.array.audio_codec_values_ytdlp)[aCodecPrefIndex] + val aCodecPref = runCatching { context.getStringArray(R.array.audio_codec_values_ytdlp)[aCodecPrefIndex] }.getOrElse { "" } when(type){ DownloadViewModel.Type.audio -> { @@ -1162,14 +1170,14 @@ class InfoUtil(private val context: Context) { val ext = downloadItem.container if (audioQualityId.isNotBlank()) { if (audioQualityId.contains("-") && !downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){ - audioQualityId = "ba[format_id~='^(${audioQualityId.split("-")[0]})'][language=${downloadItem.format.lang}]/ba/b" + audioQualityId = "ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b" } request.addOption("-f", audioQualityId) }else{ //enters here if generic or quick downloaded with ba format val preferredLanguage = sharedPreferences.getString("audio_language","")!! if (preferredLanguage.isNotBlank()){ - request.addOption("-f", "ba[language=$preferredLanguage]/ba/b") + request.addOption("-f", "ba[language^=$preferredLanguage]/ba/b") } } request.addOption("-x") @@ -1272,15 +1280,17 @@ class InfoUtil(private val context: Context) { val defaultFormats = context.resources.getStringArray(R.array.video_formats_values) val usingGenericFormat = defaultFormats.contains(videoF) || downloadItem.allFormats.isEmpty() || downloadItem.allFormats == getGenericVideoFormats(context.resources) if (!usingGenericFormat){ - if (audioF.isNotBlank()) audioF = "+$audioF" - f.append("$videoF$audioF/") - if (preferredAudioCodec.isNotBlank()){ - f.append("$videoF+$aCodecPref/") - } - f.append("$videoF/bv/b") + // with audio removed + if (audioF.isBlank()){ + f.append("$videoF/bv/b") + }else{ + //with audio + audioF = "+$audioF" + f.append("$videoF$audioF/$videoF+ba/$videoF/b") - if (audioF.count("+") > 1){ - request.addOption("--audio-multistreams") + if (audioF.count("+") > 1){ + request.addOption("--audio-multistreams") + } } }else{ if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best") { @@ -1306,8 +1316,7 @@ class InfoUtil(private val context: Context) { .filter { it.isNotBlank() } .ifEmpty { val list = mutableListOf() - if (preferredAudioCodec.isNotBlank()) list.add("ba[acodec~='^($preferredAudioCodec)']") - if (preferredAudioLanguage.isNotEmpty()) list.add("ba[language=$preferredAudioLanguage]") + if (preferredAudioLanguage.isNotEmpty()) list.add("ba[language^=$preferredAudioLanguage]") list.add(audioF) list }.apply { @@ -1333,7 +1342,7 @@ class InfoUtil(private val context: Context) { if (!downloadItem.videoPreferences.removeAudio){ //build format with audio with preferred language if (preferredAudioLanguage.isNotBlank()){ - val al = "$v+ba[language=$preferredAudioLanguage]/" + val al = "$v+ba[language^=$preferredAudioLanguage]/" if (!f.contains(al)) f.append(al) } //build format with best audio @@ -1348,12 +1357,14 @@ class InfoUtil(private val context: Context) { f.append("b") } - val formatSorting = StringBuilder("+hasaud") - if (vCodecPref.isNotBlank()) formatSorting.append(",vcodec:$vCodecPref") - if (aCodecPref.isNotBlank()) formatSorting.append(",acodec:$aCodecPref") - if (cont.isNotBlank()) formatSorting.append(",ext:$cont") - - request.addOption("-S", formatSorting.toString()) + StringBuilder().apply { + if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref") + if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref") + if (cont.isNotBlank()) append(",ext:$cont") + if (this.isNotBlank()){ + request.addOption("-S", "+hasaud$this") + } + } } 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 3f25a470..87a477bf 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt @@ -896,7 +896,7 @@ object UiUtil { ) } - private fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){ + fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){ val builder = MaterialAlertDialogBuilder(context) builder.setTitle(context.getString(R.string.subtitle_languages)) val view = context.layoutInflater.inflate(R.layout.subtitle_dialog, null) @@ -966,7 +966,7 @@ object UiUtil { } - private fun copyToClipboard(text: String, activity: Activity){ + fun copyToClipboard(text: String, activity: Activity){ val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText(text, text) clipboard.setPrimaryClip(clip) @@ -1454,6 +1454,21 @@ object UiUtil { errDialog.show() } + fun showErrorDialog(context: Context, it: String){ + val title = context.getString(R.string.errored) + + val errDialog = MaterialAlertDialogBuilder(context) + .setTitle(title) + .setMessage(it) + .setPositiveButton(android.R.string.copy) { d:DialogInterface?, _:Int -> + val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + clipboard.setText(it) + d?.dismiss() + } + + errDialog.show() + } + @SuppressLint("SetTextI18n") fun handleDownloadsResponse(context: Activity, lifecycleScope: CoroutineScope, supportFragmentManager: FragmentManager, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){ val downloadAnywayAction = it.actions?.first { it.second == DownloadViewModel.DownloadsAction.DOWNLOAD_ANYWAY} @@ -1732,7 +1747,7 @@ object UiUtil { CoroutineScope(Dispatchers.IO).launch { val chipGroup = view.findViewById(R.id.filename_suggested_chipgroup) val chips = mutableListOf() - val instances = InfoUtil(context).getPipedInstances() + val instances = InfoUtil(context).getPipedInstances().ifEmpty { return@launch } instances.forEach { s -> val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip tmp.text = s 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 6fa3ddd1..65a0aee8 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UpdateUtil.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.util import android.annotation.SuppressLint +import android.app.Activity import android.app.DownloadManager import android.content.BroadcastReceiver import android.content.Context @@ -13,8 +14,11 @@ import android.os.Bundle import android.os.Environment import android.os.Handler import android.os.Looper +import android.text.format.DateFormat import android.text.method.LinkMovementMethod import android.util.Log +import android.widget.LinearLayout +import android.widget.ScrollView import android.widget.TextView import android.widget.Toast import androidx.core.content.ContextCompat.startActivity @@ -22,6 +26,10 @@ import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.BuildConfig import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.GithubRelease +import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll +import com.google.android.material.card.MaterialCardView +import com.google.android.material.chip.Chip +import com.google.android.material.chip.ChipGroup import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -36,6 +44,8 @@ import java.io.File import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL +import java.text.SimpleDateFormat +import java.util.Locale class UpdateUtil(var context: Context) { @@ -152,6 +162,75 @@ class UpdateUtil(var context: Context) { } } + fun showChangeLog(activity: Activity){ + runCatching { + val scrollView = ScrollView(activity) + + val linearLayout = LinearLayout(activity) + linearLayout.orientation = LinearLayout.VERTICAL + + val layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT + ) + + layoutParams.setMargins(20, 20, 20, 0) + scrollView.layoutParams = layoutParams + scrollView.addView(linearLayout) + + getGithubReleases().forEach { + (activity.layoutInflater.inflate(R.layout.changelog_item, null) as MaterialCardView).apply { + this.layoutParams = layoutParams + findViewById(R.id.version).text = it.tag_name + findViewById(R.id.date).text = SimpleDateFormat( + DateFormat.getBestDateTimePattern( + Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time) + + val mdText = findViewById(R.id.content) + val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() { + override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { + builder.linkResolver { view, link -> + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link)) + startActivity(context, browserIntent, Bundle()) + } + } + }).build() + mw.setMarkdown(mdText, it.body) + + + val assetGroup = findViewById(R.id.assets) + it.assets.forEachIndexed { idx, c -> + val tmp = activity.layoutInflater.inflate(R.layout.suggestion_chip, assetGroup, false) as Chip + tmp.text = c.name + tmp.id = idx + tmp.setOnClickListener { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url)) + startActivity(context, browserIntent, Bundle()) + } + assetGroup!!.addView(tmp) + } + + linearLayout.addView(this) + } + } + + val changeLogDialog = MaterialAlertDialogBuilder(context) + .setTitle(activity.getString(R.string.changelog)) + .setView(scrollView) + .setIcon(R.drawable.ic_chapters) + .setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> } + Handler(Looper.getMainLooper()).post { + changeLogDialog.show() + } + + }.onFailure { + if (it.message != null){ + Handler(Looper.getMainLooper()).post { + UiUtil.showErrorDialog(context, it.message!!) + } + } + } + } + private fun getGithubReleases(): List { val url = "https://api.github.com/repos/deniscerri/ytdlnis/releases" val conn: HttpURLConnection @@ -188,15 +267,8 @@ class UpdateUtil(var context: Context) { "master" to YoutubeDL.UpdateChannel._MASTER, ) val channel = sharedPreferences.getString("ytdlp_source", "nightly") - - try { - YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply { - updatingYTDL = false - } - }catch (e: Exception){ - e.printStackTrace() + YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply { updatingYTDL = false - null } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/UpdatePlaylistFormatsWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/UpdatePlaylistFormatsWorker.kt index ebcab815..5e577e17 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/UpdatePlaylistFormatsWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/UpdatePlaylistFormatsWorker.kt @@ -55,7 +55,7 @@ class UpdatePlaylistFormatsWorker( runBlocking { resDao.update(r) - dao.delete(d.id) + dao.update(d) } } diff --git a/app/src/main/res/drawable/baseline_code_off_24.xml b/app/src/main/res/drawable/baseline_code_off_24.xml new file mode 100644 index 00000000..c258bb95 --- /dev/null +++ b/app/src/main/res/drawable/baseline_code_off_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/changelog_item.xml b/app/src/main/res/layout/changelog_item.xml new file mode 100644 index 00000000..79a56110 --- /dev/null +++ b/app/src/main/res/layout/changelog_item.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 76d84aa7..cd3a053c 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1112,6 +1112,7 @@ + af sq am @@ -1222,6 +1223,7 @@ + @string/defaultValue Afrikaans Albanian Amharic diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ba30e94..422c23bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -327,4 +327,7 @@ Buffer Size Size of download buffer, e.g. 1024 or 16K (default is 1024) Preferred Audio Language + Changelog + Source + App \ No newline at end of file diff --git a/app/src/main/res/xml/processing_preferences.xml b/app/src/main/res/xml/processing_preferences.xml index c33738c2..2192608f 100644 --- a/app/src/main/res/xml/processing_preferences.xml +++ b/app/src/main/res/xml/processing_preferences.xml @@ -61,7 +61,7 @@ app:summary="@string/save_subs_desc" app:title="@string/save_subs" /> - - diff --git a/app/src/main/res/xml/updating_preferences.xml b/app/src/main/res/xml/updating_preferences.xml index 458dd210..0ef7a18e 100644 --- a/app/src/main/res/xml/updating_preferences.xml +++ b/app/src/main/res/xml/updating_preferences.xml @@ -2,86 +2,104 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + - - - - - - - - - - - - - + \ No newline at end of file