From da276e451804954f5e8d191ab683f1892e8abad1 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sat, 28 Jun 2025 20:51:31 +0200 Subject: [PATCH] more fixes --- app/build.gradle | 4 +- .../ytdl/database/models/AudioPreferences.kt | 3 +- .../database/viewmodel/DownloadViewModel.kt | 14 +++--- .../ui/downloadcard/DownloadAudioFragment.kt | 3 ++ .../downloadcard/DownloadBottomSheetDialog.kt | 10 +++-- .../DownloadMultipleBottomSheetDialog.kt | 15 ++++++- .../ytdl/ui/downloads/HistoryFragment.kt | 10 +++-- .../settings/ProcessingSettingsFragment.kt | 26 +++++++++++ .../com/deniscerri/ytdl/util/Extensions.kt | 2 +- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 44 +++++++++++++++++++ .../ytdl/util/extractors/YTDLPUtil.kt | 12 ++--- .../res/drawable/baseline_high_quality_24.xml | 5 +++ app/src/main/res/layout/adjust_audio.xml | 15 +++++++ .../fragment_generate_youtube_po_token.xml | 2 +- ...tube_player_client_create_bottom_sheet.xml | 30 ++++++++++--- app/src/main/res/values/arrays.xml | 16 +++++++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/advanced_preferences.xml | 2 +- .../main/res/xml/processing_preferences.xml | 24 +++------- 19 files changed, 190 insertions(+), 49 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_high_quality_24.xml diff --git a/app/build.gradle b/app/build.gradle index 663980e3..1c2acdfa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ plugins { def properties = new Properties() def versionMajor = 1 def versionMinor = 8 -def versionPatch = 5 -def versionBuild = 0 // bump for dogfood builds, public betas, etc. +def versionPatch = 4 +def versionBuild = 2 // bump for dogfood builds, public betas, etc. def isBeta = true def versionExt = "" diff --git a/app/src/main/java/com/deniscerri/ytdl/database/models/AudioPreferences.kt b/app/src/main/java/com/deniscerri/ytdl/database/models/AudioPreferences.kt index 2331bd57..57ce7e4d 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/models/AudioPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/models/AudioPreferences.kt @@ -8,5 +8,6 @@ data class AudioPreferences( var embedThumb: Boolean = true, var cropThumb: Boolean? = null, var splitByChapters: Boolean = false, - var sponsorBlockFilters: ArrayList = arrayListOf() + var sponsorBlockFilters: ArrayList = arrayListOf(), + var bitrate: String = "" ) : Parcelable 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 f485258f..b6bb8c02 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 @@ -293,8 +293,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) + val bitrate = sharedPreferences.getString("audio_bitrate", "") - val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!)) + val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!), bitrate!!) val preferredAudioFormats = getPreferredAudioFormats(resultItem.formats) @@ -473,6 +474,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel } val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) + val audioBitrate = sharedPreferences.getString("audio_bitrate", "") val extraCommands = when (historyItem.type) { Type.audio -> extraCommandsForAudio @@ -484,7 +486,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel } }.joinToString(" ") { it.content } - val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!)) + val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!), audioBitrate!!) val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, subsLanguages, recodeVideo = recodeVideo) var path = defaultPath historyItem.downloadPath.first().apply { @@ -890,9 +892,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel repository.startDownloadWorker(emptyList(), application) } - suspend fun queueProcessingDownloads() : QueueDownloadsResult { + suspend fun queueProcessingDownloads(ignoreDuplicates: Boolean = false) : QueueDownloadsResult { val processingItems = repository.getAllProcessingDownloads() - return queueDownloads(processingItems) + return queueDownloads(processingItems, ignoreDuplicates) } data class QueueDownloadsResult( @@ -1262,13 +1264,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel } } - suspend fun updateProcessingDownloadTimeAndQueueScheduled(time: Long) : QueueDownloadsResult { + suspend fun updateProcessingDownloadTimeAndQueueScheduled(time: Long, ignoreDuplicates: Boolean = false) : QueueDownloadsResult { val processing = repository.getAllProcessingDownloads() processing.forEach { it.downloadStartTime = time it.status = DownloadRepository.Status.Scheduled.toString() } - return queueDownloads(processing) + return queueDownloads(processing, ignoreDuplicates) } fun checkIfAllProcessingItemsHaveSameType(selectedItems: List?) : Pair { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt index df51a524..028f7efd 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt @@ -315,6 +315,9 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private splitByChaptersClicked = { downloadItem.audioPreferences.splitByChapters = it }, + bitrateSet = { + downloadItem.audioPreferences.bitrate = it + }, filenameTemplateSet = { downloadItem.customFileNameTemplate = it }, diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt index 233808da..5cc4eb74 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt @@ -85,6 +85,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { private lateinit var result: ResultItem private lateinit var type: Type + private var ignoreDuplicates: Boolean = false private var disableUpdateData : Boolean = false private var currentDownloadItem: DownloadItem? = null private var incognito: Boolean = false @@ -108,6 +109,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { } type = arguments?.getSerializable("type") as Type disableUpdateData = arguments?.getBoolean("disableUpdateData") == true + ignoreDuplicates = arguments?.getBoolean("ignore_duplicates") == true if (res == null){ dismiss() @@ -335,7 +337,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { lifecycleScope.launch { val result = withContext(Dispatchers.IO){ - downloadViewModel.queueDownloads(itemsToQueue) + downloadViewModel.queueDownloads(itemsToQueue, ignoreDuplicates) } if (result.message.isNotBlank()){ @@ -350,7 +352,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { }else{ lifecycleScope.launch { val result = withContext(Dispatchers.IO){ - downloadViewModel.queueDownloads(listOf(item)) + downloadViewModel.queueDownloads(listOf(item), ignoreDuplicates) } if (result.message.isNotBlank()){ @@ -381,7 +383,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { lifecycleScope.launch { val result = withContext(Dispatchers.IO) { - downloadViewModel.queueDownloads(itemsToQueue) + downloadViewModel.queueDownloads(itemsToQueue, ignoreDuplicates) } withContext(Dispatchers.Main){ handleDuplicatesAndDismiss(result.duplicateDownloadIDs) @@ -390,7 +392,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() { }) }else{ val result = withContext(Dispatchers.IO) { - downloadViewModel.queueDownloads(listOf(item)) + downloadViewModel.queueDownloads(listOf(item), ignoreDuplicates) } handleDuplicatesAndDismiss(result.duplicateDownloadIDs) } 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 7843f07a..60d9a6f7 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 @@ -105,6 +105,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure private lateinit var currentDownloadIDs: List private lateinit var currentHistoryIDs: List + private var ignoreDuplicates: Boolean = false private var processingItemsCount : Int = 0 private lateinit var formatBtn : MenuItem @@ -128,6 +129,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure currentDownloadIDs = arguments?.getLongArray("currentDownloadIDs")?.toList() ?: listOf() currentHistoryIDs = arguments?.getLongArray("currentHistoryIDs")?.toList() ?: listOf() + ignoreDuplicates = arguments?.getBoolean("ignore_duplicates") == true processingItemsCount = currentDownloadIDs.size } @@ -242,7 +244,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure lifecycleScope.launch { withContext(Dispatchers.IO){ historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs) - val result = downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis) + val result = downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis, ignoreDuplicates) if (result.message.isNotBlank()){ lifecycleScope.launch { withContext(Dispatchers.Main) { @@ -265,7 +267,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure lifecycleScope.launch { withContext(Dispatchers.IO){ historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs) - val result = downloadViewModel.queueProcessingDownloads() + val result = downloadViewModel.queueProcessingDownloads(ignoreDuplicates) if (result.message.isNotBlank()){ lifecycleScope.launch { withContext(Dispatchers.Main) { @@ -545,6 +547,15 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure } CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } } }, + bitrateSet = { bitrate -> + items.forEach { + it.audioPreferences.bitrate = bitrate + } + requireActivity().lifecycleScope.launch { + items.forEach { downloadViewModel.updateDownload(it) } + } + bottomSheet.dismiss() + }, filenameTemplateSet = {template -> items.forEach { it.customFileNameTemplate = template 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 39c92938..0d848b9c 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 @@ -516,7 +516,7 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ if (!filePresent) { historyViewModel.delete(it, false) } - downloadViewModel.queueDownloads(listOf(downloadItem)) + downloadViewModel.queueDownloads(listOf(downloadItem), ignoreDuplicates = true) } historyViewModel.delete(it, false) }, @@ -524,6 +524,7 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf( Pair("result", downloadViewModel.createResultItemFromHistory(it)), Pair("type", it.type), + Pair("ignore_duplicates", true), )) } ) @@ -647,7 +648,8 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf( Pair("result", downloadViewModel.createResultItemFromHistory(tmp)), - Pair("type", tmp.type) + Pair("type", tmp.type), + Pair("ignore_duplicates", true) )) }else { val showDownloadCard = sharedPreferences.getBoolean("download_card", true) @@ -656,6 +658,7 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ if (showDownloadCard){ val bundle = Bundle() bundle.putLongArray("currentHistoryIDs", selectedObjects.toLongArray()) + bundle.putBoolean("ignore_duplicates", true) findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundle) } } @@ -741,7 +744,8 @@ class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{ historyAdapter.notifyItemChanged(position) findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf( Pair("result", downloadViewModel.createResultItemFromHistory(item)), - Pair("type", item.type) + Pair("type", item.type), + Pair("ignore_duplicates", true) )) } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/ProcessingSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/ProcessingSettingsFragment.kt index 181e2b5a..64bfccbf 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/ProcessingSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/ProcessingSettingsFragment.kt @@ -6,6 +6,7 @@ import androidx.navigation.fragment.findNavController import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceManager +import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.deniscerri.ytdl.R import com.deniscerri.ytdl.util.UiUtil @@ -74,6 +75,31 @@ class ProcessingSettingsFragment : BaseSettingsFragment() { } } + findPreference("audio_bitrate")?.apply { + var currentValue = prefs.getString("audio_bitrate", "")!! + val entries = context.getStringArray(R.array.audio_bitrate) + val entryValues = context.getStringArray(R.array.audio_bitrate_values) + + summary = if (currentValue.isNotBlank()) { + entries[entryValues.indexOf(currentValue)] + }else { + getString(R.string.defaultValue) + } + + setOnPreferenceClickListener { + currentValue = prefs.getString("audio_bitrate", "")!! + UiUtil.showAudioBitrateDialog(requireActivity(), currentValue) { + editor.putString("audio_bitrate", it).apply() + summary = if (it.isNotBlank()) { + entries[entryValues.indexOf(it)] + }else { + getString(R.string.defaultValue) + } + } + true + } + } + findPreference("reset_preferences")?.setOnPreferenceClickListener { UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) { resetPreferences(editor, R.xml.processing_preferences) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt b/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt index 54a9580f..219f46a0 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt @@ -320,7 +320,7 @@ object Extensions { Picasso.get() .load(imageURL) .resize(1280, 0) - .transform(BlurTransformation(context, 10, 1)) + .transform(BlurTransformation(context, 7, 1)) .onlyScaleDown() .into(this) 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 5560bb02..5faf5779 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -1180,6 +1180,21 @@ object UiUtil { dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START } + fun showAudioBitrateDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){ + val entries = context.getStringArray(R.array.audio_bitrate) + val entryValues = context.getStringArray(R.array.audio_bitrate_values) + + val prefIndex = entryValues.indexOf(currentValue) + MaterialAlertDialogBuilder(context) + .setTitle(context.getString(R.string.bitrate)) + .setSingleChoiceItems(entries, prefIndex) { dialog, index -> + ok(entryValues[index]) + dialog.dismiss() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + fun copyToClipboard(text: String, activity: Activity){ val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -1620,6 +1635,7 @@ object UiUtil { embedThumbClicked: (Boolean) -> Unit, cropThumbClicked: (Boolean) -> Unit, splitByChaptersClicked: (Boolean) -> Unit, + bitrateSet: (String) -> Unit, filenameTemplateSet: (String) -> Unit, sponsorBlockItemsSet: (Array, List) -> Unit, cutClicked: (VideoCutListener) -> Unit, @@ -1654,6 +1670,34 @@ object UiUtil { splitByChaptersClicked(splitByChapters.isChecked) } + val bitrate = view.findViewById(R.id.audio_bitrate) + bitrate.apply { + fun changeLabelText(newVal: String) { + if(newVal.isBlank()) return + val t = context.getString(R.string.bitrate) + " (${newVal})" + text = t + } + + fun getCurrentBitrate() = if (items.size == 1 || items.all { it.audioPreferences.bitrate == items[0].audioPreferences.bitrate }){ + items[0].audioPreferences.bitrate + }else { + "" + } + + changeLabelText(getCurrentBitrate()) + + setOnClickListener { + showAudioBitrateDialog(context, getCurrentBitrate()) { + var newText = context.getString(R.string.bitrate) + if (it.isNotBlank()) { + newText += " (${it})" + } + text = newText + bitrateSet(it) + } + } + } + val filenameTemplate = view.findViewById(R.id.filename_template) filenameTemplate.setOnClickListener { val currentFilename = if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){ 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 9f58fc6d..3a5d1a19 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 @@ -867,13 +867,13 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } if(downloadItem.title.isNotBlank()){ - metadataCommands.addOption("--replace-in-metadata", "title", """^.*$""", downloadItem.title) + metadataCommands.addOption("--replace-in-metadata", "title", """^.*$""", downloadItem.title.replace("""\""", """\\""")) metadataCommands.addOption("--parse-metadata", "%(title)s:%(meta_title)s") } if (downloadItem.author.isNotBlank()){ - metadataCommands.addOption("--replace-in-metadata", "uploader", """^.*$""", downloadItem.author) + metadataCommands.addOption("--replace-in-metadata", "uploader", """^.*$""", downloadItem.author.replace("""\""", """\\""")) metadataCommands.addOption("--parse-metadata", "%(uploader)s:%(artist)s") } @@ -892,10 +892,6 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } } - if (sharedPreferences.getBoolean("use_audio_quality", false)){ - request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0).toString()) - } - if (sharedPreferences.getBoolean("write_description", false)){ request.addOption("--write-description") } @@ -1089,6 +1085,10 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } } + if (downloadItem.audioPreferences.bitrate.isNotBlank()) { + request.addOption("--audio-quality", downloadItem.audioPreferences.bitrate) + } + } DownloadViewModel.Type.video -> { val useArtistTags = if (downloadItem.url.isYoutubeURL()) "artists,artist," else "" diff --git a/app/src/main/res/drawable/baseline_high_quality_24.xml b/app/src/main/res/drawable/baseline_high_quality_24.xml new file mode 100644 index 00000000..c97232eb --- /dev/null +++ b/app/src/main/res/drawable/baseline_high_quality_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/adjust_audio.xml b/app/src/main/res/layout/adjust_audio.xml index af4324f7..a9088558 100644 --- a/app/src/main/res/layout/adjust_audio.xml +++ b/app/src/main/res/layout/adjust_audio.xml @@ -80,6 +80,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + @@ -158,10 +158,30 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:tag="potoken_player" - android:hint="PO Token (Player)" + android:hint="Player" app:endIconDrawable="@drawable/ic_clipboard" app:endIconMode="custom" - android:paddingBottom="10dp"> + android:paddingBottom="10dp" + tools:ignore="HardcodedText"> + + + + + + web_music + + @string/defaultValue + 320 kpbs + 192 kpbs + 128 kpbs + 64 kpbs + + + + + 320k + 192k + 128k + 64k + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc2ef78c..d54932dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -478,4 +478,6 @@ Custom Cutting is not available for this item Specify the range of items to select + PO Token + Web Client PO Token diff --git a/app/src/main/res/xml/advanced_preferences.xml b/app/src/main/res/xml/advanced_preferences.xml index 6f296101..17aa858e 100644 --- a/app/src/main/res/xml/advanced_preferences.xml +++ b/app/src/main/res/xml/advanced_preferences.xml @@ -14,7 +14,7 @@ android:defaultValue="" android:icon="@drawable/baseline_stars_24" android:key="generate_po_tokens" - android:summary="Web Client PO Token" + android:summary="@string/web_client_po_token" android:title="@string/generate_potokens" /> + + - - - -