diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt index dd6d90c1..a0d445ce 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/DownloadItem.kt @@ -23,7 +23,7 @@ data class DownloadItem( var container: String, @ColumnInfo(defaultValue = "") var downloadSections: String, - val allFormats: MutableList, + var allFormats: MutableList, var downloadPath: String, var website: String, val downloadSize: String, diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt index 5620f738..a924eaee 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt @@ -25,5 +25,7 @@ data class Format( @SerializedName(value = "asr", alternate = ["audioSampleRate"]) var asr: String? = "", @SerializedName(value = "url") - var url: String? = "" + var url: String? = "", + @SerializedName(value = "language", alternate = ["audioTrackLocale"]) + val lang: String? = "" ) : Parcelable \ No newline at end of file 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 76a45df6..90366ebe 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 @@ -45,13 +45,15 @@ class LogRepository(private val logDao: LogDao) { 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() - } + var newLine = line + if (newLine.contains("[download")){ + newLine = "[download]" + line.split("[download]").last() + } - val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" - item.content = l + val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" + item.content = l + + //item.content += "\n$line" logDao.update(item) } } 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 eaad76d0..362552ba 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 @@ -33,7 +33,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co if (resetResults) deleteAll() val res = infoUtil.search(inputQuery) itemCount.value = res.size - resultDao.insertMultiple(res) + val ids = resultDao.insertMultiple(res) + ids.forEachIndexed { index, id -> + res[index]?.id = id + } return res } @@ -46,7 +49,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co }else{ v.filter { it?.playlistTitle.isNullOrBlank() }.forEach { it?.playlistTitle = "ytdlnis-Search" } } - resultDao.insertMultiple(v) + val ids = resultDao.insertMultiple(v) + ids.forEachIndexed { index, id -> + v[index]?.id = id + } return ArrayList(v) } @@ -66,7 +72,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co nextPageToken = tmpToken } while (true) itemCount.value = items.size - resultDao.insertMultiple(items.toList()) + val ids = resultDao.insertMultiple(items.toList()) + ids.forEachIndexed { index, id -> + items[index]?.id = id + } return items } @@ -79,7 +88,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co }else{ items.filter { it?.playlistTitle.isNullOrBlank() }.forEach { it!!.playlistTitle = "ytdlnis-Search" } } - resultDao.insertMultiple(items.toList()) + val ids = resultDao.insertMultiple(items.toList()) + ids.forEachIndexed { index, id -> + items[index]?.id = id + } return items } 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 9cf38281..a62933eb 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 @@ -441,6 +441,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val theFormats = formats.filter { it.format_note.contains("audio", ignoreCase = true) } val requirements: MutableList<(Format) -> Boolean> = mutableListOf() requirements.add {it: Format -> audioFormatIDPreference.contains(it.format_id)} + + sharedPreferences.getString("audio_language", "")?.apply { + if (this.isNotEmpty()){ + requirements.add { it: Format -> it.lang == this } + } + } + requirements.add {it: Format -> it.container == audioContainer } requirements.add {it: Format -> "^(${audioCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.acodec)} theFormats.maxByOrNull { f -> requirements.count{req -> req(f)} } ?: throw Exception() 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 c6a69ae3..a8a8af50 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -23,6 +23,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.os.bundleOf import androidx.core.view.children import androidx.core.view.forEach +import androidx.core.view.isVisible import androidx.core.widget.doAfterTextChanged import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider @@ -89,6 +90,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene private var shimmerCards: ShimmerFrameLayout? = null private var searchBar: SearchBar? = null private var searchView: SearchView? = null + private var providersChipGroup: ChipGroup? = null + private var chipGroupDivider: View? = null private var linkYouCopied: ConstraintLayout? = null private var queriesChipGroup: ChipGroup? = null private var recyclerView: RecyclerView? = null @@ -280,9 +283,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene } if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){ - lifecycleScope.launch { - updateSearchViewItems(searchView?.editText?.text, linkYouCopied) - } + updateSearchViewItems(searchView?.editText?.text, linkYouCopied) } } @@ -292,7 +293,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene val queriesInitStartBtn = queriesConstraint.findViewById(R.id.init_search_query) val isRightToLeft = resources.getBoolean(R.bool.is_right_to_left) - val providersChipGroup = searchView!!.findViewById(R.id.providers) + providersChipGroup = searchView!!.findViewById(R.id.providers) val providers = resources.getStringArray(R.array.search_engines) val providersValues = resources.getStringArray(R.array.search_engines_values).toMutableList() @@ -311,27 +312,27 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene } - providersChipGroup!!.addView(tmp) + providersChipGroup?.addView(tmp) } - val chipGroupDivider : View? = requireView().findViewById(R.id.chipGroupDivider) + chipGroupDivider = requireView().findViewById(R.id.chipGroupDivider) searchView!!.addTransitionListener { _, _, newState -> if (newState == SearchView.TransitionState.SHOWN) { val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch") - providersChipGroup.children.forEach { - val tmp = providersChipGroup.findViewById(it.id) - if (tmp.tag == currentProvider) { + providersChipGroup?.children?.forEach { + val tmp = providersChipGroup?.findViewById(it.id) + if (tmp?.tag?.equals(currentProvider) == true) { tmp.isChecked = true return@forEach } } if (Patterns.WEB_URL.matcher(searchBar!!.text.toString()).matches() && searchBar!!.text.isNotBlank()){ - providersChipGroup.visibility = GONE + providersChipGroup?.visibility = GONE chipGroupDivider?.visibility = GONE }else{ - providersChipGroup.visibility = VISIBLE + providersChipGroup?.visibility = VISIBLE chipGroupDivider?.visibility = VISIBLE } @@ -371,9 +372,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene }else{ linkYouCopied!!.visibility = GONE } - lifecycleScope.launch { - updateSearchViewItems(searchView!!.editText.text, linkYouCopied) - } + updateSearchViewItems(searchView!!.editText.text, linkYouCopied) }catch (e: Exception){ e.printStackTrace() linkYouCopied!!.visibility = GONE @@ -383,16 +382,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene searchView!!.editText.doAfterTextChanged { if (searchView!!.currentTransitionState != SearchView.TransitionState.SHOWN) return@doAfterTextChanged - lifecycleScope.launch { - updateSearchViewItems(it, linkYouCopied) - } - if (Patterns.WEB_URL.matcher(it.toString()).matches()){ - providersChipGroup.visibility = GONE - chipGroupDivider?.visibility = GONE - }else{ - providersChipGroup.visibility = VISIBLE - chipGroupDivider?.visibility = VISIBLE - } + updateSearchViewItems(it, linkYouCopied) } searchView!!.editText.setOnTouchListener(OnTouchListener { _, event -> @@ -450,7 +440,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene queriesChipGroup!!.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> if (queriesChipGroup!!.childCount == 0) queriesConstraint.visibility = GONE else queriesConstraint.visibility = VISIBLE - searchView!!.editText.setText("") } queriesInitStartBtn.setOnClickListener { @@ -459,11 +448,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene } @SuppressLint("InflateParams") - private suspend fun updateSearchViewItems(searchQuery: Editable?, linkYouCopied: View?){ + private fun updateSearchViewItems(searchQuery: Editable?, linkYouCopied: View?) = lifecycleScope.launch(Dispatchers.Main) { searchSuggestionsLinearLayout!!.visibility = GONE searchHistoryLinearLayout!!.visibility = GONE - searchSuggestionsLinearLayout!!.removeAllViews() - searchHistoryLinearLayout!!.removeAllViews() + searchSuggestionsLinearLayout!!.post { + searchSuggestionsLinearLayout!!.removeAllViews() + } + searchHistoryLinearLayout!!.post { + searchHistoryLinearLayout!!.removeAllViews() + } linkYouCopied!!.visibility = GONE @@ -472,76 +465,82 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene }else{ searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0) } - val suggestions = withContext(Dispatchers.IO){ - resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) } + - if (sharedPreferences!!.getBoolean("search_suggestions", false)){ - infoUtil!!.getSearchSuggestions(searchQuery.toString()) - }else{ - emptyList() - } + val history = withContext(Dispatchers.IO){ + resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) } } - - if (searchQuery!!.isEmpty()){ - for (i in suggestions.indices) { - val v = LayoutInflater.from(fragmentContext) - .inflate(R.layout.search_suggestion_item, null) - val textView = v.findViewById(R.id.suggestion_text) - textView.text = suggestions[i] - textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0) - Handler(Looper.getMainLooper()).post { - searchHistoryLinearLayout!!.addView( - v - ) - } - textView.setOnClickListener { - searchView!!.setText(textView.text) - initSearch(searchView!!) - } - textView.setOnLongClickListener { - val deleteDialog = MaterialAlertDialogBuilder(requireContext()) - deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + textView.text + "\"!") - deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } - deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> - searchHistoryLinearLayout!!.removeView(v) - resultViewModel.removeSearchQueryFromHistory(textView.text.toString()) - } - deleteDialog.show() - true - } - - val mb = v.findViewById(R.id.set_search_query_button) - mb.setOnClickListener { - searchView!!.setText(textView.text) - searchView!!.editText.setSelection(searchView!!.editText.length()) - } - } - searchHistoryLinearLayout!!.visibility = VISIBLE - if (linkYouCopied.findViewById(R.id.suggestion_text).text.isNotEmpty()){ - linkYouCopied.visibility = VISIBLE - } + val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){ + infoUtil!!.getSearchSuggestions(searchQuery.toString()) }else{ - for (i in suggestions.indices) { - val v = LayoutInflater.from(fragmentContext) - .inflate(R.layout.search_suggestion_item, null) - val textView = v.findViewById(R.id.suggestion_text) - textView.text = suggestions[i] - Handler(Looper.getMainLooper()).post { - searchSuggestionsLinearLayout!!.addView( - v - ) - } - textView.setOnClickListener { - searchView!!.setText(textView.text) - initSearch(searchView!!) - } - val mb = v.findViewById(R.id.set_search_query_button) - mb.setOnClickListener { - searchView!!.setText(textView.text) - searchView!!.editText.setSelection(searchView!!.editText.length()) - } - } - searchSuggestionsLinearLayout!!.visibility = VISIBLE + 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 + ) + } + 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 + } + + 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 + } + + 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 + } + } private fun initSearch(searchView: SearchView){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt index b9cefd30..f3260fd3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt @@ -112,7 +112,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val formatDetailsChip = card.findViewById(R.id.format_note) val sideDetails = mutableListOf() - sideDetails.add(item.format.format_note.uppercase()) + sideDetails.add(item.format.format_note.uppercase().replace("\n", " ")) sideDetails.add(item.container.uppercase().ifEmpty { item.format.container.uppercase() }) val fileSize = FileUtil.convertFileSize(item.format.filesize) 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 2b79af61..b6ab961d 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 @@ -14,12 +14,14 @@ import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import android.widget.LinearLayout import android.widget.TextView +import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.media3.common.util.Log import androidx.preference.PreferenceManager import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdlnis.R @@ -205,19 +207,21 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private override fun onFormatsUpdated(allFormats: List>) { lifecycleScope.launch(Dispatchers.IO) { - resultItem?.formats?.removeAll(formats.toSet()) - resultItem?.formats?.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) }) - if (resultItem != null){ - resultViewModel.update(resultItem!!) + resultItem?.apply { + this.formats.removeAll(formats.toSet()) + this.formats.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) }) + resultViewModel.update(this) kotlin.runCatching { val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment - f1.updateUI(resultItem) + f1.updateUI(this) } } } formats = allFormats.first().filter { !genericAudioFormats.contains(it) }.toMutableList() formats.removeAll(genericAudioFormats) val preferredFormat = downloadViewModel.getFormat(formats, Type.audio) + downloadItem.format = preferredFormat + downloadItem.allFormats = formats UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, null) } } 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 873fd930..1c3d3b25 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 @@ -20,6 +20,7 @@ import android.widget.Toast import androidx.core.content.edit import androidx.core.os.bundleOf import androidx.core.view.isVisible +import androidx.fragment.app.commit import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController @@ -46,7 +47,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.button.MaterialButton import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.progressindicator.LinearProgressIndicator -import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -417,65 +417,69 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { lifecycleScope.launch { resultViewModel.updatingData.collectLatest { - if (it){ - title.visibility = View.GONE - subtitle.visibility = View.GONE - shimmerLoading.visibility = View.VISIBLE - shimmerLoadingSubtitle.visibility = View.VISIBLE - shimmerLoading.startShimmer() - shimmerLoadingSubtitle.startShimmer() - }else{ - title.visibility = View.VISIBLE - subtitle.visibility = View.VISIBLE - shimmerLoading.visibility = View.GONE - shimmerLoadingSubtitle.visibility = View.GONE - shimmerLoading.stopShimmer() - shimmerLoadingSubtitle.stopShimmer() + kotlin.runCatching { + if (it){ + title.visibility = View.GONE + subtitle.visibility = View.GONE + shimmerLoading.visibility = View.VISIBLE + shimmerLoadingSubtitle.visibility = View.VISIBLE + shimmerLoading.startShimmer() + shimmerLoadingSubtitle.startShimmer() + }else{ + title.visibility = View.VISIBLE + subtitle.visibility = View.VISIBLE + shimmerLoading.visibility = View.GONE + shimmerLoadingSubtitle.visibility = View.GONE + shimmerLoading.stopShimmer() + shimmerLoadingSubtitle.stopShimmer() + } } } } 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)?.apply { - isVisible = true - isClickable = true - setOnClickListener { - lifecycleScope.launch(Dispatchers.IO) { - resultViewModel.cancelUpdateFormatsItemData() + kotlin.runCatching { + if (it){ + delay(500) + runCatching { + val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment + f1.view?.findViewById(R.id.format_loading_progress)?.apply { + isVisible = true + isClickable = true + setOnClickListener { + lifecycleScope.launch(Dispatchers.IO) { + resultViewModel.cancelUpdateFormatsItemData() + } } } } - } - runCatching { - val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment - f1.view?.findViewById(R.id.format_loading_progress)?.apply { - isVisible = true - isClickable = true - setOnClickListener { - lifecycleScope.launch(Dispatchers.IO) { - resultViewModel.cancelUpdateFormatsItemData() + runCatching { + val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment + f1.view?.findViewById(R.id.format_loading_progress)?.apply { + isVisible = true + isClickable = true + setOnClickListener { + lifecycleScope.launch(Dispatchers.IO) { + resultViewModel.cancelUpdateFormatsItemData() + } } } } - } - }else{ - runCatching { - val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment - f1.view?.findViewById(R.id.format_loading_progress)?.apply { - isVisible = false - isClickable = false + }else{ + runCatching { + val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment + f1.view?.findViewById(R.id.format_loading_progress)?.apply { + isVisible = false + isClickable = false + } } - } - runCatching { - val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment - f1.view?.findViewById(R.id.format_loading_progress)?.apply { - isVisible = false - isClickable = false + runCatching { + val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment + f1.view?.findViewById(R.id.format_loading_progress)?.apply { + isVisible = false + isClickable = false + } } } } @@ -485,47 +489,50 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { lifecycleScope.launch { resultViewModel.updateResultData.collectLatest { result -> if (result == null) return@collectLatest - lifecycleScope.launch(Dispatchers.Main) { - if (result.isNotEmpty()){ - if (result.size == 1 && result[0] != null){ - fragmentAdapter.setResultItem(result[0]!!) - runCatching { - val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment - f.updateUI(result[0]) - } + kotlin.runCatching { + lifecycleScope.launch(Dispatchers.Main) { + if (result.isNotEmpty()){ + if (result.size == 1 && result[0] != null){ + fragmentAdapter.setResultItem(result[0]!!) + runCatching { + val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment + f.updateUI(result[0]) + } - runCatching { - val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment - f1.updateUI(result[0]) - } + runCatching { + val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment + f1.updateUI(result[0]) + } - title.visibility = View.VISIBLE - subtitle.visibility = View.VISIBLE - shimmerLoading.visibility = View.GONE - shimmerLoadingSubtitle.visibility = View.GONE - shimmerLoading.stopShimmer() - shimmerLoadingSubtitle.stopShimmer() + title.visibility = View.VISIBLE + subtitle.visibility = View.VISIBLE + shimmerLoading.visibility = View.GONE + shimmerLoadingSubtitle.visibility = View.GONE + shimmerLoading.stopShimmer() + shimmerLoadingSubtitle.stopShimmer() - val usingGenericFormatsOrEmpty = result[0]!!.formats.isEmpty() || result[0]!!.formats.any { it.format_note.contains("ytdlnisgeneric") } - fragmentAdapter.setResultItem(result[0]!!) - arguments?.putParcelable("result", result[0]!!) - if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){ - initUpdateFormats(result[0]!!) - } - }else{ - //open multi download card instead - if (activity is ShareActivity){ - val preferredType = DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!) - findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_selectPlaylistItemsDialog, bundleOf( - Pair("results", result), - Pair("type", preferredType), - )) + val usingGenericFormatsOrEmpty = result[0]!!.formats.isEmpty() || result[0]!!.formats.any { it.format_note.contains("ytdlnisgeneric") } + fragmentAdapter.setResultItem(result[0]!!) + arguments?.putParcelable("result", result[0]!!) + if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){ + initUpdateFormats(result[0]!!) + } }else{ - dismiss() + //open multi download card instead + if (activity is ShareActivity){ + val preferredType = DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!) + findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_selectPlaylistItemsDialog, bundleOf( + Pair("results", result), + Pair("type", preferredType), + )) + }else{ + dismiss() + } } } + resultViewModel.updateResultData.emit(null) } - resultViewModel.updateResultData.emit(null) + } } } @@ -533,40 +540,37 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { lifecycleScope.launch { resultViewModel.updateFormatsResultData.collectLatest { formats -> if (formats == null) return@collectLatest - lifecycleScope.launch { - withContext(Dispatchers.Main){ - runCatching { - val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment - val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem) - resultItem.formats = formats - fragmentAdapter.setResultItem(resultItem) - f1.updateUI(resultItem) - f1.view?.findViewById(R.id.format_loading_progress)?.visibility = View.GONE + kotlin.runCatching { + lifecycleScope.launch { + withContext(Dispatchers.Main){ + runCatching { + val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment + val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem) + resultItem.formats = formats + fragmentAdapter.setResultItem(resultItem) + f1.updateUI(resultItem) + f1.view?.findViewById(R.id.format_loading_progress)?.visibility = View.GONE + } + runCatching { + val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment + val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem) + resultItem.formats = formats + fragmentAdapter.setResultItem(resultItem) + f1.updateUI(resultItem) + f1.view?.findViewById(R.id.format_loading_progress)?.visibility = View.GONE + } } - runCatching { - val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment - val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem) - resultItem.formats = formats - fragmentAdapter.setResultItem(resultItem) - f1.updateUI(resultItem) - f1.view?.findViewById(R.id.format_loading_progress)?.visibility = View.GONE - } - } - if (formats.isNotEmpty()){ - result.formats = formats + if (formats.isNotEmpty()){ + result.formats = formats + } + resultViewModel.updateFormatsResultData.emit(null) } - resultViewModel.updateFormatsResultData.emit(null) } } } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - } - private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{ return when(selectedTabPosition){ 0 -> { @@ -614,21 +618,25 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } private fun initUpdateData() { - if (result.url.isBlank()) { - dismiss() - return - } - if (resultViewModel.updatingData.value) return + kotlin.runCatching { + if (result.url.isBlank()) { + dismiss() + return + } + if (resultViewModel.updatingData.value) return - CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { - resultViewModel.updateItemData(result) + CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { + resultViewModel.updateItemData(result) + } } } private fun initUpdateFormats(res: ResultItem){ - if (resultViewModel.updatingFormats.value) return - CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { - resultViewModel.updateFormatItemData(res) + kotlin.runCatching { + if (resultViewModel.updatingFormats.value) return + CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { + resultViewModel.updateFormatItemData(res) + } } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt index ae3f1cfc..e040ab17 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadCommandFragment.kt @@ -66,7 +66,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) - shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() } + shownFields = preferences.getStringSet("modify_download_card", requireContext().getStringArray(R.array.modify_download_card_values).toSet())!!.toList() return fragmentView } 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 3a43fca2..eb193f55 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 @@ -470,6 +470,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure } ) bottomSheet.show() + val displayMetrics = DisplayMetrics() + requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) + bottomSheet.behavior.peekHeight = displayMetrics.heightPixels + bottomSheet.window!!.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) } DownloadViewModel.Type.video -> { val bottomSheet = BottomSheetDialog(requireContext()) @@ -530,6 +537,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure ) bottomSheet.show() + val displayMetrics = DisplayMetrics() + requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) + bottomSheet.behavior.peekHeight = displayMetrics.heightPixels + bottomSheet.window!!.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) } DownloadViewModel.Type.command -> { } 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 ad3fd511..ba836fec 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 @@ -223,13 +223,13 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private override fun onFormatsUpdated(allFormats: List>) { lifecycleScope.launch { withContext(Dispatchers.IO){ - resultItem?.formats?.removeAll(formats) - resultItem?.formats?.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) }) - if (resultItem != null){ - resultViewModel.update(resultItem!!) + resultItem?.apply { + this.formats.removeAll(formats) + this.formats.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) }) + resultViewModel.update(this) kotlin.runCatching { val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment - f1.updateUI(resultItem) + f1.updateUI(this) } } } @@ -237,6 +237,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private formats = allFormats.first().filter { !genericVideoFormats.contains(it) }.toMutableList() val preferredFormat = downloadViewModel.getFormat(formats, Type.video) val preferredAudioFormats = downloadViewModel.getPreferredAudioFormats(formats) + downloadItem.format = preferredFormat + downloadItem.allFormats = formats UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, formats.filter { preferredAudioFormats.contains(it.format_id) }) } 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 0c276907..49ac0849 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 @@ -11,23 +11,9 @@ class ProcessingSettingsFragment : BaseSettingsFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.processing_preferences, rootKey) - val preferredVideoCodec : ListPreference? = findPreference("video_codec") - val preferredAudioCodec : ListPreference? = findPreference("audio_codec") val preferredFormatID : EditTextPreference? = findPreference("format_id") val preferredFormatIDAudio : EditTextPreference? = findPreference("format_id_audio") - preferredVideoCodec?.summary = preferredVideoCodec?.entry - preferredVideoCodec?.setOnPreferenceChangeListener { preference, newValue -> - preferredVideoCodec.summary = preferredVideoCodec.entries[preferredVideoCodec.findIndexOfValue(newValue.toString())] - true - } - - preferredAudioCodec?.summary = preferredAudioCodec?.entry - preferredAudioCodec?.setOnPreferenceChangeListener { preference, newValue -> - preferredAudioCodec.summary = preferredAudioCodec.entries[preferredAudioCodec.findIndexOfValue(newValue.toString())] - true - } - preferredFormatID?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.video)}]" preferredFormatID?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]" 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 068a065e..311d0533 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 @@ -5,6 +5,7 @@ import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceManager 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 @@ -63,6 +64,15 @@ class UpdateSettingsFragment : BaseSettingsFragment() { true } + val pipedInstance = findPreference("piped_instance") + pipedInstance?.setOnPreferenceClickListener { + UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){ + editor.putString("piped_instance", it) + editor.apply() + } + true + } + } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/terminal/TerminalFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/terminal/TerminalFragment.kt index e25ca8f6..81e2c92c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/terminal/TerminalFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/terminal/TerminalFragment.kt @@ -202,7 +202,7 @@ class TerminalFragment : Fragment() { showCancelFab() imm.hideSoftInputFromWindow(input?.windowToken, 0) lifecycleScope.launch { - val command = input!!.text.toString().replace("yt-dlp", "") + val command = input!!.text.toString().replaceFirst("yt-dlp", "") downloadID = withContext(Dispatchers.IO){ terminalViewModel.insert(TerminalItem(command = command, log = output!!.text.toString())) } 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 14e672fd..192521b3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/Extensions.kt @@ -98,12 +98,14 @@ object Extensions { } fun File.getMediaDuration(context: Context): Int { - if (!exists()) return 0 - val retriever = MediaMetadataRetriever() - retriever.setDataSource(context, Uri.parse(absolutePath)) - val duration = retriever.extractMetadata(METADATA_KEY_DURATION) - retriever.release() + return kotlin.runCatching { + if (!exists()) return 0 + val retriever = MediaMetadataRetriever() + retriever.setDataSource(context, Uri.parse(absolutePath)) + val duration = retriever.extractMetadata(METADATA_KEY_DURATION) + retriever.release() - return duration?.toIntOrNull()?.div(1000) ?: 0 + duration?.toIntOrNull()?.div(1000) ?: 0 + }.getOrElse { 0 } } } \ No newline at end of file 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 ce8576b2..335392f1 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -10,6 +10,7 @@ import android.util.Patterns import android.widget.Toast import androidx.preference.PreferenceManager import com.afollestad.materialdialogs.utils.MDUtil.getStringArray +import com.anggrayudi.storage.extension.count import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.ChapterItem import com.deniscerri.ytdlnis.database.models.DownloadItem @@ -1045,13 +1046,11 @@ class InfoUtil(private val context: Context) { request.addOption("--keep-fragments") } - val preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!! - val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']" val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true) var filenameTemplate = downloadItem.customFileNameTemplate if(downloadItem.type != DownloadViewModel.Type.command){ - request.addOption("--trim-filenames", downDir.absolutePath.length + 117) + request.addOption("--trim-filenames", 254 - downDir.absolutePath.length) if (downloadItem.SaveThumb) { request.addOption("--write-thumbnail") @@ -1144,31 +1143,52 @@ 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] + when(type){ DownloadViewModel.Type.audio -> { val supportedContainers = context.resources.getStringArray(R.array.audio_containers) var audioQualityId : String = downloadItem.format.format_id - if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "best", "").contains(audioQualityId)){ + if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "ba", "best", "").contains(audioQualityId)){ audioQualityId = "" - if (preferredAudioCodec.isNotBlank()){ - audioQualityId = "${aCodecPref}/bestaudio" - } - }else if (listOf(context.getString(R.string.worst_quality), "worst").contains(audioQualityId)){ + }else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){ audioQualityId = "worstaudio" } + val ext = downloadItem.container - if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId) + 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" + } + 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("-x") + val formatSorting = StringBuilder("hasaud") + + if (aCodecPref.isNotBlank()){ + formatSorting.append(",acodec:$aCodecPref") + } + if(ext.isNotBlank()){ if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){ request.addOption("--audio-format", ext) + formatSorting.append(",ext:$ext") } } request.addOption("-P", downDir.absolutePath) + request.addOption("-S", formatSorting.toString()) if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ request.addOption("--split-chapters") @@ -1242,111 +1262,99 @@ class InfoUtil(private val context: Context) { } //format logic - var videof = downloadItem.format.format_id - var audiof = "ba" - if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()){ - audiof = downloadItem.videoPreferences.audioFormatIDs.joinToString("+") - } - if (downloadItem.videoPreferences.removeAudio) audiof = "" + var videoF = downloadItem.format.format_id + var audioF = downloadItem.videoPreferences.audioFormatIDs.joinToString("+").ifBlank { "ba" } + val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!! + if (downloadItem.videoPreferences.removeAudio) audioF = "" - val defaultFormats = context.resources.getStringArray(R.array.video_formats_values) - val usingGenericFormat = defaultFormats.contains(videof) || downloadItem.allFormats.isEmpty() || downloadItem.allFormats == getGenericVideoFormats(context.resources) val f = StringBuilder() - if(!usingGenericFormat){ - 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 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") - if (audiof.contains("+")){ + if (audioF.count("+") > 1){ request.addOption("--audio-multistreams") } }else{ - if (videof == context.resources.getString(R.string.best_quality) || videof == "best") { - videof = "bv" - }else if (videof == context.resources.getString(R.string.worst_quality) || videof == "worst") { - videof = "worst" - }else if (defaultFormats.contains(videof)) { - videof = "bv[height<="+videof.split("_")[0].dropLast(1)+"]" + if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best") { + videoF = "bv" + }else if (videoF == context.resources.getString(R.string.worst_quality) || videoF == "worst") { + videoF = "worst" + }else if (defaultFormats.contains(videoF)) { + videoF = "bv[height<="+videoF.split("_")[0].dropLast(1)+"]" } val preferredFormatIDs = sharedPreferences.getString("format_id", "").toString() .split(",") .filter { it.isNotEmpty() } - .ifEmpty { listOf(videof) }.toMutableList() - if (!preferredFormatIDs.contains(videof)){ - preferredFormatIDs.add(0, videof) + .ifEmpty { listOf(videoF) }.toMutableList() + if (!preferredFormatIDs.contains(videoF)){ + preferredFormatIDs.add(0, videoF) } + // ^ [videoF, preferredID1, preferredID2...] val preferredAudioFormatIDs = sharedPreferences.getString("format_id_audio", "") .toString() .split(",") - .filter { it.isNotEmpty() } + .filter { it.isNotBlank() } .ifEmpty { val list = mutableListOf() if (preferredAudioCodec.isNotBlank()) list.add("ba[acodec~='^($preferredAudioCodec)']") - list.add(audiof) + if (preferredAudioLanguage.isNotEmpty()) list.add("ba[language=$preferredAudioLanguage]") + list.add(audioF) list - } + }.apply { - val preferredAudioFormats = if (downloadItem.videoPreferences.audioFormatIDs.isEmpty()) { - preferredAudioFormatIDs - } else listOf(audiof) - - if (preferredAudioFormats.any{it.contains("+")}){ + }.toMutableList() + if (!preferredAudioFormatIDs.contains(audioF) && audioF != "ba"){ + preferredAudioFormatIDs.add(0, audioF) + } + // ^ [audioF, preferredAID1, preferredAID2, ....] + if (preferredAudioFormatIDs.any{it.contains("+")}){ request.addOption("--audio-multistreams") } val preferredCodec = sharedPreferences.getString("video_codec", "") - val extPref = if (cont.isNotBlank()) "[ext=$cont]" else "" - val vCodecPref = if (preferredCodec!!.isNotBlank()) "[vcodec~='^($preferredCodec)']" else "" - - + val vCodecPrefIndex = context.getStringArray(R.array.video_codec_values).indexOf(preferredCodec) + val vCodecPref = context.getStringArray(R.array.video_codec_values_ytdlp)[vCodecPrefIndex] preferredFormatIDs.forEach { v -> - //build format with extension and vcodec - preferredAudioFormats.forEach { a -> + preferredAudioFormatIDs.forEach { a -> val aa = if (a.isNotBlank()) "+$a" else "" - f.append("$v$extPref$vCodecPref$aa/") + f.append("$v$aa/") } - - //build format with vcodec - if (extPref.isNotBlank()){ - preferredAudioFormats.forEach { a -> - val aa = if (a.isNotBlank()) "+$a" else "" - f.append("$v$vCodecPref$aa/") - } - } - - //build format with audio - if (vCodecPref.isNotBlank()){ - preferredAudioFormats.forEach {a -> - val aa = if (a.isNotBlank()) "+$a" else "" - f.append("$v$aa/") - } - } - + //build format with just videoformatid and audio if remove audio is not checked if (!downloadItem.videoPreferences.removeAudio){ + //build format with audio with preferred language + if (preferredAudioLanguage.isNotBlank()){ + val al = "$v+ba[language=$preferredAudioLanguage]/" + if (!f.contains(al)) f.append(al) + } //build format with best audio - if(!f.contains("$v+ba/")){ - f.append("$v+ba/") - } - - //build formats with standalone video - if(!f.contains("/$v/")){ - f.append("$v/") - } + if (!f.contains("$v+ba/")) f.append("$v+ba/") + //build format with standalone video + f.append("$v/") } } - if(!f.endsWith("best/")){ + if(!f.endsWith("b/")){ //last fallback - f.append("best") + 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()) + } @@ -1404,6 +1412,18 @@ class InfoUtil(private val context: Context) { return request } + fun getPipedInstances() : List { + kotlin.runCatching { + val res = genericArrayRequest("https://piped-instances.kavin.rocks/") + val list = mutableListOf() + for (i in 0 until res.length()) { + val element = res.getJSONObject(i) + list.add(element.getString("api_url")) + } + return list + } + return listOf() + } fun parseYTDLRequestString(request : YoutubeDLRequest) : String { val arr = request.buildCommand().toMutableList() 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 81d2be89..786e6dca 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -35,6 +35,8 @@ class NotificationUtil(var context: Context) { private val workerNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_WORKER_CHANNEL_ID) private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID) private val finishedDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_FINISHED_CHANNEL_ID) + private val erroredDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_ERRORED_CHANNEL_ID) + private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java) private val resources: Resources = context.resources @@ -68,7 +70,7 @@ class NotificationUtil(var context: Context) { channel.description = description notificationManager.createNotificationChannel(channel) - //finished or errored downloads + //finished downloads name = resources.getString(R.string.finished_download_notification_channel_name) description = resources.getString(R.string.finished_download_notification_channel_description) @@ -76,7 +78,7 @@ class NotificationUtil(var context: Context) { channel.description = description notificationManager.createNotificationChannel(channel) - //finished or errored downloads + //errored downloads name = resources.getString(R.string.errored_downloads) description = resources.getString(R.string.errored_download_notification_channel_description) @@ -99,6 +101,7 @@ class NotificationUtil(var context: Context) { COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID -> { return commandDownloadNotificationBuilder } DOWNLOAD_FINISHED_CHANNEL_ID -> { return finishedDownloadNotificationBuilder } DOWNLOAD_WORKER_CHANNEL_ID -> { return workerNotificationBuilder } + DOWNLOAD_ERRORED_CHANNEL_ID -> { return erroredDownloadNotificationBuilder } } return downloadNotificationBuilder } @@ -554,11 +557,11 @@ class NotificationUtil(var context: Context) { 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_FINISHED_NOTIFICATION_ID = 7 - const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 6 + const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 30000 + const val DOWNLOAD_RESUME_NOTIFICATION_ID = 40000 + const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 50000 + const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 60000 + const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 70000 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 501dfd2b..3f25a470 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt @@ -8,9 +8,6 @@ import android.content.ClipboardManager import android.content.Context import android.content.DialogInterface import android.content.Intent -import android.graphics.Color -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.shapes.OvalShape import android.net.Uri import android.os.Build import android.text.Editable @@ -19,7 +16,6 @@ import android.text.format.DateFormat import android.util.DisplayMetrics import android.util.Log import android.view.Gravity -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.Window @@ -36,13 +32,12 @@ 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.core.widget.doOnTextChanged import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.CommandTemplate @@ -57,6 +52,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener +import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton @@ -69,23 +65,19 @@ import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.materialswitch.MaterialSwitch +import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputLayout import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat -import com.neo.highlight.core.Highlight -import com.neo.highlight.util.listener.HighlightTextWatcher -import com.neo.highlight.util.scheme.ColorScheme import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import me.zhanghai.android.fastscroll.FastScrollerBuilder import java.io.File import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale -import java.util.regex.Pattern object UiUtil { @@ -481,10 +473,18 @@ object UiUtil { val bottomSheet = BottomSheetDialog(context) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet) - val title = bottomSheet.findViewById(R.id.bottom_sheet_title) - title!!.text = item.title.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } - val author = bottomSheet.findViewById(R.id.bottom_sheet_author) - author!!.text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } + bottomSheet.findViewById(R.id.bottom_sheet_title)?.apply { + text = item.title.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } + setOnClickListener{ + showFullTextDialog(context, text.toString(), context.getString(R.string.title)) + } + } + bottomSheet.findViewById(R.id.bottom_sheet_author)?.apply { + text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } + setOnClickListener{ + showFullTextDialog(context, text.toString(), context.getString(R.string.author)) + } + } // BUTTON ---------------------------------- val btn = bottomSheet.findViewById(R.id.download_button_type) @@ -507,6 +507,7 @@ object UiUtil { val container = bottomSheet.findViewById(R.id.container_chip) val codec = bottomSheet.findViewById(R.id.codec) val fileSize = bottomSheet.findViewById(R.id.file_size) + val command = bottomSheet.findViewById(R.id.command) when(status){ DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused -> { @@ -527,9 +528,13 @@ object UiUtil { } } - if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility = - View.GONE - else formatNote!!.text = item.format.format_note + if (item.type != DownloadViewModel.Type.command){ + if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility = + View.GONE + else formatNote!!.text = item.format.format_note + }else{ + formatNote?.isVisible = false + } if (item.format.container != "") { container!!.text = item.format.container.uppercase() @@ -557,6 +562,12 @@ object UiUtil { if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE else fileSize!!.text = fileSizeReadable + val infoUtil = InfoUtil(context) + + command?.setOnClickListener { + showGeneratedCommand(context, infoUtil.parseYTDLRequestString(infoUtil.buildYoutubeDLRequest(item))) + } + val link = bottomSheet.findViewById