From 80672a2556a774b33bd94cf84ece5f9e1310115f Mon Sep 17 00:00:00 2001 From: zaednasr <75589932+zaednasr@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:40:22 +0200 Subject: [PATCH] new features added a header on cancelled, errored, saved, scheduled tabs to show the count of items and their own separate menus to copy the urls and delete them all fix app not starting a scheduled download when there were no paused items, strange ik add socket timeout in download settings fixed app not putting extra commands in the very end of the command list home screen channel tab filter bug fixes Add ability to Set default command template --- .../ytdl/database/dao/DownloadDao.kt | 3 ++ .../database/viewmodel/DownloadViewModel.kt | 5 ++ .../database/viewmodel/HistoryViewModel.kt | 2 +- .../DownloadMultipleBottomSheetDialog.kt | 4 +- .../downloads/CancelledDownloadsFragment.kt | 46 +++++++++++++++- .../ui/downloads/DownloadQueueMainFragment.kt | 53 +------------------ .../ui/downloads/ErroredDownloadsFragment.kt | 44 ++++++++++++++- .../ytdl/ui/downloads/HistoryFragment.kt | 2 +- .../ui/downloads/QueuedDownloadsFragment.kt | 2 +- .../ui/downloads/SavedDownloadsFragment.kt | 45 +++++++++++++++- .../downloads/ScheduledDownloadsFragment.kt | 50 ++++++++++++++++- .../downloadLogs/DownloadLogListFragment.kt | 2 +- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 10 ++++ .../ytdl/util/extractors/YTDLPUtil.kt | 39 +++++++++----- .../deniscerri/ytdl/work/DownloadWorker.kt | 2 +- .../res/drawable/baseline_more_horiz_24.xml | 5 ++ app/src/main/res/layout/generic_list.xml | 40 +++++++++++++- .../layout/history_other_filters_sheet.xml | 2 +- .../main/res/menu/cancelled_header_menu.xml | 13 +++++ app/src/main/res/menu/download_queue_menu.xml | 33 ------------ app/src/main/res/menu/errored_header_menu.xml | 13 +++++ app/src/main/res/menu/saved_header_menu.xml | 13 +++++ .../main/res/menu/scheduled_header_menu.xml | 19 +++++++ app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-bg/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-in/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-iw/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pa/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-sq-rAL/strings.xml | 2 +- app/src/main/res/values-sr/strings.xml | 2 +- app/src/main/res/values-tr-rTR/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/arrays.xml | 5 ++ app/src/main/res/values/strings.xml | 3 ++ .../main/res/xml/downloading_preferences.xml | 6 +++ app/src/main/res/xml/general_preferences.xml | 2 +- 52 files changed, 371 insertions(+), 142 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_more_horiz_24.xml create mode 100644 app/src/main/res/menu/cancelled_header_menu.xml create mode 100644 app/src/main/res/menu/errored_header_menu.xml create mode 100644 app/src/main/res/menu/saved_header_menu.xml create mode 100644 app/src/main/res/menu/scheduled_header_menu.xml diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt index 337b55ac..a0e4d920 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt @@ -273,6 +273,9 @@ interface DownloadDao { @Query("UPDATE downloads SET downloadStartTime=0, status='Queued' where id in (:list)") suspend fun resetScheduleTimeForItems(list: List) + @Query("UPDATE downloads SET downloadStartTime=0, status='Queued' WHERE status = 'Scheduled'") + suspend fun resetScheduleTimeForAllScheduledItems() + @Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)") suspend fun reQueueDownloadItems(list: List) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index 89a7e895..84e51662 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -754,6 +754,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel repository.startDownloadWorker(emptyList(), application) } + suspend fun resetScheduleItemForAllScheduledItemsAndStartDownload() = CoroutineScope(Dispatchers.IO).launch { + dbManager.downloadDao.resetScheduleTimeForAllScheduledItems() + repository.startDownloadWorker(emptyList(), application) + } + suspend fun putAtTopOfQueue(ids: List) = CoroutineScope(Dispatchers.IO).launch{ val downloads = dao.getQueuedDownloadsListIDs() val lastID = ids.maxOf { it } diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt index 12d934af..479bba0a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/HistoryViewModel.kt @@ -28,7 +28,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) val sortOrder = MutableLiveData(SORTING.DESC) val sortType = MutableLiveData(HistorySortType.DATE) val websiteFilter = MutableLiveData("") - val statusFilter = MutableLiveData(HistoryStatus.UNSET) + val statusFilter = MutableLiveData(HistoryStatus.ALL) private val queryFilter = MutableLiveData("") private val typeFilter = MutableLiveData("") diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index 32ff2a77..09dedb76 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -913,7 +913,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure downloadViewModel.deleteDownload(id) if (processingItemsCount > 0){ - Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) + Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { lifecycleScope.launch(Dispatchers.IO) { processingItemsCount++ @@ -976,7 +976,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure if (processingItemsCount > 0) { - Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) + Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { processingItemsCount++ downloadViewModel.insert(deletedItem) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt index 3a664102..cf2ce7e2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/CancelledDownloadsFragment.kt @@ -6,17 +6,23 @@ import android.content.DialogInterface import android.content.SharedPreferences import android.graphics.Canvas import android.graphics.Color +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.PopupMenu import android.widget.RelativeLayout +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.os.bundleOf +import androidx.core.view.get +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -36,6 +42,7 @@ import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode import com.deniscerri.ytdl.util.Extensions.toListString import com.deniscerri.ytdl.util.UiUtil import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar @@ -60,6 +67,10 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic private var actionMode : ActionMode? = null private var totalSize = 0 + private lateinit var listHeader : ConstraintLayout + private lateinit var count : TextView + private lateinit var headerMenuBtn : TextView + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -72,7 +83,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic return fragmentView } - @SuppressLint("RestrictedApi") + @SuppressLint("RestrictedApi", "SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -100,8 +111,39 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic } } + listHeader = view.findViewById(R.id.list_header) + count = view.findViewById(R.id.count) + headerMenuBtn = view.findViewById(R.id.dropdown_menu) + + headerMenuBtn.setOnClickListener { + val popup = PopupMenu(activity, it) + popup.menuInflater.inflate(R.menu.cancelled_header_menu, popup.menu) + popup.setOnMenuItemClickListener { m -> + when(m.itemId){ + R.id.delete_all -> { + UiUtil.showGenericDeleteAllDialog(requireContext()) { + downloadViewModel.deleteCancelled() + } + } + R.id.copy_urls -> { + lifecycleScope.launch { + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Cancelled)) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + } + } + } + true + } + + popup.show() + } + downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Cancelled)).observe(viewLifecycleOwner){ totalSize = it + listHeader.isVisible = it > 0 + count.text = "$it ${getString(R.string.items)}" noResults.visibility = if (it == 0) View.VISIBLE else View.GONE } } @@ -331,7 +373,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic downloadViewModel.getItemByID(itemID) } downloadViewModel.deleteDownload(deletedItem.id) - Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG) + Snackbar.make(cancelledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { downloadViewModel.insert(deletedItem) }.show() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt index f7c72749..e1096e79 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt @@ -208,54 +208,15 @@ class DownloadQueueMainFragment : Fragment(){ try{ when(m.itemId){ R.id.clear_all -> { - showDeleteDialog { + UiUtil.showGenericDeleteAllDialog(requireContext()) { downloadViewModel.deleteAll() } } R.id.clear_queue -> { - showDeleteDialog { + UiUtil.showGenericDeleteAllDialog(requireContext()) { downloadViewModel.cancelAllDownloads() } } - R.id.clear_cancelled -> { - showDeleteDialog { - downloadViewModel.deleteCancelled() - } - } - R.id.clear_scheduled -> { - showDeleteDialog { - downloadViewModel.deleteScheduled() - } - } - R.id.clear_errored -> { - showDeleteDialog { - downloadViewModel.deleteErrored() - } - } - R.id.clear_saved -> { - showDeleteDialog { - downloadViewModel.deleteSaved() - } - } - R.id.copy_urls -> { - lifecycleScope.launch { - val tabStatus = mapOf( - 0 to listOf(DownloadRepository.Status.Active), - 1 to listOf(DownloadRepository.Status.Queued), - 2 to listOf(DownloadRepository.Status.Scheduled), - 3 to listOf(DownloadRepository.Status.Cancelled), - 4 to listOf(DownloadRepository.Status.Error), - 5 to listOf(DownloadRepository.Status.Saved), - ) - tabStatus[tabLayout.selectedTabPosition]?.apply { - val urls = withContext(Dispatchers.IO){ - downloadViewModel.getURLsByStatus(this@apply) - } - UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) - } - } - - } } }catch (e: Exception){ Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() @@ -265,16 +226,6 @@ class DownloadQueueMainFragment : Fragment(){ } } - private fun showDeleteDialog (deleteClicked: (deleteClicked: Boolean) -> Unit){ - val deleteDialog = MaterialAlertDialogBuilder(requireContext()) - deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items)) - deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } - deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> - deleteClicked(true) - } - deleteDialog.show() - } - fun scrollToActive(){ tabLayout.getTabAt(0)!!.select() viewPager2.setCurrentItem(0, true) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt index 3d363350..3239d9d4 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ErroredDownloadsFragment.kt @@ -14,10 +14,14 @@ import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.AdapterView.OnItemClickListener +import android.widget.PopupMenu import android.widget.RelativeLayout +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -60,6 +64,11 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL private lateinit var adapter : GenericDownloadAdapter private var actionMode : ActionMode? = null private var totalSize: Int = 0 + + private lateinit var listHeader : ConstraintLayout + private lateinit var count : TextView + private lateinit var headerMenuBtn : TextView + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -72,7 +81,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL return fragmentView } - @SuppressLint("RestrictedApi") + @SuppressLint("RestrictedApi", "SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -100,8 +109,39 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL } } + listHeader = view.findViewById(R.id.list_header) + count = view.findViewById(R.id.count) + headerMenuBtn = view.findViewById(R.id.dropdown_menu) + + headerMenuBtn.setOnClickListener { + val popup = PopupMenu(activity, it) + popup.menuInflater.inflate(R.menu.errored_header_menu, popup.menu) + popup.setOnMenuItemClickListener { m -> + when(m.itemId){ + R.id.delete_all -> { + UiUtil.showGenericDeleteAllDialog(requireContext()) { + downloadViewModel.deleteErrored() + } + } + R.id.copy_urls -> { + lifecycleScope.launch { + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Error)) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + } + } + } + true + } + + popup.show() + } + downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Error)).observe(viewLifecycleOwner){ totalSize = it + listHeader.isVisible = it > 0 + count.text = "$it ${getString(R.string.items)}" noResults.visibility = if (it == 0) View.VISIBLE else View.GONE } } @@ -334,7 +374,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL downloadViewModel.getItemByID(itemID) } downloadViewModel.deleteDownload(deletedItem.id) - Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG) + Snackbar.make(erroredRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { downloadViewModel.insert(deletedItem) }.show() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt index 533e62d0..e0624e6b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt @@ -684,7 +684,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{ } if (!deleteFile){ - Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) + Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { historyViewModel.insert(deletedItem) }.show() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt index d1bfbecc..cb8ccfd3 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/QueuedDownloadsFragment.kt @@ -154,7 +154,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis downloadViewModel.updateDownload(item) } - Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title.ifEmpty { item.url }, Snackbar.LENGTH_LONG) + Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title.ifEmpty { item.url }, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { lifecycleScope.launch(Dispatchers.IO) { downloadViewModel.deleteDownload(item.id) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt index daa152d1..119f11ac 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/SavedDownloadsFragment.kt @@ -12,11 +12,15 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.PopupMenu import android.widget.RelativeLayout +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -59,6 +63,11 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis private lateinit var noResults: RelativeLayout private var actionMode : ActionMode? = null private var totalSize : Int = 0 + + private lateinit var listHeader : ConstraintLayout + private lateinit var count : TextView + private lateinit var headerMenuBtn : TextView + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -71,7 +80,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis return fragmentView } - @SuppressLint("RestrictedApi") + @SuppressLint("RestrictedApi", "SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -100,8 +109,40 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis } } + listHeader = view.findViewById(R.id.list_header) + count = view.findViewById(R.id.count) + headerMenuBtn = view.findViewById(R.id.dropdown_menu) + + headerMenuBtn.setOnClickListener { + val popup = PopupMenu(activity, it) + popup.menuInflater.inflate(R.menu.saved_header_menu, popup.menu) + popup.setOnMenuItemClickListener { m -> + when(m.itemId){ + R.id.delete_all -> { + UiUtil.showGenericDeleteAllDialog(requireContext()) { + downloadViewModel.deleteSaved() + } + } + R.id.copy_urls -> { + lifecycleScope.launch { + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Saved)) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + } + } + } + true + } + + popup.show() + } + + downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Saved)).observe(viewLifecycleOwner){ totalSize = it + listHeader.isVisible = it > 0 + count.text = "$it ${getString(R.string.items)}" noResults.visibility = if (it == 0) View.VISIBLE else View.GONE } } @@ -321,7 +362,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis downloadViewModel.getItemByID(itemID) } downloadViewModel.deleteDownload(deletedItem.id) - Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG) + Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { downloadViewModel.insert(deletedItem) }.show() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt index a8cd3f3d..b70d6d47 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ScheduledDownloadsFragment.kt @@ -12,11 +12,15 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.PopupMenu import android.widget.RelativeLayout +import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -61,6 +65,10 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl private var actionMode : ActionMode? = null private var totalSize = 0 + private lateinit var listHeader : ConstraintLayout + private lateinit var count : TextView + private lateinit var headerMenuBtn : TextView + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -73,7 +81,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl return fragmentView } - @SuppressLint("RestrictedApi") + @SuppressLint("RestrictedApi", "SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -101,9 +109,47 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl } } + listHeader = view.findViewById(R.id.list_header) + count = view.findViewById(R.id.count) + headerMenuBtn = view.findViewById(R.id.dropdown_menu) + + headerMenuBtn.setOnClickListener { + val popup = PopupMenu(activity, it) + popup.menuInflater.inflate(R.menu.scheduled_header_menu, popup.menu) + popup.setOnMenuItemClickListener { m -> + when(m.itemId){ + R.id.download_now -> { + lifecycleScope.launch { + withContext(Dispatchers.IO){ + downloadViewModel.resetScheduleItemForAllScheduledItemsAndStartDownload() + } + } + } + R.id.delete_all -> { + UiUtil.showGenericDeleteAllDialog(requireContext()) { + downloadViewModel.deleteScheduled() + } + } + R.id.copy_urls -> { + lifecycleScope.launch { + val urls = withContext(Dispatchers.IO){ + downloadViewModel.getURLsByStatus(listOf(DownloadRepository.Status.Scheduled)) + } + UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity()) + } + } + } + true + } + + popup.show() + } + lifecycleScope.launch { downloadViewModel.scheduledDownloadsCount.collectLatest { totalSize = it + listHeader.isVisible = it > 0 + count.text = "$it ${getString(R.string.items)}" noResults.visibility = if (it == 0) View.VISIBLE else View.GONE } } @@ -336,7 +382,7 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl downloadViewModel.getItemByID(itemID) } downloadViewModel.deleteDownload(deletedItem.id) - Snackbar.make(scheduledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_LONG) + Snackbar.make(scheduledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title.ifEmpty { deletedItem.url }, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { downloadViewModel.insert(deletedItem) }.show() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogListFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogListFragment.kt index dc20d44f..633de570 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogListFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogListFragment.kt @@ -240,7 +240,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe logViewModel.getItemById(items[position].id) } logViewModel.delete(deletedItem) - Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) + Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE) .setAction(getString(R.string.undo)) { lifecycleScope.launch(Dispatchers.IO){ logViewModel.insert(deletedItem) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index ae27e023..9bb8389d 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -1637,6 +1637,16 @@ object UiUtil { deleteDialog.show() } + fun showGenericDeleteAllDialog(context: Context, accepted: () -> Unit){ + val deleteDialog = MaterialAlertDialogBuilder(context) + deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete_multiple_items)) + deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } + deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int -> + accepted() + } + deleteDialog.show() + } + fun showRemoveHistoryItemDialog(item: HistoryItem, context: Activity, delete: (item: HistoryItem, deleteFile: Boolean) -> Unit){ val deleteFile = booleanArrayOf(false) val deleteDialog = MaterialAlertDialogBuilder(context) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt index 290aa566..ff8bbf7e 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt @@ -81,7 +81,8 @@ class YTDLPUtil(private val context: Context) { request.addOption("--skip-download") request.addOption("-R", "1") request.addOption("--compat-options", "manifest-filesize-approx") - request.addOption("--socket-timeout", "5") + val socketTimeout = sharedPreferences.getString("socket_timeout", "5")!!.ifEmpty { "5" } + request.addOption("--socket-timeout", socketTimeout) if (sharedPreferences.getBoolean("force_ipv4", false)){ request.addOption("-4") @@ -240,7 +241,8 @@ class YTDLPUtil(private val context: Context) { request.addOption("-a", urlsFile.absolutePath) request.addOption("--skip-download") request.addOption("-R", "1") - request.addOption("--socket-timeout", "5") + val socketTimeout = sharedPreferences.getString("socket_timeout", "5")!!.ifEmpty { "5" } + request.addOption("--socket-timeout", socketTimeout) if (sharedPreferences.getBoolean("force_ipv4", false)){ request.addOption("-4") @@ -435,7 +437,8 @@ class YTDLPUtil(private val context: Context) { request.addOption("--print", "%(.{urls,chapters})s") request.addOption("--skip-download") request.addOption("-R", "1") - request.addOption("--socket-timeout", "5") + val socketTimeout = sharedPreferences.getString("socket_timeout", "5")!!.ifEmpty { "5" } + request.addOption("--socket-timeout", socketTimeout) if (sharedPreferences.getBoolean("force_ipv4", false)){ request.addOption("-4") @@ -524,6 +527,7 @@ class YTDLPUtil(private val context: Context) { @SuppressLint("RestrictedApi") fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest { val useItemURL = sharedPreferences.getBoolean("use_itemurl_instead_playlisturl", false) + var isPlaylistItem = false val request = if (downloadItem.url.endsWith(".txt")) { YoutubeDLRequest(listOf()).apply { @@ -532,6 +536,7 @@ class YTDLPUtil(private val context: Context) { }else if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || useItemURL){ YoutubeDLRequest(downloadItem.url) }else{ + isPlaylistItem = true YoutubeDLRequest(downloadItem.playlistURL!!).apply { if(downloadItem.playlistIndex == null){ val matchPortion = downloadItem.url.split("/").last().split("=").last().split("&").first() @@ -588,6 +593,11 @@ class YTDLPUtil(private val context: Context) { request.addOption("--no-resize-buffer") } + val socketTimeout = sharedPreferences.getString("socket_timeout", "")!! + if (socketTimeout.isNotBlank()) { + request.addOption("--socket-timeout", socketTimeout) + } + val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!! if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL) @@ -801,9 +811,10 @@ class YTDLPUtil(private val context: Context) { metadataCommands.addOption("--parse-metadata", "description:(?:Released on: )(?P\\d{4})") metadataCommands.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P\\d+)") metadataCommands.addOption("--parse-metadata", "%(album_artist,first_artist|)s:%(album_artist)s") - metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P\\d+)") - + if (isPlaylistItem) { + metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P\\d+)") + } } val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true) @@ -1081,21 +1092,21 @@ class YTDLPUtil(private val context: Context) { else -> {} } + if (metadataCommands.isNotEmpty()){ + request.addCommands(metadataCommands) + } + if (downloadItem.extraCommands.isNotBlank() && downloadItem.type != DownloadViewModel.Type.command){ val cache = File(FileUtil.getCachePath(context)) cache.mkdirs() val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt") conf.createNewFile() conf.writeText(downloadItem.extraCommands) - request.addOption( - "--config-locations", - conf.absolutePath - ) + val tmp = mutableListOf() + tmp.addOption("--config-locations", conf.absolutePath) + request.addCommands(tmp) } - if (metadataCommands.isNotEmpty()){ - request.addCommands(metadataCommands) - } return request } @@ -1103,8 +1114,8 @@ class YTDLPUtil(private val context: Context) { val playerClient = sharedPreferences.getString("youtube_player_client", "default,mediaconnect")!! .ifEmpty { "default,mediaconnect" } val extractorArgs = mutableListOf() - extractorArgs.add("player_client:${playerClient}") - val lang = sharedPreferences.getString("app_language", "en") + extractorArgs.add("player_client=${playerClient}") + val lang = Locale.getDefault().language if (context.getStringArray(R.array.subtitle_langs).contains(lang)) { extractorArgs.add("lang=$lang") } diff --git a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt index 171f1863..9dcc5d5a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt @@ -74,7 +74,7 @@ class DownloadWorker( val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val time = System.currentTimeMillis() + 6000 val queuedItems = dao.getQueuedScheduledDownloadsUntil(time) - val priorityItemIDs = inputData.getLongArray("priority_item_ids")!!.toMutableList() + val priorityItemIDs = (inputData.getLongArray("priority_item_ids") ?: longArrayOf()).toMutableList() val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true) // this is needed for observe sources call, so it wont create result items diff --git a/app/src/main/res/drawable/baseline_more_horiz_24.xml b/app/src/main/res/drawable/baseline_more_horiz_24.xml new file mode 100644 index 00000000..ccdca9d2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_more_horiz_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/generic_list.xml b/app/src/main/res/layout/generic_list.xml index 41f37d88..47a151f2 100644 --- a/app/src/main/res/layout/generic_list.xml +++ b/app/src/main/res/layout/generic_list.xml @@ -1,10 +1,46 @@ - + + + + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/history_other_filters_sheet.xml b/app/src/main/res/layout/history_other_filters_sheet.xml index 1b366ba8..12270e8a 100644 --- a/app/src/main/res/layout/history_other_filters_sheet.xml +++ b/app/src/main/res/layout/history_other_filters_sheet.xml @@ -10,6 +10,7 @@ @@ -66,7 +67,6 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="20dp" android:layout_marginBottom="10dp" - android:layout_marginTop="20dp" android:text="@string/websites" android:textSize="14sp" android:textStyle="bold" /> diff --git a/app/src/main/res/menu/cancelled_header_menu.xml b/app/src/main/res/menu/cancelled_header_menu.xml new file mode 100644 index 00000000..4259e64d --- /dev/null +++ b/app/src/main/res/menu/cancelled_header_menu.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/download_queue_menu.xml b/app/src/main/res/menu/download_queue_menu.xml index 2946873b..20bfa3e4 100644 --- a/app/src/main/res/menu/download_queue_menu.xml +++ b/app/src/main/res/menu/download_queue_menu.xml @@ -7,42 +7,9 @@ android:icon="@drawable/ic_delete_all" android:title="@string/remove_downloading" app:showAsAction="never" /> - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/errored_header_menu.xml b/app/src/main/res/menu/errored_header_menu.xml new file mode 100644 index 00000000..e2e22d45 --- /dev/null +++ b/app/src/main/res/menu/errored_header_menu.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/saved_header_menu.xml b/app/src/main/res/menu/saved_header_menu.xml new file mode 100644 index 00000000..8dfaf6b1 --- /dev/null +++ b/app/src/main/res/menu/saved_header_menu.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/scheduled_header_menu.xml b/app/src/main/res/menu/scheduled_header_menu.xml new file mode 100644 index 00000000..f4c8f377 --- /dev/null +++ b/app/src/main/res/menu/scheduled_header_menu.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 0e96fead..cc8e662d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -409,7 +409,7 @@ استخدام AlarmManager بدلًا من WorkManager يتعين تمكين هذا الخيار عندما تكون أداة الـ WorkManager مقيدًة من قبل مُصنِّع الجهاز أو إذا كانت المهام المجدولة ليست دقيقة جدًا استخدام رابط العنصر بدلًا من رابط قائمة التشغيل - مفيد لقوائم تشغيل LibreTube غير المتصلة بالإنترنت والتي لا يتعرف عليها yt-dlp، لن يتم تضمين البيانات الوصفية لقائمة التشغيل مع تمكين هذا الخيار + لن يتم تضمين البيانات الوصفية لقائمة التشغيل مع تمكين هذا الخيار إعادة ترميز الفيديو يعيد ترميز ملف الفيديو إلى تنسيق الفيديو المحدد يوميًا diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index d4016a7b..0968414f 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -418,6 +418,6 @@ Прекодиране на видео Перекодира видео файлът в указания видеоформат Подреждане важността на елемента на формат. Това подреждане ще се използва, само когато приложението извлече форматите от картата за изтегляне и автоматично избере формата! - Полезно за офлайн плейлисти Libretube, които не се разпознават от yt-dlp. Метаданните на плейлиста няма да бъдат вградени, ако функция та е включена + Метаданните на плейлиста няма да бъдат вградени, ако функция та е включена Екстрактор за извличане на данни (YouTube) \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index eef6dd30..46bc06e9 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -409,7 +409,7 @@ Použít AlarmManager místo WorkManageru pro plánování Funkci povolte, pokud je WorkManager omezen výrobcem zařízení nebo pokud naplánované úlohy nejsou příliš přesné Použít URL položky místo URL playlistu - Užitečné pro offline playlisty Libretube, které nejsou rozpoznány yt-dlp. Metadata playlistu nebudou vložena, pokud je tato funkce povolena + Metadata playlistu nebudou vložena, pokud je tato funkce povolena Překódovat video Překóduje videosoubor do zadaného formátu videa Denně diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 63c0b292..27ae494c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -418,5 +418,5 @@ Nimmt die Videodatei im bestimmten Format auf AlarmManager anstatt WorkManager für die Zeitplanung nutzen Die Element URL anstatt die Playlist URL nutzen - Nützlich für Libretube Offline-Wiedergabelisten, die von yt-dlp nicht erkannt werden. Wiedergabelisten-Metadaten werden nicht eingebettet, wenn diese Option aktiviert ist + Wiedergabelisten-Metadaten werden nicht eingebettet, wenn diese Option aktiviert ist \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 09e3bb8c..865c9afe 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -409,7 +409,7 @@ Utilizar AlarmManager en lugar de WorkManager para la programación Habilite esta opción si WorkManager está restringido por el proveedor de su dispositivo o si los trabajos programados no son demasiado precisos Utilizar la URL del artículo en lugar de la URL de la lista de reproducción - Útil para listas de reproducción sin conexión de Libretube que no son reconocidas por yt-dlp. Los metadatos de la lista de reproducción no se incrustarán con esta opción activada + Los metadatos de la lista de reproducción no se incrustarán con esta opción activada Recodificar video Recodifica el archivo de vídeo al formato de vídeo especificado Diariamente diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 92599f9c..50065cd4 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -419,6 +419,6 @@ Ajuste l\'ordre de priorité des formats. Cette ordre ne sera utilisée que lorsque l\'application récupère les formats dans la carte de téléchargement et sélectionne automatiquement le format ! Vous devez activer la permission SCHEDULE_EXACT_ALARM dans les paramètres de l\'application. "Les téléchargements sont toujours en cours de chargement. Voulez-vous continuer à les charger en arrière-plan et commencer à télécharger ? " - Utile pour les playlists hors-ligne de Libretube qui ne sont pas reconnues par yt-dlp. Les métadonnées de la liste de lecture ne seront pas intégrées quand ceci est activé + Les métadonnées de la liste de lecture ne seront pas intégrées quand ceci est activé Utiliser l\'URL de l\'élément au lieu de celui de la liste de lecture \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 27d6cdde..4c661690 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -408,7 +408,7 @@ \nफाइल में सहेजे गए डेटा को मिटाने और केवल उसका उपयोग करने के लिए \'रीसेट करें\' दबाएं। शेड्यूलिंग के लिए WorkManager के बजाय AlarmManager का उपयोग करें यदि WorkManager आपके डिवाइस विक्रेता द्वारा प्रतिबंधित है या शेड्यूल किए गए कार्य बहुत सटीक नहीं हैं तो इसे सक्षम करें - Libretube ऑफ़लाइन प्लेलिस्ट के लिए उपयोगी है जो yt-dlp द्वारा पहचाने नहीं जाते हैं। इस सक्षम के साथ प्लेलिस्ट मेटाडेटा एम्बेड नहीं किया जाएगा + नहीं जाते हैं। इस सक्षम के साथ प्लेलिस्ट मेटाडेटा एम्बेड नहीं किया जाएगा प्लेलिस्ट URL के बजाय आइटम URL का उपयोग करें वीडियो पुनःकोड करें वीडियो फ़ाइल को निर्दिष्ट वीडियो प्रारूप में पुनःकोडित करता है diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 726d4af8..587b93f3 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -419,5 +419,5 @@ Adatszerző Extractor (YouTube) Nyomja meg a \'Restore\' gombot hogy összeolvassza a lementett adatokat a jelenlegiekkel. \nNyomja meg a \'Reset\' gombot ha csak a lementett adatokat kívánja megtartani. - Hasznos tud lenni olyan Libretube offline playlistekhez, amelyeket a yt-dlp nem ismer fel. Ennek bekapcsolásával a playlistek metadatája nem fog beágyazódni + Ennek bekapcsolásával a playlistek metadatája nem fog beágyazódni \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 19bbff78..8f0178c3 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -413,7 +413,7 @@ Aktifkan ini jika WorkManager dibatasi oleh vendor perangkatmu atau penjadwalan tugas tidak terlalu tepat waktu Tiap hari Gunakan URL Butir, bukannya URL Daftar Putar - Berguna untuk daftar putar luring (offline) LibreTube yang tidak dikenali oleh yt-dlp. Metadata daftar putar tidak akan ditanamkan kalau preferensi ini diaktifkan + Metadata daftar putar tidak akan ditanamkan kalau preferensi ini diaktifkan Mengkodekan ulang file video ke format video yang ditentukan Rekodekan Video \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6e6c430a..035795b5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -415,7 +415,7 @@ Posizione È necessario abilitare l\'autorizzazione SCHEDULE_EXACT_ALARM nelle impostazioni dell\'app. Usa l\'URL dell\'elemento invece dell\'URL della playlist - Utile per le playlist offline di Libretube che non sono riconosciute da yt-dlp. I metadati della playlist non saranno incorporati se questa opzione è abilitata + I metadati della playlist non saranno incorporati se questa opzione è abilitata Ricodifica Video Ricodifica il file video nel formato video specificato "I download sono ancora in fase di caricamento. Continuare a elaborarli in background e iniziare il download? " diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 9cc9cdf1..640e1a2a 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -357,7 +357,7 @@ שמור כתוביות אוטומטיות ‬הפוך את כרטיס ההורדה תמיד להופיע למעלה. הפעל זאת אם האפליקציה הפועלת כעת נסגרת כאשר כרטיס ההורדה מופיע וידאו ללא שמע - שימושי עבור רשימות השמעה לא מקוונות של Libretube שאינן מזוהות על ידי yt-dlp. מטא נתונים של רשימת השמעה לא יוטמעו כאשר זה מופעל + מטא נתונים של רשימת השמעה לא יוטמעו כאשר זה מופעל אל תוריד כפרגמנטים אל תשתמש בקבצי part. ותכתוב ישירות לקובץ הפלט החלק תנועות בכרטיס ההורדה diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 33e3d262..0f6df4ae 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -404,7 +404,7 @@ 時間管理に WorkManager でなく AlarmManager を使う WorkManager が提供者によりに制限されていたり、時間管理されたジョブの精度が良くないなら、これを有効にします キュー - Libretube のオフライン再生リストは yt-dlp が認識できないため有用です。有効にすると、再生リストのメタデータは埋め込まれません + 有効にすると、再生リストのメタデータは埋め込まれません 再生リストのURLの代わりに個々の項目のURLを使用 復元は、保存済みデータと現在のデータを統合します。 \n初期化は、消去しファイル中の保存済みデータのみを使用します。 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 0d493814..d8af0bbb 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -409,7 +409,7 @@ AlarmManager in plaats van WorkManager gebruiken voor planning Schakel dit in als WorkManager beperkt is door de leverancier van uw apparaat of als geplande taken niet al te nauwkeurig zijn Item-URL gebruiken in plaats van afspeel­lijst-URL - Handig voor offline afspeel­lijsten van LibreTube die niet worden herkend door yt-dlp. Meta­gegevens van de afspeel­lijst worden niet ingesloten als dit is ingeschakeld + Meta­gegevens van de afspeel­lijst worden niet ingesloten als dit is ingeschakeld Video hercoderen Hercodeert het video­bestand naar het opgegeven video­formaat Overgebleven downloads opschonen (geannuleerd, foutief) diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index e12229ad..7c2c41d9 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -416,7 +416,7 @@ ਸਮਾਂ-ਤਹਿ ਕਰਨ ਲਈ WorkManager ਦੀ ਬਜਾਏ AlarmManager ਦੀ ਵਰਤੋਂ ਕਰੋ ਇਸ ਨੂੰ ਸਮਰੱਥ ਬਣਾਓ ਜੇਕਰ ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਵਿਕਰੇਤਾ ਦੁਆਰਾ WorkManager ਨੂੰ ਪ੍ਰਤਿਬੰਧਿਤ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ ਸ਼ਡਿਊਲ ਕੀਤੇ ਜੌਬ ਬਹੁਤ ਸਟੀਕ ਨਹੀਂ ਹਨ ਪਲੇਲਿਸਟ URL ਦੀ ਬਜਾਏ ਆਈਟਮ URL ਦੀ ਵਰਤੋਂ ਕਰੋ - Libretube ਆਫਲਾਈਨ ਪਲੇਲਿਸਟਾਂ ਲਈ ਉਪਯੋਗੀ ਜੋ yt-dlp ਦੁਆਰਾ ਮਾਨਤਾ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹਨ। ਇਸਨੂੰ ਸਮਰੱਥ ਕਰਨ ਨਾਲ ਪਲੇਲਿਸਟ ਮੈਟਾਡੇਟਾ ਏਮਬੇਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ + ਦੁਆਰਾ ਮਾਨਤਾ ਪ੍ਰਾਪਤ ਨਹੀਂ ਹਨ। ਇਸਨੂੰ ਸਮਰੱਥ ਕਰਨ ਨਾਲ ਪਲੇਲਿਸਟ ਮੈਟਾਡੇਟਾ ਏਮਬੇਡ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ ਵੀਡੀਓ ਰੀਕੋਡ ਕਰੋ ਵੀਡੀਓ ਫਾਈਲ ਨੂੰ ਨਿਸ਼ਚਿਤ ਵੀਡੀਓ ਫਾਰਮੈਟ ਵਿੱਚ ਰੀਕੋਡ ਕਰਦਾ ਹੈ ਡਾਟਾ ਪ੍ਰਾਪਤੀ ਐਕਸਟਰੈਕਟਰ (ਯੂਟਿਊਬ) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9da3a103..027d66ce 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -416,7 +416,7 @@ Co miesiąc Tak czy inaczej, kontynuuj Użyj adresu URL elementu zamiast adresu URL listy odtwarzania - Przydatne dla list odtwarzania offline Libretube, które nie są rozpoznawane przez yt-dlp. Metadane listy odtwarzania nie zostaną osadzone po włączeniu tej opcji + Metadane listy odtwarzania nie zostaną osadzone po włączeniu tej opcji Przekodowuje plik wideo do określonego formatu wideo Przekoduj Video \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index fb14beb3..f7505ae5 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -409,7 +409,7 @@ Usar \'AlarmManager\' em vez de \'WorkManager\' para agendamento Habilite isto se o \"WorkManager\" for restrito pelo fornecedor do seu dispositivo ou se as tarefas agendadas não forem muito precisas Usar URL do item em vez do URL da playlist - Útil para playlists offline do LibreTube que não são reconhecidas pelo yt-dlp. Os metadados da playlist não serão adicionados com esta opção ativada + Os metadados da playlist não serão adicionados com esta opção ativada Recodifica o arquivo de vídeo para o formato especificado Recodificar vídeo Diariamente diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 07983ecc..6c8897cb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -409,7 +409,7 @@ Используйте AlarmManager вместо WorkManager для планирования Включите это, если WorkManager ограничен поставщиком вашего устройства или запланированные задания не слишком точны Используйте URL-адрес элемента вместо URL-адреса плейлиста - Полезно для автономных плейлистов Libretube, которые не распознаются yt-dlp. Метаданные плейлиста не будут встроены, если эта функция включена + Метаданные плейлиста не будут встроены, если эта функция включена Перекодировать видео Перекодирует видеофайл в указанный видеоформат Ежедневно diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 53409bc6..c0f6aae5 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -408,7 +408,7 @@ Pásmo Použiť AlarmManager namiesto WorkManageru na plánovanie Zapnite túto funkciu, ak je WorkManager obmedzený výrobcom zariadenia alebo ak naplánované úlohy nie sú príliš presné - Užitočné pre offline zoznamy skladieb Libretube, ktoré yt-dlp nerozpoznáva. Metadáta playlistu nebudú vložené, ak je táto funkcia zapnutá + Metadáta playlistu nebudú vložené, ak je táto funkcia zapnutá Použiť URL položky namiesto URL playlistu Prekódovať video Prekóduje videosúbor do určeného formátu videa diff --git a/app/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml index 34a5fd3c..106e276f 100644 --- a/app/src/main/res/values-sq-rAL/strings.xml +++ b/app/src/main/res/values-sq-rAL/strings.xml @@ -407,7 +407,7 @@ Përdor AlarmManager në vend të WorkManager për Skedulim Aktivizo këtë ku WorkManager është i limituar në paisje ose punët e skeduluara nuk ekzekutohen në kohë Përdor URL e videos në vend të URL së playlist - E dobishme për playlistet Libretube qe nuk njihen nga yt-dlp. Metadata e playlist nuk do te shtohen dot kur ky opsion është i aktivizuar + Metadata e playlist nuk do te shtohen dot kur ky opsion është i aktivizuar Rikodo Videon Rikodon skedarin e videos sipas formatit të cilësuar nga përdoruesi Shtypni \"Rivendosni\" për të bashkuar të dhënat e ruajtura me të dhënat tuaja aktuale. diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index c1f397f8..ae4e49fc 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -409,7 +409,7 @@ Користите AlarmManager уместо WorkManager за планирање Омогућите ово ако је WorkManager ограничен од стране продавца уређаја или заказани послови нису превише прецизни Користи URL адресу предмета уместо URL адресе плејлисте - Корисно за Libretube офлајн плејлисте које yt-dlp не препознаје. Метаподаци плејлисте неће бити уграђени, ако је ово омогућено + Метаподаци плејлисте неће бити уграђени, ако је ово омогућено Рекодирај видео снимак Рекодира фајл видео снимка у наведени формат видео снимка Дневно diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 6bf82af6..e857de2b 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -416,7 +416,7 @@ Aylık Artık indirmeleri temizleme (iptal edildi, hatalı) Çalma Listesi URL\'si yerine Öge URL\'sini kullanın - yt-dlp tarafından tanınmayan Libretube çevrimdışı çalma listeleri için kullanışlıdır. Bu etkinleştirildiğinde çalma listesi meta verileri gömülmeyecektir + Bu etkinleştirildiğinde çalma listesi meta verileri gömülmeyecektir Videoyu tekrar kodla Video dosyasını belirtilen video formatına yeniden kodlar Her Neyse Devam Et diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 51f6cbcf..a7494628 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -409,7 +409,7 @@ Використовуйте AlarmManager замість WorkManager для планування Увімкніть це, якщо WorkManager обмежено постачальником вашого пристрою або заплановані завдання не надто точні Використовуйте URL елемента замість URL списку відтворення - Корисно для списків відтворення Libretube без мережі, які не розпізнаються yt-dlp. Метадані списку відтворення не буде вбудовано, якщо увімкнено цей параметр + Метадані списку відтворення не буде вбудовано, якщо увімкнено цей параметр Перекодувати відео Перекодує відеофайл у вказаний відеоформат Щоденно diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index ab294f9b..37879a5f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -416,7 +416,7 @@ Sử dụng AlarmManager thay cho WorkManager trong phần Lên lịch để tải xuống Hãy bật nếu WorkManager bị hạn chế bởi Nhà cung cấp thiết bị hoặc các công việc không được lên lịch chính xác Dùng URL mục thay cho URL danh sách phát - Dùng cho danh sách phát ngoại tuyến Libretube không được yt-dlp nhận dạng. Nếu bật thì metadata của danh sách phát sẽ không được thêm vào tệp + Nếu bật thì metadata của danh sách phát sẽ không được thêm vào tệp Mã hóa tệp video thành định dạng được chỉ định Mã hóa Video \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fb5ee7f2..0da82698 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -409,7 +409,7 @@ 使用闹铃管理器而非工作管理器安排任务 如果工作管理器被设备厂商限制或安排好时间的任务不太精确,请启用这个 使用项目 URL 而非播放列表 URL - 对于未被 yt-dlp 识别的 Libretube 离线播放列表有用。启用此选项后不会内嵌播放列表元数据 + 启用此选项后不会内嵌播放列表元数据 重编码视频文件到指定的视频格式 重新编码视频 每天 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f4da357d..1cfbf2c2 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -416,7 +416,7 @@ 每月 清理殘留的下載(取消或錯誤的下載) 使用項目 URL 而非播放清單 URL - 此功能適用於 Libretube 離線播放清單,這些清單無法被 yt-dlp 認識。啟用此選項後,播放清單的中繼資料將不會嵌入 + 啟用此選項後,播放清單的中繼資料將不會嵌入 重新編碼影片 將影片檔案重新編碼為指定的影片格式 資料擷取提取器(YouTube) diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index e9b408a4..3f943641 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1495,6 +1495,11 @@ + yt-dlp + NewPipe + + + YT_DLP NEWPIPE diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d89f9554..f1e9e325 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -430,4 +430,7 @@ Deleted Websites Other YouTube Extractor Arguments + item(s) + Socket Timeout + Time to wait before giving up, in seconds \ No newline at end of file diff --git a/app/src/main/res/xml/downloading_preferences.xml b/app/src/main/res/xml/downloading_preferences.xml index 64d8792e..667d8291 100644 --- a/app/src/main/res/xml/downloading_preferences.xml +++ b/app/src/main/res/xml/downloading_preferences.xml @@ -128,6 +128,12 @@ app:summary="@string/buffer_size_summary" app:title="@string/buffer_size" /> + +