This commit is contained in:
zaednasr 2024-11-17 18:49:20 +01:00
parent c3aa1f0f2f
commit 7bad5ad587
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
34 changed files with 428 additions and 99 deletions

View file

@ -1,5 +1,28 @@
# YTDLnis Changelog # YTDLnis Changelog
> # 1.8.1 (2024-11)
# What's Changed
- Implemented pagination in the History screen to help with large lists
- Added delete all for each page in the download queue screen
- Added accessing Terminal from the shortcuts menu of the app
- Made the download notifications grouped together
- Fixed app crashing when pressing the log of a download but the log has been deleted
- Fixed app crashing when queueing long list of items in the download queue
- Added ability to mass re-download items from the history screen
- Made the app remember the last used scheduled time so it can suggest you that time for the next download
- #618, made all preferences with a description show their values
- Fixed bug that prevented app from loading all urls from text file
# Advanced Settings
Added a new category for more advanced users and moved the extractor argument settings there.
- Added ability to make command templates usable while fetching data in the home screen. They will be appended to the data fetching command as an extra command in the end. Enable the toggle in the advanced settings to be able to configure your templates for it
- When PO Token is set, the app now adds the web extractor argument for youtube. I forgor...
(If you want to set more PO tokens for other player clients i guess set them in the other extractor argument text preference, and also modify the player client. The separate PO Token preference applies it only for the web player client)
> # 1.8.0 (2024-10) > # 1.8.0 (2024-10)
# Notice # Notice

View file

@ -10,7 +10,7 @@ plugins {
def properties = new Properties() def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 8 def versionMinor = 8
def versionPatch = 0 def versionPatch = 1
def versionBuild = 0 // bump for dogfood builds, public betas, etc. def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def isBeta = false def isBeta = false

View file

@ -35,6 +35,10 @@ class HistoryRepository(private val historyDao: HistoryDao) {
return historyDao.getAllHistoryByURL(url) return historyDao.getAllHistoryByURL(url)
} }
fun getAllByIDs(ids: List<Long>) : List<HistoryItem> {
return historyDao.getAllHistoryByIDs(ids)
}
data class HistoryIDsAndPaths( data class HistoryIDsAndPaths(
val id: Long, val id: Long,
val downloadPath: List<String> val downloadPath: List<String>

View file

@ -225,6 +225,14 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
ytExtractorResult.getOrElse { items } ytExtractorResult.getOrElse { items }
}else{ }else{
val res = ytdlpUtil.getFromYTDL(url) val res = ytdlpUtil.getFromYTDL(url)
//TODO REMOVE THIS WHEN YT-DLP FIXES ISSUE #10827
res.forEach {
it.apply {
playlistTitle = ""
playlistURL = ""
playlistIndex = 0
}
}
val ids = resultDao.insertMultiple(res) val ids = resultDao.insertMultiple(res)
ids.forEachIndexed { index, id -> ids.forEachIndexed { index, id ->
res[index].id = id res[index].id = id

View file

@ -616,6 +616,39 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
processingItemsJob = job processingItemsJob = job
} }
fun turnHistoryItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
val job = viewModelScope.launch(Dispatchers.IO) {
repository.deleteProcessing()
processingItems.emit(true)
try {
val toInsert = mutableListOf<DownloadItem>()
itemIDs.forEach {
val item = historyRepository.getItem(it)
val downloadItem = createDownloadItemFromHistory(item)
downloadItem.status = DownloadRepository.Status.Processing.toString()
if (processingItemsJob?.isCancelled == true) {
throw CancellationException()
}
if (downloadNow) {
downloadItem.status = DownloadRepository.Status.Queued.toString()
queueDownloads(listOf(downloadItem))
}else{
toInsert.add(downloadItem)
//repository.insert(downloadItem)
}
}
repository.insertAll(toInsert)
processingItems.emit(false)
} catch (e: Exception) {
deleteProcessing()
processingItems.emit(false)
}
}
processingItemsJob = job
}
fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) { fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
val job = viewModelScope.launch(Dispatchers.IO) { val job = viewModelScope.launch(Dispatchers.IO) {

View file

@ -146,7 +146,11 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
val ids = repository.getFilteredIDs(queryFilter.value, typeFilter.value, websiteFilter.value, sortType.value, sortOrder.value, statusFilter.value) val ids = repository.getFilteredIDs(queryFilter.value, typeFilter.value, websiteFilter.value, sortType.value, sortOrder.value, statusFilter.value)
val firstIndex = ids.indexOf(firstID) val firstIndex = ids.indexOf(firstID)
val secondIndex = ids.indexOf(secondID) val secondIndex = ids.indexOf(secondID)
return ids.filterIndexed {index, _ -> index in (firstIndex + 1) until secondIndex } return if (firstIndex > secondIndex) {
ids.filterIndexed {index, _ -> index < firstIndex && index > secondIndex }
}else {
ids.filterIndexed {index, _ -> index > firstIndex && index < secondIndex }
}
} }
fun getItemIDsNotPresentIn(not: List<Long>) : List<Long> { fun getItemIDsNotPresentIn(not: List<Long>) : List<Long> {

View file

@ -14,28 +14,28 @@ import kotlinx.coroutines.withContext
class PauseDownloadNotificationReceiver : BroadcastReceiver() { class PauseDownloadNotificationReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) { override fun onReceive(c: Context, intent: Intent) {
// val result = goAsync() val result = goAsync()
// val id = intent.getIntExtra("itemID", 0) val id = intent.getIntExtra("itemID", 0)
// if (id != 0) { if (id != 0) {
// runCatching { runCatching {
// val title = intent.getStringExtra("title") val title = intent.getStringExtra("title")
// val notificationUtil = NotificationUtil(c) val notificationUtil = NotificationUtil(c)
// notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
// YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
// val dbManager = DBManager.getInstance(c) val dbManager = DBManager.getInstance(c)
// CoroutineScope(Dispatchers.IO).launch{ CoroutineScope(Dispatchers.IO).launch{
// try { try {
// val item = dbManager.downloadDao.getDownloadById(id.toLong()) val item = dbManager.downloadDao.getDownloadById(id.toLong())
// item.status = DownloadRepository.Status.ActivePaused.toString() item.status = DownloadRepository.Status.Paused.toString()
// dbManager.downloadDao.update(item) dbManager.downloadDao.update(item)
// }finally { }finally {
// withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
// notificationUtil.createResumeDownload(id, title) notificationUtil.createResumeDownload(id, title)
// result.finish() result.finish()
// } }
// } }
// } }
// } }
// } }
} }
} }

View file

@ -3,6 +3,7 @@ package com.deniscerri.ytdl.ui.downloads
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.SharedPreferences
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
@ -85,6 +86,7 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
private lateinit var uiHandler: Handler private lateinit var uiHandler: Handler
private lateinit var noResults: RelativeLayout private lateinit var noResults: RelativeLayout
private lateinit var selectionChips: LinearLayout private lateinit var selectionChips: LinearLayout
private lateinit var sharedPreferences: SharedPreferences
private var websiteList: MutableList<String> = mutableListOf() private var websiteList: MutableList<String> = mutableListOf()
private var totalCount = 0 private var totalCount = 0
private var actionMode : ActionMode? = null private var actionMode : ActionMode? = null
@ -104,6 +106,7 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
fragmentContext = context fragmentContext = context
layoutinflater = LayoutInflater.from(context) layoutinflater = LayoutInflater.from(context)
topAppBar = view.findViewById(R.id.history_toolbar) topAppBar = view.findViewById(R.id.history_toolbar)
@ -629,6 +632,31 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
} }
true true
} }
R.id.redownload -> {
lifecycleScope.launch {
val selectedObjects = getSelectedIDs()
historyAdapter.clearCheckedItems()
actionMode?.finish()
if (selectedObjects.size == 1) {
val tmp = withContext(Dispatchers.IO) {
historyViewModel.getByID(selectedObjects.first())
}
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
Pair("result", downloadViewModel.createResultItemFromHistory(tmp)),
Pair("type", tmp.type)
))
}else {
val showDownloadCard = sharedPreferences.getBoolean("download_card", true)
downloadViewModel.turnHistoryItemsToProcessingDownloads(selectedObjects, downloadNow = !showDownloadCard)
actionMode?.finish()
if (showDownloadCard){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
}
}
}
true
}
R.id.select_all -> { R.id.select_all -> {
historyAdapter.checkAll() historyAdapter.checkAll()
val selectedCount = historyAdapter.getSelectedObjectsCount(totalCount) val selectedCount = historyAdapter.getSelectedObjectsCount(totalCount)

View file

@ -8,6 +8,7 @@ import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.DragInteraction
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -176,6 +177,92 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
} }
true true
} }
findPreference<EditTextPreference>("proxy")?.apply {
val s = getString(R.string.socks5_proxy_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
findPreference<ListPreference>("preferred_download_type")?.apply {
val s = getString(R.string.preferred_download_type_summary)
summary = if (value.isNullOrBlank()) {
s
}else {
"${s}\n[${entries[entryValues.indexOf(value)]}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${entries[entryValues.indexOf(newValue)]}]"
}
true
}
}
findPreference<EditTextPreference>("limit_rate")?.apply {
val s = getString(R.string.limit_rate_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
findPreference<EditTextPreference>("buffer_size")?.apply {
val s = getString(R.string.limit_rate_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
findPreference<EditTextPreference>("socket_timeout")?.apply {
val s = getString(R.string.limit_rate_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
} }
private var archivePathResultLauncher = registerForActivityResult( private var archivePathResultLauncher = registerForActivityResult(

View file

@ -18,9 +18,13 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.core.text.HtmlCompat
import androidx.core.text.parseAsHtml
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
@ -35,6 +39,7 @@ import com.deniscerri.ytdl.databinding.NavOptionsItemBinding
import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter
import com.deniscerri.ytdl.util.NavbarUtil import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.ThemeUtil import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.ThemeUtil.getThemeColor
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.UpdateUtil import com.deniscerri.ytdl.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -43,15 +48,6 @@ import java.util.Locale
class GeneralSettingsFragment : BaseSettingsFragment() { class GeneralSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.general override val title: Int = R.string.general
private var language: ListPreference? = null
private var theme: ListPreference? = null
private var accent: ListPreference? = null
private var highContrast: SwitchPreferenceCompat? = null
private var locale: ListPreference? = null
private var showTerminalShareIcon: SwitchPreferenceCompat? = null
private var ignoreBatteryOptimization: Preference? = null
private var displayOverApps: SwitchPreferenceCompat? = null
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
private var updateUtil: UpdateUtil? = null private var updateUtil: UpdateUtil? = null
@ -71,20 +67,16 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
} }
} }
language = findPreference("app_language") findPreference<ListPreference>("app_language")?.apply {
theme = findPreference("ytdlnis_theme") if (value == null) {
accent = findPreference("theme_accent") value = Locale.getDefault().language
highContrast = findPreference("high_contrast") summary = Locale.getDefault().displayLanguage
locale = findPreference("locale") }
showTerminalShareIcon = findPreference("show_terminal") setOnPreferenceChangeListener { _, newValue ->
if(language!!.value == null) language!!.value = Locale.getDefault().language
language!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString())) AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
true true
} }
}
findPreference<Preference>("label_visibility")?.apply { findPreference<Preference>("label_visibility")?.apply {
isVisible = !resources.getBoolean(R.bool.uses_side_nav) isVisible = !resources.getBoolean(R.bool.uses_side_nav)
@ -96,6 +88,9 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
findPreference<Preference>("navigation_bar")?.apply { findPreference<Preference>("navigation_bar")?.apply {
isVisible = !resources.getBoolean(R.bool.uses_side_nav) isVisible = !resources.getBoolean(R.bool.uses_side_nav)
if (isVisible) {
summary = NavbarUtil.getNavBarItems(requireContext()).filter { it.isVisible }.map { it.title }.joinToString(", ")
}
setOnPreferenceClickListener { setOnPreferenceClickListener {
val binding = requireActivity().layoutInflater.inflate(R.layout.simple_options_recycler, null) val binding = requireActivity().layoutInflater.inflate(R.layout.simple_options_recycler, null)
val options = NavbarUtil.getNavBarItems(requireContext()) val options = NavbarUtil.getNavBarItems(requireContext())
@ -162,6 +157,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
NavbarUtil.setNavBarItems(adapter.items, requireContext()) NavbarUtil.setNavBarItems(adapter.items, requireContext())
NavbarUtil.setStartFragment(adapter.selectedHomeTabId) NavbarUtil.setStartFragment(adapter.selectedHomeTabId)
summary = adapter.items.filter { it.isVisible }.map { it.title }.joinToString(", ")
ThemeUtil.recreateMain() ThemeUtil.recreateMain()
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -170,38 +166,44 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
} }
} }
findPreference<ListPreference>("ytdlnis_theme")?.apply {
theme!!.summary = theme!!.entry summary = entry
theme!!.onPreferenceChangeListener = setOnPreferenceChangeListener { _, newValue ->
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> summary = when(newValue){
when(newValue){
"System" -> { "System" -> {
theme!!.summary = getString(R.string.system) getString(R.string.system)
} }
"Dark" -> { "Dark" -> {
theme!!.summary = getString(R.string.dark) getString(R.string.dark)
} }
else -> { else -> {
theme!!.summary = getString(R.string.light) getString(R.string.light)
} }
} }
ThemeUtil.updateThemes() ThemeUtil.updateThemes()
true true
} }
accent!!.summary = accent!!.entry }
accent!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateThemes()
true
}
highContrast!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateThemes()
true
}
showTerminalShareIcon!!.onPreferenceChangeListener = findPreference<ListPreference>("theme_accent")?.apply {
Preference.OnPreferenceChangeListener { pref: Preference?, _: Any -> summary = entry
setOnPreferenceChangeListener { _, _ ->
ThemeUtil.updateThemes()
true
}
}
findPreference<SwitchPreferenceCompat>("high_contrast")?.apply {
setOnPreferenceChangeListener { _, _ ->
ThemeUtil.updateThemes()
true
}
}
findPreference<SwitchPreferenceCompat>("show_terminal")?.apply {
setOnPreferenceChangeListener { pref, _ ->
val packageManager = requireContext().packageManager val packageManager = requireContext().packageManager
val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdl.terminalShareAlias") val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdl.terminalShareAlias")
if ((pref as SwitchPreferenceCompat).isChecked){ if ((pref as SwitchPreferenceCompat).isChecked){
@ -215,11 +217,11 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
} }
true true
} }
}
displayOverApps = findPreference("display_over_apps") findPreference<SwitchPreferenceCompat>("display_over_apps")?.apply {
displayOverApps?.isChecked = Settings.canDrawOverlays(requireContext()) isChecked = Settings.canDrawOverlays(requireContext())
displayOverApps?.onPreferenceClickListener = setOnPreferenceChangeListener { _, _ ->
Preference.OnPreferenceClickListener {
runCatching { runCatching {
val i = Intent( val i = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
@ -231,16 +233,17 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
} }
true true
} }
}
ignoreBatteryOptimization = findPreference("ignore_battery") findPreference<Preference>("ignore_battery")?.apply {
ignoreBatteryOptimization!!.onPreferenceClickListener = setOnPreferenceClickListener {
Preference.OnPreferenceClickListener {
val intent = Intent() val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + requireContext().packageName) intent.data = Uri.parse("package:" + requireContext().packageName)
startActivity(intent) startActivity(intent)
true true
} }
}
findPreference<Preference>("piped_instance")?.setOnPreferenceClickListener { findPreference<Preference>("piped_instance")?.setOnPreferenceClickListener {
UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){ UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){
@ -249,13 +252,87 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
} }
true true
} }
findPreference<MultiSelectListPreference>("hide_thumbnails")?.apply {
summary = values.joinToString(", ") { entries[entryValues.indexOf(it)] }
setOnPreferenceChangeListener { _, newValues ->
summary = (newValues as Set<*>).joinToString(", ") { entries[entryValues.indexOf(it)] }
true
}
}
findPreference<MultiSelectListPreference>("modify_download_card")?.apply {
summary = values.joinToString(", ") { entries[entryValues.indexOf(it)] }
setOnPreferenceChangeListener { _, newValues ->
summary = (newValues as Set<*>).joinToString(", ") { entries[entryValues.indexOf(it)] }
true
}
}
findPreference<EditTextPreference>("api_key")?.apply {
val s = getString(R.string.api_key_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
findPreference<ListPreference>("search_engine")?.apply {
val s = getString(R.string.preferred_search_engine_summary)
summary = if (value.isNullOrBlank()) {
s
}else {
"${s}\n[${entries[entryValues.indexOf(value)]}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${entries[entryValues.indexOf(newValue)]}]"
}
true
}
}
findPreference<MultiSelectListPreference>("swipe_gesture")?.apply {
val s = getString(R.string.preferred_search_engine_summary)
if (values.size == entries.size) {
summary = "${s}\n[${getString(R.string.all)}]"
}else if (values.size > 0) {
val indexes = entryValues.mapIndexed { index, _ -> index }
summary = "${s}\n[${entries.filterIndexed { index, _ -> indexes.contains(index) }.joinToString(", ")}]"
}else{
summary = s
}
setOnPreferenceChangeListener { _, newValue ->
val newValues = newValue as Set<*>
if (newValues.size == entries.size) {
summary = "${s}\n[${getString(R.string.all)}]"
}else if (newValues.isNotEmpty()) {
val indexes = newValues.mapIndexed { index, _ -> index }
summary = "${s}\n[${entries.filterIndexed { index, _ -> indexes.contains(index) }.joinToString(", ")}]"
}else{
summary = s
}
true
}
}
} }
override fun onResume() { override fun onResume() {
val packageName: String = requireContext().packageName val packageName: String = requireContext().packageName
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) { if (pm.isIgnoringBatteryOptimizations(packageName)) {
ignoreBatteryOptimization!!.isVisible = false findPreference<Preference>("ignore_battery")?.isVisible = false
} }
super.onResume() super.onResume()
} }

View file

@ -91,6 +91,40 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
true true
} }
} }
findPreference<EditTextPreference>("format_id")?.apply {
val s = getString(R.string.preferred_format_id_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
findPreference<EditTextPreference>("format_id_audio")?.apply {
val s = getString(R.string.preferred_format_id_summary)
summary = if (text.isNullOrBlank()) {
s
}else {
"${s}\n[${text}]"
}
setOnPreferenceChangeListener { _, newValue ->
summary = if ((newValue as String?).isNullOrBlank()) {
s
}else {
"${s}\n[${newValue}]"
}
true
}
}
} }

View file

@ -120,6 +120,7 @@ class NotificationUtil(var context: Context) {
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setGroup(DOWNLOAD_RUNNING_NOTIFICATION_ID.toString()) .setGroup(DOWNLOAD_RUNNING_NOTIFICATION_ID.toString())
.setGroupSummary(true)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.clearActions() .clearActions()
.build() .build()
@ -433,7 +434,7 @@ class NotificationUtil(var context: Context) {
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText)) .setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
.setGroup(DOWNLOAD_RUNNING_NOTIFICATION_ID.toString()) .setGroup(DOWNLOAD_RUNNING_NOTIFICATION_ID.toString())
.clearActions() .clearActions()
//.addAction(0, resources.getString(R.string.pause), pauseNotificationPendingIntent) .addAction(0, resources.getString(R.string.pause), pauseNotificationPendingIntent)
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent) .addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
notificationManager.notify(id, notificationBuilder.build()) notificationManager.notify(id, notificationBuilder.build())
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -164,7 +164,7 @@ object ThemeUtil {
} }
private fun getThemeColor(context: Context, colorCode: Int): Int { fun getThemeColor(context: Context, colorCode: Int): Int {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val accent = sharedPreferences.getString("theme_accent", "blue") val accent = sharedPreferences.getString("theme_accent", "blue")
return if (accent == "blue"){ return if (accent == "blue"){

View file

@ -196,6 +196,8 @@ object UiUtil {
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!! val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!! val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
extraCommandsDataFetchingSwitch.isVisible = sharedPreferences.getBoolean("enable_data_fetching_extra_commands", false)
if (item != null){ if (item != null){
title.editText!!.setText(item.title) title.editText!!.setText(item.title)
content.editText!!.setText(item.content) content.editText!!.setText(item.content)
@ -254,7 +256,7 @@ object UiUtil {
if (item != null){ if (item != null){
preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "") preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "")
extraCommandsDataFetchingSwitch.isChecked = item.useAsExtraCommandDataFetching extraCommandsDataFetchingSwitch.isChecked = item.useAsExtraCommandDataFetching && extraCommandsDataFetchingSwitch.isVisible
extraCommandsSwitch.isChecked = item.useAsExtraCommand extraCommandsSwitch.isChecked = item.useAsExtraCommand
if (item.useAsExtraCommand){ if (item.useAsExtraCommand){

View file

@ -1126,15 +1126,22 @@ class YTDLPUtil(private val context: Context) {
} }
private fun getYoutubeExtractorArgs() : String { private fun getYoutubeExtractorArgs() : String {
val playerClient = sharedPreferences.getString("youtube_player_client", "default,mediaconnect")!! val playerClient = sharedPreferences.getString("youtube_player_client", "default,mediaconnect")!!.split(",").filter { it.isNotBlank() }.toMutableList()
.ifEmpty { "default,mediaconnect" }
val extractorArgs = mutableListOf<String>() val extractorArgs = mutableListOf<String>()
extractorArgs.add("player_client=${playerClient}")
val poToken = sharedPreferences.getString("youtube_po_token", "")!!
if (poToken.isNotBlank() && !playerClient.contains("web")) {
playerClient.add("web")
}
if (playerClient.isNotEmpty()){
extractorArgs.add("player_client=${playerClient.joinToString(",")}")
}
val lang = Locale.getDefault().language val lang = Locale.getDefault().language
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) { if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs.add("lang=$lang") extractorArgs.add("lang=$lang")
} }
val poToken = sharedPreferences.getString("youtube_po_token", "")!!
if (poToken.isNotBlank()) { if (poToken.isNotBlank()) {
extractorArgs.add("po_token=web+$poToken") extractorArgs.add("po_token=web+$poToken")
} }

View file

@ -171,9 +171,6 @@
android:textSize="15sp" android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<View
style="@style/Divider.Horizontal"
android:layout_marginVertical="10dp" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -22,6 +22,12 @@
app:showAsAction="ifRoom" app:showAsAction="ifRoom"
android:title="@string/share" /> android:title="@string/share" />
<item
android:id="@+id/redownload"
android:icon="@drawable/baseline_share_24"
app:showAsAction="ifRoom"
android:title="@string/redownload" />
<item <item
android:id="@+id/select_all" android:id="@+id/select_all"
android:title="@string/select_all" android:title="@string/select_all"

View file

@ -423,7 +423,7 @@
<string name="items">أغراض/غرض</string> <string name="items">أغراض/غرض</string>
<string name="socket_timeout_description">وقت الانتظار قبل الاستسلام، في ثوانٍ</string> <string name="socket_timeout_description">وقت الانتظار قبل الاستسلام، في ثوانٍ</string>
<string name="advanced">متقدم</string> <string name="advanced">متقدم</string>
<string name="data_fetching_extra_commands">أوامر إضافية لجلب البيانات</string> <string name="data_fetching_extra_command">أوامر إضافية لجلب البيانات</string>
<string name="thumbnail">الصورة المصغرة</string> <string name="thumbnail">الصورة المصغرة</string>
<string name="package_name">اسم الحزمة</string> <string name="package_name">اسم الحزمة</string>
<string name="websites">مواقع الويب</string> <string name="websites">مواقع الويب</string>

View file

@ -422,7 +422,7 @@
<string name="data_fetching_extractor_youtube">Məlumat Çıxarıcı (YouTube)</string> <string name="data_fetching_extractor_youtube">Məlumat Çıxarıcı (YouTube)</string>
<string name="items">element</string> <string name="items">element</string>
<string name="socket_timeout">Uzun Vaxt Çıxışı</string> <string name="socket_timeout">Uzun Vaxt Çıxışı</string>
<string name="data_fetching_extra_commands">Məlumat Alınması Əlavə Əmrləri</string> <string name="data_fetching_extra_command">Məlumat Alınması Əlavə Əmrləri</string>
<string name="thumbnail">Miniatür</string> <string name="thumbnail">Miniatür</string>
<string name="other_youtube_extractor_args">Digər YouTube Extractor Hissəcikləri</string> <string name="other_youtube_extractor_args">Digər YouTube Extractor Hissəcikləri</string>
<string name="socket_timeout_description">Bitirməzdən əvvəl saniyə ilə gözləmək vaxtı</string> <string name="socket_timeout_description">Bitirməzdən əvvəl saniyə ilə gözləmək vaxtı</string>

View file

@ -433,5 +433,5 @@
<string name="status">Stav</string> <string name="status">Stav</string>
<string name="deleted">Odstraněno</string> <string name="deleted">Odstraněno</string>
<string name="websites">Webové stránky</string> <string name="websites">Webové stránky</string>
<string name="data_fetching_extra_commands">Doplňkové příkazy pro načítání dat</string> <string name="data_fetching_extra_command">Doplňkové příkazy pro načítání dat</string>
</resources> </resources>

View file

@ -423,7 +423,7 @@
<string name="socket_timeout">Socket-Timeout</string> <string name="socket_timeout">Socket-Timeout</string>
<string name="socket_timeout_description">Wartezeit vor dem Aufgeben in Sekunden</string> <string name="socket_timeout_description">Wartezeit vor dem Aufgeben in Sekunden</string>
<string name="advanced">Erweitert</string> <string name="advanced">Erweitert</string>
<string name="data_fetching_extra_commands">Zusätzliche Befehle zum Abrufen von Daten</string> <string name="data_fetching_extra_command">Zusätzliche Befehle zum Abrufen von Daten</string>
<string name="items">Element(e)</string> <string name="items">Element(e)</string>
<string name="thumbnail">Vorschaubild</string> <string name="thumbnail">Vorschaubild</string>
</resources> </resources>

View file

@ -425,7 +425,7 @@
<string name="advanced">Avanzado</string> <string name="advanced">Avanzado</string>
<string name="other_youtube_extractor_args">Otros argumentos del extractor de YouTube</string> <string name="other_youtube_extractor_args">Otros argumentos del extractor de YouTube</string>
<string name="socket_timeout_description">Tiempo de espera antes de abandonar, en segundos</string> <string name="socket_timeout_description">Tiempo de espera antes de abandonar, en segundos</string>
<string name="data_fetching_extra_commands">Comandos adicionales para la obtención de datos</string> <string name="data_fetching_extra_command">Comandos adicionales para la obtención de datos</string>
<string name="thumbnail">Miniaturas</string> <string name="thumbnail">Miniaturas</string>
<string name="pause_all">Pausar todo</string> <string name="pause_all">Pausar todo</string>
<string name="resume_all">Reanudar todo</string> <string name="resume_all">Reanudar todo</string>

View file

@ -421,7 +421,7 @@
<string name="recode_video">Przekoduj Video</string> <string name="recode_video">Przekoduj Video</string>
<string name="socket_timeout_description">Czas oczekiwania przed rezygnacją w sekundach</string> <string name="socket_timeout_description">Czas oczekiwania przed rezygnacją w sekundach</string>
<string name="advanced">Zaawansowane</string> <string name="advanced">Zaawansowane</string>
<string name="data_fetching_extra_commands">Dodatkowe polecenia pobierania danych</string> <string name="data_fetching_extra_command">Dodatkowe polecenia pobierania danych</string>
<string name="thumbnail">Miniatura</string> <string name="thumbnail">Miniatura</string>
<string name="items">element(y)</string> <string name="items">element(y)</string>
<string name="pause_all">Zatrzymaj wszystko</string> <string name="pause_all">Zatrzymaj wszystko</string>

View file

@ -422,7 +422,7 @@
<string name="data_fetching_extractor_youtube">Extrator para busca de dados (YouTube)</string> <string name="data_fetching_extractor_youtube">Extrator para busca de dados (YouTube)</string>
<string name="socket_timeout_description">Tempo de esperar antes de desistir, em segundos</string> <string name="socket_timeout_description">Tempo de esperar antes de desistir, em segundos</string>
<string name="advanced">Avançado</string> <string name="advanced">Avançado</string>
<string name="data_fetching_extra_commands">Comandos extras para busca de dados</string> <string name="data_fetching_extra_command">Comandos extras para busca de dados</string>
<string name="other_youtube_extractor_args">Outros argumentos do YouTube Extractor</string> <string name="other_youtube_extractor_args">Outros argumentos do YouTube Extractor</string>
<string name="items">item(s)</string> <string name="items">item(s)</string>
<string name="socket_timeout">Tempo limite de Socket</string> <string name="socket_timeout">Tempo limite de Socket</string>

View file

@ -426,7 +426,7 @@
<string name="socket_timeout_description">Время подождать, прежде чем сдаться, в секундах</string> <string name="socket_timeout_description">Время подождать, прежде чем сдаться, в секундах</string>
<string name="other_youtube_extractor_args">Другие аргументы в пользу экстрактора YouTube</string> <string name="other_youtube_extractor_args">Другие аргументы в пользу экстрактора YouTube</string>
<string name="socket_timeout">Тайм-аут сокета</string> <string name="socket_timeout">Тайм-аут сокета</string>
<string name="data_fetching_extra_commands">Дополнительные команды получения данных</string> <string name="data_fetching_extra_command">Дополнительные команды получения данных</string>
<string name="pause_all">Приостановить все</string> <string name="pause_all">Приостановить все</string>
<string name="resume_all">Возобновить все</string> <string name="resume_all">Возобновить все</string>
<string name="package_name">Имя пакета</string> <string name="package_name">Имя пакета</string>

View file

@ -433,5 +433,5 @@
<string name="deleted">Odstránené</string> <string name="deleted">Odstránené</string>
<string name="websites">Webové stránky</string> <string name="websites">Webové stránky</string>
<string name="thumbnail">Miniatúra</string> <string name="thumbnail">Miniatúra</string>
<string name="data_fetching_extra_commands">Doplnkové príkazy na načítanie údajov</string> <string name="data_fetching_extra_command">Doplnkové príkazy na načítanie údajov</string>
</resources> </resources>

View file

@ -432,6 +432,6 @@
<string name="advanced">Avancuar</string> <string name="advanced">Avancuar</string>
<string name="socket_timeout">Timeout Socket</string> <string name="socket_timeout">Timeout Socket</string>
<string name="socket_timeout_description">Koha për të pritur përpara se të dorëzohet, në sekonda</string> <string name="socket_timeout_description">Koha për të pritur përpara se të dorëzohet, në sekonda</string>
<string name="data_fetching_extra_commands">Komandat Ekstra per marrjen e të dhënave</string> <string name="data_fetching_extra_command">Komandat Ekstra per marrjen e të dhënave</string>
<string name="preferred_command_template">Modeli i komandës së preferuar</string> <string name="preferred_command_template">Modeli i komandës së preferuar</string>
</resources> </resources>

View file

@ -422,7 +422,7 @@
<string name="data_fetching_extractor_youtube">Екстрактор прикупљања података (YouTube)</string> <string name="data_fetching_extractor_youtube">Екстрактор прикупљања података (YouTube)</string>
<string name="socket_timeout">Временско ограничење сокета</string> <string name="socket_timeout">Временско ограничење сокета</string>
<string name="advanced">Напредно</string> <string name="advanced">Напредно</string>
<string name="data_fetching_extra_commands">Додатне команде за прикупљање података</string> <string name="data_fetching_extra_command">Додатне команде за прикупљање података</string>
<string name="thumbnail">Сличица</string> <string name="thumbnail">Сличица</string>
<string name="preferred_command_template">Преферирани командни шаблон</string> <string name="preferred_command_template">Преферирани командни шаблон</string>
<string name="other_youtube_extractor_args">Остали YouTube Extractor аргументи</string> <string name="other_youtube_extractor_args">Остали YouTube Extractor аргументи</string>

View file

@ -419,7 +419,7 @@
<string name="recode_video">வீடியோவை மறுபரிசீலனை செய்யுங்கள்</string> <string name="recode_video">வீடியோவை மறுபரிசீலனை செய்யுங்கள்</string>
<string name="websites">வலைத்தளங்கள்</string> <string name="websites">வலைத்தளங்கள்</string>
<string name="advanced">மேம்பட்ட</string> <string name="advanced">மேம்பட்ட</string>
<string name="data_fetching_extra_commands">கூடுதல் கட்டளைகளைப் பெறும் தரவு</string> <string name="data_fetching_extra_command">கூடுதல் கட்டளைகளைப் பெறும் தரவு</string>
<string name="thumbnail">சிறுபடம்</string> <string name="thumbnail">சிறுபடம்</string>
<string name="items">உருப்படி (கள்)</string> <string name="items">உருப்படி (கள்)</string>
<string name="socket_timeout">சாக்கெட் நேரம் முடிந்தது</string> <string name="socket_timeout">சாக்கெட் நேரம் முடிந்தது</string>

View file

@ -426,7 +426,7 @@
<string name="socket_timeout">Тайм-аут сокету</string> <string name="socket_timeout">Тайм-аут сокету</string>
<string name="advanced">Додатково</string> <string name="advanced">Додатково</string>
<string name="thumbnail">Мініатюра</string> <string name="thumbnail">Мініатюра</string>
<string name="data_fetching_extra_commands">Додаткові команди отримання даних</string> <string name="data_fetching_extra_command">Додаткові команди отримання даних</string>
<string name="preferred_command_template">Переважний шаблон команди</string> <string name="preferred_command_template">Переважний шаблон команди</string>
<string name="status">Статус</string> <string name="status">Статус</string>
<string name="pause_all">Зупинити все</string> <string name="pause_all">Зупинити все</string>

View file

@ -419,7 +419,7 @@
<string name="playlist_as_album">将播放列表名用作专辑元数据</string> <string name="playlist_as_album">将播放列表名用作专辑元数据</string>
<string name="playlist_as_album_summary">如果专辑元数据不存在,使用播放列表名</string> <string name="playlist_as_album_summary">如果专辑元数据不存在,使用播放列表名</string>
<string name="data_fetching_extractor_youtube">数据获取提取工具YouTube</string> <string name="data_fetching_extractor_youtube">数据获取提取工具YouTube</string>
<string name="data_fetching_extra_commands">数据获取的额外命令</string> <string name="data_fetching_extra_command">数据获取的额外命令</string>
<string name="thumbnail">缩略图</string> <string name="thumbnail">缩略图</string>
<string name="items">项目</string> <string name="items">项目</string>
<string name="other_youtube_extractor_args">其他 YouTube 提取器变量</string> <string name="other_youtube_extractor_args">其他 YouTube 提取器变量</string>

View file

@ -436,4 +436,5 @@
<string name="advanced">Advanced</string> <string name="advanced">Advanced</string>
<string name="data_fetching_extra_command">Data Fetching Extra Command</string> <string name="data_fetching_extra_command">Data Fetching Extra Command</string>
<string name="thumbnail">Thumbnail</string> <string name="thumbnail">Thumbnail</string>
<string name="data_fetching_extra_command_summary">Enable command templates to be used for data fetching as extra commands</string>
</resources> </resources>

View file

@ -15,7 +15,7 @@
android:icon="@drawable/ic_code" android:icon="@drawable/ic_code"
android:key="youtube_po_token" android:key="youtube_po_token"
app:useSimpleSummaryProvider="true" app:useSimpleSummaryProvider="true"
android:title="PO Token" /> android:title="PO Token [Web]" />
<EditTextPreference <EditTextPreference
android:defaultValue="" android:defaultValue=""
@ -25,4 +25,12 @@
android:title="@string/other_youtube_extractor_args" /> android:title="@string/other_youtube_extractor_args" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/command_templates">
<SwitchPreferenceCompat
android:icon="@drawable/ic_terminal"
android:key="enable_data_fetching_extra_commands"
android:summary="@string/data_fetching_extra_command_summary"
android:title="@string/data_fetching_extra_command" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -0,0 +1,9 @@
# What's Changed
- Download History Pagination
- Delete All option for each screen in download queue
- Added Terminal Shortcut
- Grouped Download Notifications
- Advanced Settings
- Crash Fixes
- Read the Github changelog for more info