mirror of https://github.com/deniscerri/ytdlnis
searchable settings
parent
7498cdbad5
commit
00469962bb
@ -0,0 +1,13 @@
|
||||
package com.deniscerri.ytdl.database.models
|
||||
|
||||
import androidx.preference.Preference
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
|
||||
data class SearchSettingsItem(
|
||||
val preference: Preference,
|
||||
val xmlId: Int,
|
||||
val module: SettingModule?,
|
||||
val groupTitle: String? = null,
|
||||
val isHeader: Boolean = false,
|
||||
var canRebind: Boolean = true
|
||||
)
|
||||
@ -0,0 +1,96 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.databinding.TextinputBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
object DefaultPreferenceActions {
|
||||
fun onPreferenceDisplayDialog(context: Context, preference: Preference, callback: () -> Unit) : Boolean {
|
||||
val layoutInflater = LayoutInflater.from(context)
|
||||
|
||||
return when (preference) {
|
||||
/**
|
||||
* Show a [MaterialAlertDialogBuilder] when the preference is a [ListPreference]
|
||||
*/
|
||||
is ListPreference -> {
|
||||
// get the index of the previous selected item
|
||||
val prefIndex = preference.entryValues.indexOf(preference.value)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(preference.title)
|
||||
.setSingleChoiceItems(preference.entries, prefIndex) { dialog, index ->
|
||||
// get the new ListPreference value
|
||||
val newValue = preference.entryValues[index].toString()
|
||||
// invoke the on change listeners
|
||||
if (preference.callChangeListener(newValue)) {
|
||||
preference.value = newValue
|
||||
}
|
||||
callback()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
|
||||
true
|
||||
}
|
||||
is MultiSelectListPreference -> {
|
||||
val selectedItems = preference.entryValues.map {
|
||||
preference.values.contains(it)
|
||||
}.toBooleanArray()
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(preference.title)
|
||||
.setMultiChoiceItems(preference.entries, selectedItems) { _, which, isChecked ->
|
||||
selectedItems[which] = isChecked
|
||||
}
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
val newValues = preference.entryValues
|
||||
.filterIndexed { index, _ -> selectedItems[index] }
|
||||
.map { it.toString() }
|
||||
.toMutableSet()
|
||||
if (preference.callChangeListener(newValues)) {
|
||||
preference.values = newValues
|
||||
}
|
||||
callback()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
|
||||
true
|
||||
}
|
||||
is EditTextPreference -> {
|
||||
val binding = TextinputBinding.inflate(layoutInflater)
|
||||
binding.urlEdittext.setText(preference.text)
|
||||
binding.urlTextinput.findViewById<TextInputLayout>(R.id.url_textinput).hint = preference.title
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(preference.title)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val newValue = binding.urlEdittext.text.toString()
|
||||
if (preference.callChangeListener(newValue)) {
|
||||
preference.text = newValue
|
||||
}
|
||||
callback()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
dialog.show()
|
||||
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
binding.urlEdittext.setSelection(binding.urlEdittext.text!!.length)
|
||||
binding.urlEdittext.postDelayed({
|
||||
binding.urlEdittext.requestFocus()
|
||||
imm.showSoftInput(binding.urlEdittext, 0)
|
||||
}, 300)
|
||||
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,299 +0,0 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class DownloadSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.downloads
|
||||
|
||||
private lateinit var archivePath: Preference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val rememberDownloadType = findPreference<SwitchPreferenceCompat>("remember_download_type")
|
||||
val downloadType = findPreference<ListPreference>("preferred_download_type")
|
||||
downloadType?.isEnabled = rememberDownloadType?.isChecked == false
|
||||
rememberDownloadType?.setOnPreferenceClickListener {
|
||||
downloadType?.isEnabled = !rememberDownloadType.isChecked
|
||||
true
|
||||
}
|
||||
|
||||
val preventDuplicateDownloads = findPreference<ListPreference>("prevent_duplicate_downloads")
|
||||
preventDuplicateDownloads?.setOnPreferenceChangeListener { _, newValue ->
|
||||
archivePath.isVisible = newValue == "download_archive"
|
||||
true
|
||||
}
|
||||
|
||||
archivePath = findPreference("download_archive_path")!!
|
||||
archivePath.summary = FileUtil.getDownloadArchivePath(requireContext())
|
||||
archivePath.isVisible = preferences.getString("prevent_duplicate_downloads", "") == "download_archive"
|
||||
archivePath.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
archivePathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
|
||||
val workManager = WorkManager.getInstance(requireContext())
|
||||
val cleanupLeftoverDownloads = findPreference<Preference>("cleanup_leftover_downloads")
|
||||
cleanupLeftoverDownloads?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var nextTime : Calendar? = Calendar.getInstance()
|
||||
when(newValue) {
|
||||
"daily" -> nextTime?.add(Calendar.DAY_OF_WEEK, 1)
|
||||
"weekly" -> nextTime?.add(Calendar.DAY_OF_WEEK, 7)
|
||||
"monthly" -> nextTime?.add(Calendar.MONTH, 1)
|
||||
else -> nextTime = null
|
||||
}
|
||||
|
||||
if (nextTime == null) workManager.cancelAllWorkByTag("cleanup_leftover_downloads")
|
||||
else {
|
||||
val workConstraints = Constraints.Builder()
|
||||
val allowMeteredNetworks = preferences.getBoolean("metered_networks", true)
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
||||
val delay = nextTime.timeInMillis.minus(System.currentTimeMillis())
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<CleanUpLeftoverDownloads>()
|
||||
.addTag("cleanup_leftover_downloads")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
val scheduler = AlarmScheduler(requireContext())
|
||||
|
||||
val useAlarmManagerInsteadOfWorkManager = findPreference<SwitchPreferenceCompat>("use_alarm_for_scheduling")
|
||||
useAlarmManagerInsteadOfWorkManager?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var allowChange = true
|
||||
if (newValue as Boolean){
|
||||
if (!scheduler.canSchedule() && Build.VERSION.SDK_INT >= 31){
|
||||
Intent().also { intent ->
|
||||
intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
||||
requireContext().startActivity(intent)
|
||||
}
|
||||
allowChange = false
|
||||
}
|
||||
}
|
||||
|
||||
allowChange
|
||||
}
|
||||
|
||||
val useScheduler = findPreference<SwitchPreferenceCompat>("use_scheduler")
|
||||
val scheduleStart = findPreference<Preference>("schedule_start")
|
||||
scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
|
||||
val scheduleEnd = findPreference<Preference>("schedule_end")
|
||||
scheduleEnd?.summary = preferences.getString("schedule_end", "05:00")
|
||||
|
||||
useScheduler?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var allowChange = true
|
||||
if (newValue as Boolean){
|
||||
if (!scheduler.canSchedule() && Build.VERSION.SDK_INT >= 31){
|
||||
Intent().also { intent ->
|
||||
intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
||||
requireContext().startActivity(intent)
|
||||
}
|
||||
allowChange = false
|
||||
}else{
|
||||
scheduler.schedule()
|
||||
}
|
||||
}else{
|
||||
scheduler.cancel()
|
||||
//start worker if there are leftover downloads waiting for scheduler
|
||||
val workConstraints = Constraints.Builder()
|
||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.addTag("download")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(1000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
WorkManager.getInstance(requireContext()).enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
allowChange
|
||||
}
|
||||
|
||||
scheduleStart?.setOnPreferenceClickListener {
|
||||
UiUtil.showTimePicker(parentFragmentManager, preferences){
|
||||
val hr = it.get(Calendar.HOUR_OF_DAY)
|
||||
val mn = it.get(Calendar.MINUTE)
|
||||
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
|
||||
preferences.edit().putString("schedule_start",formattedTime).apply()
|
||||
scheduleStart.summary = formattedTime
|
||||
|
||||
scheduler.schedule()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
scheduleEnd?.setOnPreferenceClickListener {
|
||||
UiUtil.showTimePicker(parentFragmentManager, preferences){
|
||||
val hr = it.get(Calendar.HOUR_OF_DAY)
|
||||
val mn = it.get(Calendar.MINUTE)
|
||||
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
|
||||
preferences.edit().putString("schedule_end",formattedTime).apply()
|
||||
scheduleEnd.summary = formattedTime
|
||||
|
||||
scheduler.schedule()
|
||||
}
|
||||
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.buffer_size_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.socket_timeout_description)
|
||||
summary = if (text.isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${text}]"
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
summary = if ((newValue as String?).isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${newValue}]"
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(preferences.edit(), R.xml.downloading_preferences)
|
||||
requireActivity().recreate()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private var archivePathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
val path = result.data!!.data.toString()
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val editor = preferences.edit()
|
||||
editor.putString("download_archive_path", path)
|
||||
editor.apply()
|
||||
archivePath.summary = FileUtil.getDownloadArchivePath(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,361 +0,0 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.MoveCacheFilesWorker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
class FolderSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.directories
|
||||
|
||||
private var musicPath: Preference? = null
|
||||
private var videoPath: Preference? = null
|
||||
private var commandPath: Preference? = null
|
||||
private var cachePath: Preference? = null
|
||||
private var accessAllFiles : Preference? = null
|
||||
private var noFragments: SwitchPreferenceCompat? = null
|
||||
private var keepFragments: SwitchPreferenceCompat? = null
|
||||
private var cacheDownloads : Preference? = null
|
||||
private var audioFilenameTemplate : Preference? = null
|
||||
private var videoFilenameTemplate : Preference? = null
|
||||
private var clearCache: Preference? = null
|
||||
private var moveCache: Preference? = null
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private var activeDownloadCount = 0
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.folders_preference, rootKey)
|
||||
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
editor = preferences.edit()
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
|
||||
musicPath = findPreference("music_path")
|
||||
videoPath = findPreference("video_path")
|
||||
commandPath = findPreference("command_path")
|
||||
cachePath = findPreference("cache_path")
|
||||
accessAllFiles = findPreference("access_all_files")
|
||||
noFragments = findPreference("no_part")
|
||||
keepFragments = findPreference("keep_cache")
|
||||
cacheDownloads = findPreference("cache_downloads")
|
||||
videoFilenameTemplate = findPreference("file_name_template")
|
||||
audioFilenameTemplate = findPreference("file_name_template_audio")
|
||||
clearCache = findPreference("clear_cache")
|
||||
moveCache = findPreference("move_cache")
|
||||
|
||||
if (preferences.getString("music_path", "")!!.isEmpty()) {
|
||||
editor.putString("music_path", FileUtil.getDefaultAudioPath()).apply()
|
||||
}
|
||||
if (preferences.getString("video_path", "")!!.isEmpty()) {
|
||||
editor.putString("video_path", FileUtil.getDefaultVideoPath()).apply()
|
||||
}
|
||||
if (preferences.getString("command_path", "")!!.isEmpty()) {
|
||||
editor.putString("command_path", FileUtil.getDefaultCommandPath()).apply()
|
||||
}
|
||||
if (preferences.getString("cache_path", "")!!.isEmpty()) {
|
||||
editor.putString("cache_path", FileUtil.getCachePath(requireContext())).apply()
|
||||
}
|
||||
|
||||
if (FileUtil.hasAllFilesAccess()) {
|
||||
accessAllFiles!!.isVisible = false
|
||||
cacheDownloads!!.isEnabled = true
|
||||
}else{
|
||||
editor.putBoolean("cache_downloads", true).apply()
|
||||
cacheDownloads!!.isEnabled = false
|
||||
}
|
||||
|
||||
musicPath!!.summary = FileUtil.formatPath(preferences.getString("music_path", "")!!)
|
||||
musicPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
musicPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
videoPath!!.summary = FileUtil.formatPath(preferences.getString("video_path", "")!!)
|
||||
videoPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
videoPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
commandPath!!.summary = FileUtil.formatPath(preferences.getString("command_path", "")!!)
|
||||
commandPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
commandPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
|
||||
cachePath!!.summary = FileUtil.formatPath(preferences.getString("cache_path", FileUtil.getCachePath(requireContext()))!!)
|
||||
cachePath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.cache_directory), getString(R.string.cache_directory_warning)) {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
cachePathResultLauncher.launch(intent)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
if(VERSION.SDK_INT >= 30){
|
||||
accessAllFiles!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.parse("package:" + requireContext().packageName)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
if (noFragments!!.isChecked) {
|
||||
editor.putBoolean("keep_cache", false).apply()
|
||||
keepFragments!!.isChecked = false
|
||||
keepFragments!!.isEnabled = false
|
||||
}
|
||||
noFragments!!.setOnPreferenceChangeListener { _, newValue ->
|
||||
if(newValue as Boolean){
|
||||
editor.putBoolean("keep_cache", false).apply()
|
||||
keepFragments!!.isChecked = false
|
||||
keepFragments!!.isEnabled = false
|
||||
}else{
|
||||
keepFragments!!.isEnabled = true
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
||||
videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
|
||||
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||
audioFilenameTemplate?.summary = preferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
|
||||
|
||||
videoFilenameTemplate?.setOnPreferenceClickListener {
|
||||
UiUtil.showFilenameTemplateDialog(requireActivity(),videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") {
|
||||
editor.putString("file_name_template", it).apply()
|
||||
videoFilenameTemplate?.summary = it
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
audioFilenameTemplate?.setOnPreferenceClickListener {
|
||||
UiUtil.showFilenameTemplateDialog(requireActivity(), audioFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]") {
|
||||
editor.putString("file_name_template_audio", it).apply()
|
||||
audioFilenameTemplate?.summary = it
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
var cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
val filesize = if (cacheSize < 10000) {
|
||||
"0B"
|
||||
}else {
|
||||
FileUtil.convertFileSize(cacheSize)
|
||||
}
|
||||
clearCache!!.summary = "${resources.getString(R.string.clear_temporary_files_summary)} (${filesize}) "
|
||||
clearCache!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
lifecycleScope.launch {
|
||||
activeDownloadCount = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
}
|
||||
if (activeDownloadCount == 0){
|
||||
fun clearCacheFolder(folder: File) {
|
||||
if (folder.exists() && folder.isDirectory) {
|
||||
folder.listFiles()?.forEach { file ->
|
||||
if (file.isDirectory) {
|
||||
clearCacheFolder(file)
|
||||
file.delete()
|
||||
} else {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearCacheFolder(File(FileUtil.getCachePath(requireContext())))
|
||||
|
||||
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
||||
cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
val filesize = if (cacheSize < 10000) {
|
||||
"0B"
|
||||
}else {
|
||||
FileUtil.convertFileSize(cacheSize)
|
||||
}
|
||||
clearCache!!.summary = "${resources.getString(R.string.clear_temporary_files_summary)} (${filesize})"
|
||||
}else{
|
||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
moveCache!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val workRequest = OneTimeWorkRequestBuilder<MoveCacheFilesWorker>()
|
||||
.addTag("cacheFiles")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(requireContext()).beginUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.KEEP,
|
||||
workRequest
|
||||
).enqueue()
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData("cacheFiles")
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
if (list == null) return@observe
|
||||
if (list.first() == null) return@observe
|
||||
|
||||
if (list.first().state == WorkInfo.State.SUCCEEDED){
|
||||
cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "${resources.getString(R.string.clear_temporary_files_summary)} (${FileUtil.convertFileSize(cacheSize)})"
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, R.xml.folders_preference)
|
||||
requireActivity().recreate()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||
VERSION.SDK_INT < 30) {
|
||||
accessAllFiles!!.isVisible = false
|
||||
cacheDownloads!!.isEnabled = true
|
||||
}else{
|
||||
editor.putBoolean("cache_downloads", true).apply()
|
||||
cacheDownloads!!.isEnabled = false
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
private var musicPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(musicPath, result.data, MUSIC_PATH_CODE)
|
||||
}
|
||||
}
|
||||
private var videoPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(videoPath, result.data, VIDEO_PATH_CODE)
|
||||
}
|
||||
}
|
||||
private var commandPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(commandPath, result.data, COMMAND_PATH_CODE)
|
||||
}
|
||||
}
|
||||
private var cachePathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(cachePath, result.data, CACHE_PATH_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
|
||||
val path = data!!.data.toString()
|
||||
p!!.summary = FileUtil.formatPath(data.data.toString())
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val editor = sharedPreferences.edit()
|
||||
when (requestCode) {
|
||||
MUSIC_PATH_CODE -> editor.putString("music_path", path)
|
||||
VIDEO_PATH_CODE -> editor.putString("video_path", path)
|
||||
COMMAND_PATH_CODE -> editor.putString("command_path", path)
|
||||
CACHE_PATH_CODE -> editor.putString("cache_path", path)
|
||||
}
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MUSIC_PATH_CODE = 33333
|
||||
const val VIDEO_PATH_CODE = 55555
|
||||
const val COMMAND_PATH_CODE = 77777
|
||||
const val CACHE_PATH_CODE = 99999
|
||||
}
|
||||
}
|
||||
@ -1,472 +0,0 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.databinding.NavOptionsItemBinding
|
||||
import com.deniscerri.ytdl.ui.adapter.IconsSheetAdapter
|
||||
import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.general
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
|
||||
private var updateUtil: UpdateUtil? = null
|
||||
private var activeDownloadCount = 0
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.general_preferences, rootKey)
|
||||
NavbarUtil.init(requireContext())
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
updateUtil = UpdateUtil(requireContext())
|
||||
val editor = preferences.edit()
|
||||
|
||||
WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){
|
||||
activeDownloadCount = 0
|
||||
it.forEach {w ->
|
||||
if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<ListPreference>("app_language")?.apply {
|
||||
value = Locale.getDefault().language
|
||||
summary = Locale.getDefault().displayLanguage
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == "system") {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(null))
|
||||
}else{
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
|
||||
}
|
||||
summary = Locale.getDefault().displayLanguage
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("label_visibility")?.apply {
|
||||
isVisible = !resources.getBoolean(R.bool.uses_side_nav)
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
ThemeUtil.recreateMain()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("navigation_bar")?.apply {
|
||||
isVisible = !resources.getBoolean(R.bool.uses_side_nav)
|
||||
if (isVisible) {
|
||||
summary = NavbarUtil.getNavBarItems(requireContext()).filter { it.isVisible }.map { it.title }.joinToString(", ")
|
||||
}
|
||||
setOnPreferenceClickListener {
|
||||
val binding = requireActivity().layoutInflater.inflate(R.layout.simple_options_recycler, null)
|
||||
val options = NavbarUtil.getNavBarItems(requireContext())
|
||||
|
||||
val optionsRecycler = binding.findViewById<RecyclerView>(R.id.options_recycler)
|
||||
val adapter : NavBarOptionsAdapter?
|
||||
|
||||
val onItemClick = object: NavBarOptionsAdapter.OnItemClickListener {
|
||||
override fun onNavBarOptionDeselected(item: NavOptionsItemBinding) {
|
||||
optionsRecycler.findViewHolderForLayoutPosition(0)?.apply {
|
||||
(this as NavBarOptionsAdapter.NavBarOptionsViewHolder).apply {
|
||||
this.binding.home.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter = NavBarOptionsAdapter(
|
||||
options.toMutableList(),
|
||||
NavbarUtil.getStartFragmentId(requireContext()),
|
||||
onItemClick
|
||||
)
|
||||
|
||||
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
return makeMovementFlags(dragFlags, 0)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val itemToMove = adapter.items[viewHolder.absoluteAdapterPosition]
|
||||
adapter.items.remove(itemToMove)
|
||||
adapter.items.add(target.absoluteAdapterPosition, itemToMove)
|
||||
|
||||
adapter.notifyItemMoved(
|
||||
viewHolder.absoluteAdapterPosition,
|
||||
target.absoluteAdapterPosition
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
optionsRecycler.layoutManager = LinearLayoutManager(context)
|
||||
optionsRecycler.adapter = adapter
|
||||
|
||||
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
|
||||
itemTouchHelper.attachToRecyclerView(optionsRecycler)
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.navigation_bar)
|
||||
.setView(binding)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
NavbarUtil.setNavBarItems(adapter.items, requireContext())
|
||||
NavbarUtil.setStartFragment(adapter.selectedHomeTabId)
|
||||
summary = adapter.items.filter { it.isVisible }.map { it.title }.joinToString(", ")
|
||||
ThemeUtil.recreateMain()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<ListPreference>("ytdlnis_theme")?.apply {
|
||||
summary = entry
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
dialog.setTitle(context.getString(R.string.app_icon_change))
|
||||
dialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
dialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
summary = when(newValue){
|
||||
"System" -> {
|
||||
getString(R.string.system)
|
||||
}
|
||||
|
||||
"Dark" -> {
|
||||
getString(R.string.dark)
|
||||
}
|
||||
|
||||
else -> {
|
||||
getString(R.string.light)
|
||||
}
|
||||
}
|
||||
editor.putString("ytdlnis_theme", newValue.toString()).apply()
|
||||
ThemeUtil.updateThemes()
|
||||
}
|
||||
dialog.show()
|
||||
|
||||
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("ytdlnis_icon")?.apply {
|
||||
val currentValue = preferences.getString("ytdlnis_icon", "default")
|
||||
IconsSheetAdapter.availableIcons.firstOrNull { it.activityAlias == currentValue }?.let {
|
||||
summary = getString(it.nameResource)
|
||||
}
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
val bottomSheet = BottomSheetDialog(context)
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.generic_list)
|
||||
|
||||
val recycler = bottomSheet.findViewById<RecyclerView>(R.id.download_recyclerview)!!
|
||||
recycler.layoutManager = GridLayoutManager(context, 3)
|
||||
recycler.adapter = IconsSheetAdapter(requireActivity())
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<ListPreference>("theme_accent")?.apply {
|
||||
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 aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdl.terminalShareAlias")
|
||||
if ((pref as SwitchPreferenceCompat).isChecked){
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}else{
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<SwitchPreferenceCompat>("show_quick_download_share")?.apply {
|
||||
setOnPreferenceChangeListener { pref, _ ->
|
||||
val packageManager = requireContext().packageManager
|
||||
val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdl.quickDownloadShareAlias")
|
||||
if ((pref as SwitchPreferenceCompat).isChecked){
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}else{
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<SwitchPreferenceCompat>("display_over_apps")?.apply {
|
||||
isChecked = Settings.canDrawOverlays(requireContext())
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
runCatching {
|
||||
val i = Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + requireContext().packageName)
|
||||
)
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(i)
|
||||
displayOverAppsResultLauncher.launch(i)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("ignore_battery")?.apply {
|
||||
setOnPreferenceClickListener {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||
intent.data = Uri.parse("package:" + requireContext().packageName)
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<MultiSelectListPreference>("hide_thumbnails")?.apply {
|
||||
values.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValues ->
|
||||
(newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<MultiSelectListPreference>("modify_download_card")?.apply {
|
||||
values.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValues ->
|
||||
(newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<ListPreference>("recommendations_home")?.apply {
|
||||
val s = getString(R.string.video_recommendations_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)]}]"
|
||||
}
|
||||
|
||||
findPreference<EditTextPreference>("api_key")?.isVisible = newValue == "yt_api"
|
||||
findPreference<EditTextPreference>("custom_home_recommendation_url")?.isVisible = newValue == "custom"
|
||||
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<EditTextPreference>("custom_home_recommendation_url")?.apply {
|
||||
title = "[${getString(R.string.video_recommendations)}] ${getString(R.string.custom)}"
|
||||
isVisible = preferences.getString("recommendations_home", "") == "custom"
|
||||
|
||||
|
||||
setOnPreferenceChangeListener { preference, newValue ->
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<EditTextPreference>("api_key")?.apply {
|
||||
isVisible = preferences.getString("recommendations_home", "") == "yt_api"
|
||||
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}]"
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
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.swipe_gestures_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 = List(newValues.size) { index -> index }
|
||||
summary = "${s}\n[${entries.filterIndexed { index, _ -> indexes.contains(index) }.joinToString(", ")}]"
|
||||
}else{
|
||||
summary = s
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, R.xml.general_preferences)
|
||||
ThemeUtil.updateThemes()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val packageName: String = requireContext().packageName
|
||||
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
findPreference<Preference>("ignore_battery")?.isVisible = false
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
private var displayOverAppsResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { _ ->
|
||||
findNavController().popBackStack(R.id.appearanceSettingsFragment, false)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultCaller
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.edit
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.R
|
||||
|
||||
class PreferenceActivityResultDelegate(caller: ActivityResultCaller) {
|
||||
private var pendingCallback : ((result: ActivityResult) -> Unit)? = null
|
||||
|
||||
private val launcher = caller.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
pendingCallback?.invoke(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun launch(
|
||||
intent: Intent,
|
||||
callback: (result: ActivityResult) -> Unit
|
||||
) {
|
||||
pendingCallback = callback
|
||||
launcher.launch(intent)
|
||||
}
|
||||
}
|
||||
@ -1,169 +0,0 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
|
||||
|
||||
class ProcessingSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.processing
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.processing_preferences, rootKey)
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val editor = prefs.edit()
|
||||
|
||||
val preferredFormatID : EditTextPreference? = findPreference("format_id")
|
||||
val preferredFormatIDAudio : EditTextPreference? = findPreference("format_id_audio")
|
||||
val subtitleLanguages : Preference? = findPreference("subs_lang")
|
||||
|
||||
|
||||
preferredFormatID?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.video)}]"
|
||||
preferredFormatID?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
||||
|
||||
preferredFormatIDAudio?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.audio)}]"
|
||||
preferredFormatIDAudio?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||
|
||||
subtitleLanguages?.summary = prefs.getString("subs_lang", "en.*,.*-orig")!!
|
||||
subtitleLanguages?.setOnPreferenceClickListener {
|
||||
UiUtil.showSubtitleLanguagesDialog(requireActivity(), listOf(), prefs.getString("subs_lang", "en.*,.*-orig")!!){
|
||||
editor.putString("subs_lang", it)
|
||||
editor.apply()
|
||||
subtitleLanguages.summary = it
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("audio_bitrate")?.apply {
|
||||
var currentValue = prefs.getString("audio_bitrate", "")!!
|
||||
val entries = context.resources.getStringArray(R.array.audio_bitrate)
|
||||
val entryValues = context.resources.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
|
||||
}
|
||||
}
|
||||
|
||||
val audioCodecPref = findPreference<ListPreference>("audio_codec")
|
||||
val videoCodecPref = findPreference<ListPreference>("video_codec")
|
||||
val videoContainerPref = findPreference<ListPreference>("video_format")
|
||||
|
||||
val recodeVideoPreference = findPreference<SwitchPreferenceCompat>("recode_video")!!
|
||||
val compatibleVideoPreference = findPreference<SwitchPreferenceCompat>("compatible_video")!!
|
||||
|
||||
audioCodecPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
videoCodecPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
videoContainerPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
|
||||
recodeVideoPreference.setOnPreferenceClickListener {
|
||||
if (compatibleVideoPreference.isChecked && recodeVideoPreference.isChecked) {
|
||||
compatibleVideoPreference.performClick()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
compatibleVideoPreference.setOnPreferenceClickListener {
|
||||
audioCodecPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
videoCodecPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
videoContainerPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
|
||||
if (compatibleVideoPreference.isChecked) {
|
||||
if (recodeVideoPreference.isChecked) {
|
||||
recodeVideoPreference.performClick()
|
||||
}
|
||||
|
||||
editor.putString("audio_codec_tmp", audioCodecPref?.value ?: "").apply()
|
||||
editor.putString("video_codec_tmp", videoCodecPref?.value ?: "").apply()
|
||||
editor.putString("video_format_tmp", videoContainerPref?.value ?: "").apply()
|
||||
|
||||
val audioCodecs = requireContext().getStringArray(R.array.audio_codec)
|
||||
val audioCodecValues = requireContext().getStringArray(R.array.audio_codec_values)
|
||||
val videoCodecs = requireContext().getStringArray(R.array.video_codec)
|
||||
val videoCodecValues = requireContext().getStringArray(R.array.video_codec_values)
|
||||
|
||||
val newAudioCodec = "M4A"
|
||||
val newVideoCodec = "AVC (H264)"
|
||||
|
||||
editor.putString("audio_codec", audioCodecValues[audioCodecs.indexOf(newAudioCodec)]).apply()
|
||||
editor.putString("video_codec", videoCodecValues[videoCodecs.indexOf(newVideoCodec)]).apply()
|
||||
editor.putString("video_format", "").apply()
|
||||
requireActivity().recreate()
|
||||
} else {
|
||||
editor.putString("audio_codec", prefs.getString("audio_codec_tmp", "")).apply()
|
||||
editor.putString("video_codec", prefs.getString("video_codec_tmp", "")).apply()
|
||||
editor.putString("video_format", prefs.getString("video_format_tmp", "")).apply()
|
||||
requireActivity().recreate()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, R.xml.processing_preferences)
|
||||
requireActivity().recreate()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.preference.Preference
|
||||
|
||||
interface SettingHost {
|
||||
fun findPref(key: String): Preference?
|
||||
fun refreshUI()
|
||||
fun getHostContext(): Activity
|
||||
val hostLifecycleOwner: LifecycleOwner
|
||||
val hostViewModelStoreOwner: ViewModelStoreOwner
|
||||
val activityResultDelegate: PreferenceActivityResultDelegate
|
||||
val hostView: View?
|
||||
fun requestGetParentFragmentManager(): FragmentManager
|
||||
fun requestRecreateActivity()
|
||||
fun requestNavigate(id: Int)
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import androidx.preference.Preference
|
||||
|
||||
interface SettingModule {
|
||||
fun bindLogic(pref: Preference, host: SettingHost)
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceGroup
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.SearchSettingsItem
|
||||
import com.deniscerri.ytdl.ui.more.settings.advanced.AdvancedSettingsModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.downloading.DownloadSettingsModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.folder.FolderSettingsModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.general.GeneralSettingsModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.processing.ProcessingSettingsModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.updating.UpdateSettingsModule
|
||||
|
||||
object SettingsRegistry {
|
||||
private val xmlToModule = mapOf(
|
||||
R.xml.general_preferences to GeneralSettingsModule,
|
||||
R.xml.folders_preference to FolderSettingsModule,
|
||||
R.xml.downloading_preferences to DownloadSettingsModule,
|
||||
R.xml.processing_preferences to ProcessingSettingsModule,
|
||||
R.xml.updating_preferences to UpdateSettingsModule,
|
||||
R.xml.advanced_preferences to AdvancedSettingsModule
|
||||
)
|
||||
|
||||
fun getModuleForXml(xmlRes: Int) = xmlToModule[xmlRes]
|
||||
|
||||
fun bindFragment(fragment: BaseSettingsFragment, xmlRes: Int) {
|
||||
val module = getModuleForXml(xmlRes) ?: return
|
||||
val allPrefs = mutableListOf<Preference>()
|
||||
fragment.getPreferences(fragment.preferenceScreen, allPrefs).forEach {
|
||||
module.bindLogic(it, fragment)
|
||||
}
|
||||
}
|
||||
|
||||
fun indexAll(context: Context): List<SearchSettingsItem> {
|
||||
val manager = PreferenceManager(context)
|
||||
val results = mutableListOf<SearchSettingsItem>()
|
||||
|
||||
xmlToModule.forEach { (xmlRes, module) ->
|
||||
val screen = manager.inflateFromResource(context, xmlRes, null)
|
||||
results.addAll(crawl(screen, xmlRes, module, null))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
private fun crawl(
|
||||
group: PreferenceGroup,
|
||||
xmlId: Int,
|
||||
module: SettingModule?,
|
||||
parentTitle: String? = null
|
||||
): List<SearchSettingsItem> {
|
||||
val list = mutableListOf<SearchSettingsItem>()
|
||||
|
||||
if (!group.title.isNullOrBlank()) {
|
||||
list.add(SearchSettingsItem(
|
||||
preference = group,
|
||||
xmlId = xmlId,
|
||||
module = module,
|
||||
groupTitle = group.title.toString(),
|
||||
isHeader = true,
|
||||
canRebind = true
|
||||
))
|
||||
}
|
||||
|
||||
val preferencesWithoutRebindingLogic = listOf("ytdl-version")
|
||||
|
||||
for (i in 0 until group.preferenceCount) {
|
||||
val p = group.getPreference(i)
|
||||
if (p is PreferenceGroup) {
|
||||
list.addAll(crawl(p, xmlId, module, group.title?.toString()))
|
||||
} else if (p.key != null && p.key != "reset_preferences") {
|
||||
list.add(SearchSettingsItem(
|
||||
preference = p,
|
||||
xmlId = xmlId,
|
||||
module = module,
|
||||
groupTitle = group.title?.toString() ?: parentTitle,
|
||||
isHeader = false,
|
||||
canRebind = !preferencesWithoutRebindingLogic.contains(p.key)
|
||||
))
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.advanced
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Typeface
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.ui.adapter.SortableTextItemAdapter
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingHost
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlin.collections.indexOf
|
||||
|
||||
object AdvancedSettingsModule : SettingModule {
|
||||
override fun bindLogic(pref: Preference, host: SettingHost) {
|
||||
val context = pref.context
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
when(pref.key) {
|
||||
"yt_player_client" -> {
|
||||
pref.setOnPreferenceClickListener {
|
||||
host.requestNavigate(R.id.youtubePlayerClientFragment)
|
||||
false
|
||||
}
|
||||
}
|
||||
"generate_po_tokens" -> {
|
||||
pref.setOnPreferenceClickListener {
|
||||
host.requestNavigate(R.id.generateYoutubePoTokensFragment)
|
||||
false
|
||||
}
|
||||
}
|
||||
"format_importance_audio" -> {
|
||||
pref.apply {
|
||||
title = "${context.getString(R.string.format_importance)} [${context.getString(R.string.audio)}]"
|
||||
val items = context.resources.getStringArray(R.array.format_importance_audio)
|
||||
val itemValues = context.resources.getStringArray(R.array.format_importance_audio_values).toSet()
|
||||
val prefVideo = prefs.getString("format_importance_audio", itemValues.joinToString(","))!!
|
||||
summary = prefVideo.split(",").mapIndexed { index, s -> "${index + 1}. ${items[itemValues.indexOf(s)]}" }.joinToString("\n")
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
val prefValue = prefs.getString("format_importance_audio", itemValues.joinToString(","))!!
|
||||
val prefArr = prefValue.split(",")
|
||||
val itms = itemValues.sortedBy { prefArr.indexOf(it) }.map {
|
||||
Pair(it, items[itemValues.indexOf(it)])
|
||||
}.toMutableList()
|
||||
|
||||
showFormatImportanceDialog(context,title.toString(), itms) { new ->
|
||||
prefs.edit(commit = true) {
|
||||
putString("format_importance_audio", new.joinToString(",") { it.first })
|
||||
}
|
||||
pref.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
|
||||
host.refreshUI()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"format_importance_video" -> {
|
||||
pref.apply {
|
||||
title = "${context.getString(R.string.format_importance)} [${context.getString(R.string.video)}]"
|
||||
val items = context.resources.getStringArray(R.array.format_importance_video)
|
||||
val itemValues = context.resources.getStringArray(R.array.format_importance_video_values).toSet()
|
||||
val prefVideo = prefs.getString("format_importance_video", itemValues.joinToString(","))!!
|
||||
summary = prefVideo.split(",").mapIndexed { index, s -> "${index + 1}. ${items[itemValues.indexOf(s)]}" }.joinToString("\n")
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
val prefValue = prefs.getString("format_importance_video", itemValues.joinToString(","))!!
|
||||
val prefArr = prefValue.split(",")
|
||||
val itms = itemValues.sortedBy { prefArr.indexOf(it) }.map {
|
||||
Pair(it, items[itemValues.indexOf(it)])
|
||||
}.toMutableList()
|
||||
|
||||
showFormatImportanceDialog(context,title.toString(), itms) {new ->
|
||||
prefs.edit(commit = true) {
|
||||
putString("format_importance_video", new.joinToString(",") { it.first })
|
||||
}
|
||||
pref.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
|
||||
host.refreshUI()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFormatImportanceDialog(context: Context, t: String, items: MutableList<Pair<String, String>>, onChange: (items: List<Pair<String, String>>) -> Unit){
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(t)
|
||||
val adapter = SortableTextItemAdapter(items)
|
||||
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
return makeMovementFlags(dragFlags, 0)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val itemToMove = adapter.items[viewHolder.absoluteAdapterPosition]
|
||||
adapter.items.remove(itemToMove)
|
||||
adapter.items.add(target.absoluteAdapterPosition, itemToMove)
|
||||
|
||||
adapter.notifyItemMoved(
|
||||
viewHolder.absoluteAdapterPosition,
|
||||
target.absoluteAdapterPosition
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
val linear = LinearLayout(context)
|
||||
linear.orientation = LinearLayout.VERTICAL
|
||||
|
||||
val note = TextView(context)
|
||||
note.text = context.getString(R.string.format_importance_note)
|
||||
note.textSize = 16f
|
||||
note.setTypeface(note.typeface, Typeface.BOLD)
|
||||
note.setPadding(20,20,20,20)
|
||||
linear.addView(note)
|
||||
|
||||
val recycler = RecyclerView(context)
|
||||
recycler.layoutManager = LinearLayoutManager(context)
|
||||
recycler.adapter = adapter
|
||||
|
||||
linear.addView(recycler)
|
||||
|
||||
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recycler)
|
||||
|
||||
|
||||
builder.setView(linear)
|
||||
builder.setPositiveButton(
|
||||
context.getString(android.R.string.ok)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
onChange(adapter.items)
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.downloading
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.ui.more.settings.BaseSettingsFragment
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsRegistry
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
|
||||
class DownloadSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.downloads
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
val preferenceXMLRes = R.xml.downloading_preferences
|
||||
setPreferencesFromResource(preferenceXMLRes, rootKey)
|
||||
SettingsRegistry.bindFragment(this, preferenceXMLRes)
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val editor = prefs.edit()
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, preferenceXMLRes)
|
||||
requireActivity().recreate()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,282 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.downloading
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.EditTextPreference
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingHost
|
||||
|
||||
object DownloadSettingsModule : SettingModule {
|
||||
override fun bindLogic(pref: Preference, host: SettingHost) {
|
||||
val context = pref.context
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
when(pref.key) {
|
||||
"remember_download_type" -> {
|
||||
val rememberDownloadType = pref as SwitchPreferenceCompat
|
||||
val downloadType = host.findPref("preferred_download_type")
|
||||
downloadType?.isEnabled = !rememberDownloadType.isChecked
|
||||
|
||||
rememberDownloadType.setOnPreferenceChangeListener { _, newValue ->
|
||||
downloadType?.isEnabled = !(newValue as Boolean)
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
"prevent_duplicate_downloads" -> {
|
||||
val archivePath = host.findPref("download_archive_path")
|
||||
pref.setOnPreferenceChangeListener { _, newValue ->
|
||||
archivePath?.isVisible = newValue == "download_archive"
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
"download_archive_path" -> {
|
||||
pref.summary = FileUtil.getDownloadArchivePath(context)
|
||||
pref.isVisible = preferences.getString("prevent_duplicate_downloads", "") == "download_archive"
|
||||
pref.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
host.activityResultDelegate.launch(intent) { result ->
|
||||
result.data?.data?.let {
|
||||
host.getHostContext().contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
val path = result.data!!.data.toString()
|
||||
preferences.edit(commit = true) {
|
||||
putString("download_archive_path", path)
|
||||
}
|
||||
host.refreshUI()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
"cleanup_leftover_downloads" -> {
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
pref.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var nextTime : Calendar? = Calendar.getInstance()
|
||||
when(newValue) {
|
||||
"daily" -> nextTime?.add(Calendar.DAY_OF_WEEK, 1)
|
||||
"weekly" -> nextTime?.add(Calendar.DAY_OF_WEEK, 7)
|
||||
"monthly" -> nextTime?.add(Calendar.MONTH, 1)
|
||||
else -> nextTime = null
|
||||
}
|
||||
|
||||
if (nextTime == null) workManager.cancelAllWorkByTag("cleanup_leftover_downloads")
|
||||
else {
|
||||
val workConstraints = Constraints.Builder()
|
||||
val allowMeteredNetworks = preferences.getBoolean("metered_networks", true)
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
||||
val delay = nextTime.timeInMillis.minus(System.currentTimeMillis())
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<CleanUpLeftoverDownloads>()
|
||||
.addTag("cleanup_leftover_downloads")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
"use_alarm_for_scheduling" -> {
|
||||
val scheduler = AlarmScheduler(context)
|
||||
pref.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var allowChange = true
|
||||
if (newValue as Boolean){
|
||||
if (!scheduler.canSchedule() && Build.VERSION.SDK_INT >= 31){
|
||||
Intent().also { intent ->
|
||||
intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
||||
context.startActivity(intent)
|
||||
}
|
||||
allowChange = false
|
||||
}
|
||||
}
|
||||
host.refreshUI()
|
||||
allowChange
|
||||
}
|
||||
}
|
||||
"use_scheduler" -> {
|
||||
val scheduler = AlarmScheduler(context)
|
||||
|
||||
val useScheduler = pref as SwitchPreferenceCompat
|
||||
useScheduler.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var allowChange = true
|
||||
if (newValue as Boolean){
|
||||
if (!scheduler.canSchedule() && Build.VERSION.SDK_INT >= 31){
|
||||
Intent().also { intent ->
|
||||
intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
|
||||
context.startActivity(intent)
|
||||
}
|
||||
allowChange = false
|
||||
}else{
|
||||
scheduler.schedule()
|
||||
}
|
||||
}else{
|
||||
scheduler.cancel()
|
||||
//start worker if there are leftover downloads waiting for scheduler
|
||||
val workConstraints = Constraints.Builder()
|
||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.addTag("download")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(1000L, TimeUnit.MILLISECONDS)
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
host.refreshUI()
|
||||
allowChange
|
||||
}
|
||||
}
|
||||
"schedule_start" -> {
|
||||
val scheduler = AlarmScheduler(context)
|
||||
|
||||
pref.summary = preferences.getString("schedule_start", "00:00")
|
||||
pref.setOnPreferenceClickListener {
|
||||
UiUtil.showTimePicker(host.requestGetParentFragmentManager(), preferences){
|
||||
val hr = it.get(Calendar.HOUR_OF_DAY)
|
||||
val mn = it.get(Calendar.MINUTE)
|
||||
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
|
||||
preferences.edit(commit = true) {
|
||||
putString("schedule_start", formattedTime)
|
||||
}
|
||||
pref.summary = formattedTime
|
||||
scheduler.schedule()
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
"schedule_end" -> {
|
||||
val scheduler = AlarmScheduler(context)
|
||||
|
||||
pref.summary = preferences.getString("schedule_end", "05:00")
|
||||
pref.setOnPreferenceClickListener {
|
||||
UiUtil.showTimePicker(host.requestGetParentFragmentManager(), preferences){
|
||||
val hr = it.get(Calendar.HOUR_OF_DAY)
|
||||
val mn = it.get(Calendar.MINUTE)
|
||||
val formattedTime = String.format("%02d", hr) + ":" + String.format("%02d", mn)
|
||||
preferences.edit(commit = true) {
|
||||
putString("schedule_end",formattedTime)
|
||||
}
|
||||
pref.summary = formattedTime
|
||||
scheduler.schedule()
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
"proxy" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
val s = context.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}]"
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"preferred_download_type" -> {
|
||||
(pref as ListPreference).apply {
|
||||
val s = context.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)]}]"
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"buffer_size" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
val s = context.getString(R.string.buffer_size_summary)
|
||||
summary = if (text.isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${text}]"
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
summary = if ((newValue as String?).isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${newValue}]"
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"socket_timeout" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
val s = context.getString(R.string.socket_timeout_description)
|
||||
summary = if (text.isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${text}]"
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
summary = if ((newValue as String?).isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${newValue}]"
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.folder
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.more.settings.BaseSettingsFragment
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsRegistry
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.MoveCacheFilesWorker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class FolderSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.directories
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
val preferenceXMLRes = R.xml.folders_preference
|
||||
setPreferencesFromResource(preferenceXMLRes, rootKey)
|
||||
SettingsRegistry.bindFragment(this, preferenceXMLRes)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
editor = preferences.edit()
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, preferenceXMLRes)
|
||||
requireActivity().recreate()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if((Build.VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||
Build.VERSION.SDK_INT < 30) {
|
||||
findPreference<Preference>("access_all_files")!!.isVisible = false
|
||||
findPreference<Preference>("cache_downloads")!!.isEnabled = true
|
||||
}else{
|
||||
editor.putBoolean("cache_downloads", true).apply()
|
||||
findPreference<Preference>("cache_downloads")!!.isEnabled = false
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.folder
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingHost
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.MoveCacheFilesWorker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import kotlin.collections.first
|
||||
|
||||
object FolderSettingsModule: SettingModule {
|
||||
|
||||
const val MUSIC_PATH_CODE = 33333
|
||||
const val VIDEO_PATH_CODE = 55555
|
||||
const val COMMAND_PATH_CODE = 77777
|
||||
const val CACHE_PATH_CODE = 99999
|
||||
|
||||
override fun bindLogic(
|
||||
pref: Preference,
|
||||
host: SettingHost
|
||||
) {
|
||||
val context = pref.context
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
var activeDownloadCount = 0
|
||||
val downloadViewModel = ViewModelProvider(host.hostViewModelStoreOwner)[DownloadViewModel::class.java]
|
||||
|
||||
when(pref.key) {
|
||||
"music_path" -> {
|
||||
if (preferences.getString(pref.key, "")!!.isEmpty()) {
|
||||
preferences.edit(commit = true) {
|
||||
putString(pref.key, FileUtil.getDefaultAudioPath())
|
||||
}
|
||||
}
|
||||
pref.apply {
|
||||
summary = FileUtil.formatPath(preferences.getString(pref.key, "")!!)
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
host.activityResultDelegate.launch(intent) { result ->
|
||||
result.data?.data?.let {
|
||||
host.getHostContext().contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(host,pref, result.data, MUSIC_PATH_CODE)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"video_path" -> {
|
||||
if (preferences.getString(pref.key, "")!!.isEmpty()) {
|
||||
preferences.edit(commit = true) {
|
||||
putString(pref.key, FileUtil.getDefaultVideoPath())
|
||||
}
|
||||
}
|
||||
pref.apply {
|
||||
summary = FileUtil.formatPath(preferences.getString(pref.key, "")!!)
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
host.activityResultDelegate.launch(intent) { result ->
|
||||
result.data?.data?.let {
|
||||
host.getHostContext().contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(host, pref, result.data, VIDEO_PATH_CODE)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"command_path" -> {
|
||||
if (preferences.getString(pref.key, "")!!.isEmpty()) {
|
||||
preferences.edit(commit = true) {
|
||||
putString(pref.key, FileUtil.getDefaultCommandPath())
|
||||
}
|
||||
}
|
||||
pref.apply {
|
||||
summary = FileUtil.formatPath(preferences.getString("command_path", "")!!)
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
host.activityResultDelegate.launch(intent) { result ->
|
||||
result.data?.data?.let {
|
||||
host.getHostContext().contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(host,pref, result.data, COMMAND_PATH_CODE)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"cache_path" -> {
|
||||
if (preferences.getString(pref.key, "")!!.isEmpty()) {
|
||||
preferences.edit(commit = true) {
|
||||
putString(pref.key, FileUtil.getCachePath(context))
|
||||
}
|
||||
}
|
||||
pref.apply {
|
||||
summary = FileUtil.formatPath(preferences.getString(pref.key, FileUtil.getCachePath(context))!!)
|
||||
isEnabled = (Build.VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||
Build.VERSION.SDK_INT < 30
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(context, context.getString(R.string.cache_directory), context.getString(
|
||||
R.string.cache_directory_warning)) {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
host.activityResultDelegate.launch(intent) { result ->
|
||||
result.data?.data?.let {
|
||||
host.getHostContext().contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(host, pref, result.data, CACHE_PATH_CODE)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"access_all_files" -> {
|
||||
pref.apply {
|
||||
if ((Build.VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||
Build.VERSION.SDK_INT < 30) {
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.parse("package:" + context.packageName)
|
||||
intent.data = uri
|
||||
host.getHostContext().startActivity(intent)
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"no_part" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
if(newValue as Boolean){
|
||||
preferences.edit(commit = true) {
|
||||
putBoolean("keep_cache", false)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"keep_cache" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
val noFragments = host.findPref("no_part") as SwitchPreferenceCompat
|
||||
if (noFragments.isChecked) {
|
||||
isEnabled = false
|
||||
isChecked = false
|
||||
} else {
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
"cache_downloads" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
if (FileUtil.hasAllFilesAccess()) {
|
||||
isEnabled = true
|
||||
} else {
|
||||
isEnabled = false
|
||||
preferences.edit(commit = true) {
|
||||
putBoolean(pref.key, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"file_name_template" -> {
|
||||
pref.apply {
|
||||
title = "${context.getString(R.string.file_name_template)} [${context.getString(R.string.video)}]"
|
||||
summary = preferences.getString(pref.key, "%(uploader).30B - %(title).170B")
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
UiUtil.showFilenameTemplateDialog(host.getHostContext(),pref.summary.toString(), "${context.getString(
|
||||
R.string.file_name_template)} [${context.getString(R.string.video)}]") {
|
||||
preferences.edit(commit = true) {
|
||||
putString(pref.key, it)
|
||||
}
|
||||
host.refreshUI()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
"file_name_template_audio" -> {
|
||||
pref.apply {
|
||||
title = "${context.getString(R.string.file_name_template)} [${context.getString(R.string.audio)}]"
|
||||
summary = preferences.getString(pref.key, "%(uploader).30B - %(title).170B")
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
UiUtil.showFilenameTemplateDialog(host.getHostContext(), pref.summary.toString(), "${context.getString(
|
||||
R.string.file_name_template)} [${context.getString(R.string.audio)}]") {
|
||||
preferences.edit(commit = true) {
|
||||
putString(pref.key, it)
|
||||
}
|
||||
host.refreshUI()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
"clear_cache" -> {
|
||||
pref.apply {
|
||||
val cacheSize = File(FileUtil.getCachePath(context)).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
val filesize = if (cacheSize < 10000) {
|
||||
"0B"
|
||||
}else {
|
||||
FileUtil.convertFileSize(cacheSize)
|
||||
}
|
||||
|
||||
summary = "${context.resources.getString(R.string.clear_temporary_files_summary)} (${filesize}) "
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
activeDownloadCount = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
}
|
||||
if (activeDownloadCount == 0){
|
||||
fun clearCacheFolder(folder: File) {
|
||||
if (folder.exists() && folder.isDirectory) {
|
||||
folder.listFiles()?.forEach { file ->
|
||||
if (file.isDirectory) {
|
||||
clearCacheFolder(file)
|
||||
file.delete()
|
||||
} else {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearCacheFolder(File(FileUtil.getCachePath(context)))
|
||||
|
||||
Snackbar.make(host.hostView!!, context.getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
||||
}else{
|
||||
Snackbar.make(host.hostView!!, context.getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
host.refreshUI()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"move_cache" -> {
|
||||
pref.apply {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val workRequest = OneTimeWorkRequestBuilder<MoveCacheFilesWorker>()
|
||||
.addTag("cacheFiles")
|
||||
.build()
|
||||
|
||||
WorkManager.Companion.getInstance(context).beginUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.KEEP,
|
||||
workRequest
|
||||
).enqueue()
|
||||
|
||||
WorkManager.Companion.getInstance(context)
|
||||
.getWorkInfosByTagLiveData("cacheFiles")
|
||||
.observe(host.hostLifecycleOwner){ list ->
|
||||
if (list == null) return@observe
|
||||
if (list.first() == null) return@observe
|
||||
|
||||
if (list.first().state == WorkInfo.State.SUCCEEDED){
|
||||
host.refreshUI()
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun changePath(host: SettingHost, p: Preference?, data: Intent?, requestCode: Int) {
|
||||
val path = data!!.data.toString()
|
||||
p!!.summary = FileUtil.formatPath(data.data.toString())
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(host.getHostContext())
|
||||
val editor = sharedPreferences.edit()
|
||||
when (requestCode) {
|
||||
MUSIC_PATH_CODE -> editor.putString("music_path", path)
|
||||
VIDEO_PATH_CODE -> editor.putString("video_path", path)
|
||||
COMMAND_PATH_CODE -> editor.putString("command_path", path)
|
||||
CACHE_PATH_CODE -> editor.putString("cache_path", path)
|
||||
}
|
||||
editor.apply()
|
||||
host.refreshUI()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.general
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.ui.more.settings.BaseSettingsFragment
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsRegistry
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
|
||||
class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.general
|
||||
private lateinit var preferences: SharedPreferences
|
||||
@SuppressLint("BatteryLife")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
val preferenceXMLRes = R.xml.general_preferences
|
||||
setPreferencesFromResource(preferenceXMLRes, rootKey)
|
||||
SettingsRegistry.bindFragment(this, preferenceXMLRes)
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val editor = preferences.edit()
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, preferenceXMLRes)
|
||||
ThemeUtil.updateThemes()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val packageName: String = requireContext().packageName
|
||||
val pm = requireContext().applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
if (pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
findPreference<Preference>("ignore_battery")?.isVisible = false
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,450 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.general
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.databinding.NavOptionsItemBinding
|
||||
import com.deniscerri.ytdl.ui.adapter.IconsSheetAdapter
|
||||
import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingHost
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Locale
|
||||
import kotlin.collections.forEach
|
||||
|
||||
object GeneralSettingsModule : SettingModule {
|
||||
override fun bindLogic(
|
||||
pref: Preference,
|
||||
host: SettingHost
|
||||
) {
|
||||
val context = pref.context
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
var activeDownloadCount = 0
|
||||
WorkManager.getInstance(context).getWorkInfosByTagLiveData("download").observe(host.hostLifecycleOwner){
|
||||
activeDownloadCount = 0
|
||||
it.forEach {w ->
|
||||
if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++
|
||||
}
|
||||
}
|
||||
|
||||
val resultViewModel = ViewModelProvider(host.hostViewModelStoreOwner)[ResultViewModel::class.java]
|
||||
|
||||
when(pref.key) {
|
||||
"app_language" -> {
|
||||
(pref as ListPreference).apply {
|
||||
value = Locale.getDefault().language
|
||||
summary = Locale.getDefault().displayLanguage
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == "system") {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(null))
|
||||
}else{
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
|
||||
}
|
||||
summary = Locale.getDefault().displayLanguage
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"label_visibility" -> {
|
||||
pref.apply {
|
||||
isVisible = !context.resources.getBoolean(R.bool.uses_side_nav)
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
ThemeUtil.recreateMain()
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"navigation_bar" -> {
|
||||
pref.apply {
|
||||
NavbarUtil.init(context)
|
||||
|
||||
isVisible = !context.resources.getBoolean(R.bool.uses_side_nav)
|
||||
if (isVisible) {
|
||||
summary = NavbarUtil.getNavBarItems(context).filter { it.isVisible }.map { it.title }.joinToString(", ")
|
||||
}
|
||||
setOnPreferenceClickListener {
|
||||
val binding = host.getHostContext().layoutInflater.inflate(R.layout.simple_options_recycler, null)
|
||||
val options = NavbarUtil.getNavBarItems(context)
|
||||
|
||||
val optionsRecycler = binding.findViewById<RecyclerView>(R.id.options_recycler)
|
||||
val adapter : NavBarOptionsAdapter?
|
||||
|
||||
val onItemClick = object: NavBarOptionsAdapter.OnItemClickListener {
|
||||
override fun onNavBarOptionDeselected(item: NavOptionsItemBinding) {
|
||||
optionsRecycler.findViewHolderForLayoutPosition(0)?.apply {
|
||||
(this as NavBarOptionsAdapter.NavBarOptionsViewHolder).apply {
|
||||
this.binding.home.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter = NavBarOptionsAdapter(
|
||||
options.toMutableList(),
|
||||
NavbarUtil.getStartFragmentId(context),
|
||||
onItemClick
|
||||
)
|
||||
|
||||
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
return makeMovementFlags(dragFlags, 0)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val itemToMove = adapter.items[viewHolder.absoluteAdapterPosition]
|
||||
adapter.items.remove(itemToMove)
|
||||
adapter.items.add(target.absoluteAdapterPosition, itemToMove)
|
||||
|
||||
adapter.notifyItemMoved(
|
||||
viewHolder.absoluteAdapterPosition,
|
||||
target.absoluteAdapterPosition
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
optionsRecycler.layoutManager = LinearLayoutManager(context)
|
||||
optionsRecycler.adapter = adapter
|
||||
|
||||
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
|
||||
itemTouchHelper.attachToRecyclerView(optionsRecycler)
|
||||
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.navigation_bar)
|
||||
.setView(binding)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
NavbarUtil.setNavBarItems(adapter.items, context)
|
||||
NavbarUtil.setStartFragment(adapter.selectedHomeTabId)
|
||||
summary = adapter.items.filter { it.isVisible }.map { it.title }.joinToString(", ")
|
||||
ThemeUtil.recreateMain()
|
||||
host.refreshUI()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"ytdlnis_theme" -> {
|
||||
(pref as ListPreference).apply {
|
||||
summary = entry
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
dialog.setTitle(context.getString(R.string.app_icon_change))
|
||||
dialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
dialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
summary = when(newValue){
|
||||
"System" -> {
|
||||
context.getString(R.string.system)
|
||||
}
|
||||
|
||||
"Dark" -> {
|
||||
context.getString(R.string.dark)
|
||||
}
|
||||
|
||||
else -> {
|
||||
context.getString(R.string.light)
|
||||
}
|
||||
}
|
||||
preferences.edit(commit = true){
|
||||
putString("ytdlnis_theme", newValue.toString())
|
||||
}
|
||||
ThemeUtil.updateThemes()
|
||||
host.refreshUI()
|
||||
}
|
||||
dialog.show()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
"ytdlnis_icon" -> {
|
||||
pref.apply {
|
||||
val currentValue = preferences.getString("ytdlnis_icon", "default")
|
||||
IconsSheetAdapter.availableIcons.firstOrNull { it.activityAlias == currentValue }?.let {
|
||||
summary = context.getString(it.nameResource)
|
||||
}
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
val bottomSheet = BottomSheetDialog(context)
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.generic_list)
|
||||
|
||||
val recycler = bottomSheet.findViewById<RecyclerView>(R.id.download_recyclerview)!!
|
||||
recycler.layoutManager = GridLayoutManager(context, 3)
|
||||
recycler.adapter = IconsSheetAdapter(host)
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
host.getHostContext().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
"theme_accent" -> {
|
||||
(pref as ListPreference).apply {
|
||||
summary = entry
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
ThemeUtil.updateThemes()
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"high_contrast" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
ThemeUtil.updateThemes()
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"show_terminal" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
setOnPreferenceChangeListener { pref, _ ->
|
||||
val packageManager = context.packageManager
|
||||
val aliasComponentName =
|
||||
ComponentName(context, "com.deniscerri.ytdl.terminalShareAlias")
|
||||
if ((pref as SwitchPreferenceCompat).isChecked){
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}else{
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"show_quick_download_share" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
setOnPreferenceChangeListener { pref, _ ->
|
||||
val packageManager = context.packageManager
|
||||
val aliasComponentName =
|
||||
ComponentName(context, "com.deniscerri.ytdl.quickDownloadShareAlias")
|
||||
if ((pref as SwitchPreferenceCompat).isChecked){
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}else{
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"display_over_apps" -> {
|
||||
(pref as SwitchPreferenceCompat).apply {
|
||||
isChecked = Settings.canDrawOverlays(context)
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
runCatching {
|
||||
val i = Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + context.packageName)
|
||||
)
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
host.getHostContext().startActivity(i)
|
||||
host.activityResultDelegate.launch(i) {
|
||||
host.refreshUI()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"ignore_battery" -> {
|
||||
pref.apply {
|
||||
setOnPreferenceClickListener {
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
||||
intent.data = Uri.parse("package:" + context.packageName)
|
||||
host.getHostContext().startActivity(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"hide_thumbnails" -> {
|
||||
(pref as MultiSelectListPreference).apply {
|
||||
values.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValues ->
|
||||
(newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"modify_download_card" -> {
|
||||
(pref as MultiSelectListPreference).apply {
|
||||
values.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValues ->
|
||||
(newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply {
|
||||
summary = joinToString(", ") { entries[entryValues.indexOf(it)] }
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"recommendations_home" -> {
|
||||
(pref as ListPreference).apply {
|
||||
val s = context.getString(R.string.video_recommendations_summary)
|
||||
summary = if (value.isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${entries[entryValues.indexOf(value)]}]"
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"custom_home_recommendation_url" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
title = "[${context.getString(R.string.video_recommendations)}] ${context.getString(R.string.custom)}"
|
||||
isVisible = preferences.getString("recommendations_home", "") == "custom"
|
||||
|
||||
setOnPreferenceChangeListener { preference, newValue ->
|
||||
host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"api_key" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
isVisible = preferences.getString("recommendations_home", "") == "yt_api"
|
||||
val s = context.getString(R.string.api_key_summary)
|
||||
summary = if (text.isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${text}]"
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"search_engine" -> {
|
||||
(pref as ListPreference).apply {
|
||||
val s = context.getString(R.string.preferred_search_engine_summary)
|
||||
summary = if (value.isNullOrBlank()) {
|
||||
s
|
||||
}else {
|
||||
"${s}\n[${entries[entryValues.indexOf(value)]}]"
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"swipe_gesture" -> {
|
||||
(pref as MultiSelectListPreference).apply {
|
||||
val s = context.getString(R.string.swipe_gestures_summary)
|
||||
if (values.size == entries.size) {
|
||||
summary = "${s}\n[${context.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[${context.getString(R.string.all)}]"
|
||||
}else if (newValues.isNotEmpty()) {
|
||||
val indexes = List(newValues.size) { index -> index }
|
||||
summary = "${s}\n[${entries.filterIndexed { index, _ -> indexes.contains(index) }.joinToString(", ")}]"
|
||||
}else{
|
||||
summary = s
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.processing
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.ui.more.settings.BaseSettingsFragment
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsRegistry
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
|
||||
class ProcessingSettingsFragment : BaseSettingsFragment() {
|
||||
override val title: Int = R.string.processing
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
val preferenceXMLRes = R.xml.processing_preferences
|
||||
setPreferencesFromResource(preferenceXMLRes, rootKey)
|
||||
SettingsRegistry.bindFragment(this, preferenceXMLRes)
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val editor = prefs.edit()
|
||||
|
||||
|
||||
findPreference<Preference>("reset_preferences")?.setOnPreferenceClickListener {
|
||||
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) {
|
||||
resetPreferences(editor, preferenceXMLRes)
|
||||
requireActivity().recreate()
|
||||
val fragmentId = findNavController().currentDestination?.id
|
||||
findNavController().popBackStack(fragmentId!!,true)
|
||||
findNavController().navigate(fragmentId)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.processing
|
||||
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingHost
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import kotlin.collections.indexOf
|
||||
|
||||
object ProcessingSettingsModule : SettingModule {
|
||||
override fun bindLogic(
|
||||
pref: Preference,
|
||||
host: SettingHost
|
||||
) {
|
||||
val context = pref.context
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
when(pref.key) {
|
||||
"format_id" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
title = "${context.getString(R.string.preferred_format_id)} [${context.getString(R.string.video)}]"
|
||||
dialogTitle = "${context.getString(R.string.preferred_format_id)} [${context.getString(R.string.video)}]"
|
||||
|
||||
val s = context.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}]"
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"format_id_audio" -> {
|
||||
(pref as EditTextPreference).apply {
|
||||
title = "${context.getString(R.string.preferred_format_id)} [${context.getString(R.string.audio)}]"
|
||||
dialogTitle = "${context.getString(R.string.preferred_format_id)} [${context.getString(R.string.audio)}]"
|
||||
|
||||
val s = context.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}]"
|
||||
}
|
||||
host.refreshUI()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"subs_lang" -> {
|
||||
pref.apply {
|
||||
summary = prefs.getString("subs_lang", "en.*,.*-orig")!!
|
||||
setOnPreferenceClickListener {
|
||||
UiUtil.showSubtitleLanguagesDialog(host.getHostContext(), listOf(), prefs.getString("subs_lang", "en.*,.*-orig")!!){
|
||||
prefs.edit(commit = true) {
|
||||
putString(pref.key, it)
|
||||
}
|
||||
summary = it
|
||||
host.refreshUI()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"audio_bitrate" -> {
|
||||
pref.apply {
|
||||
var currentValue = prefs.getString("audio_bitrate", "")!!
|
||||
val entries = context.resources.getStringArray(R.array.audio_bitrate)
|
||||
val entryValues = context.resources.getStringArray(R.array.audio_bitrate_values)
|
||||
|
||||
summary = if (currentValue.isNotBlank()) {
|
||||
entries[entryValues.indexOf(currentValue)]
|
||||
}else {
|
||||
context.getString(R.string.defaultValue)
|
||||
}
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
currentValue = prefs.getString("audio_bitrate", "")!!
|
||||
UiUtil.showAudioBitrateDialog(host.getHostContext(), currentValue) {
|
||||
prefs.edit(commit = true) {
|
||||
putString("audio_bitrate", it)
|
||||
}
|
||||
|
||||
summary = if (it.isNotBlank()) {
|
||||
entries[entryValues.indexOf(it)]
|
||||
}else {
|
||||
context.getString(R.string.defaultValue)
|
||||
}
|
||||
host.refreshUI()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"audio_codec" -> {
|
||||
updateCompatibleVideoConfig(host)
|
||||
}
|
||||
"video_codec" -> {
|
||||
updateCompatibleVideoConfig(host)
|
||||
}
|
||||
"video_format" -> {
|
||||
updateCompatibleVideoConfig(host)
|
||||
}
|
||||
"recode_video" -> {
|
||||
val compatibleVideoPreference = host.findPref("compatible_video") as SwitchPreferenceCompat
|
||||
|
||||
pref.setOnPreferenceClickListener {
|
||||
if (compatibleVideoPreference.isChecked && (pref as SwitchPreferenceCompat).isChecked) {
|
||||
compatibleVideoPreference.performClick()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
"compatible_video" -> {
|
||||
updateCompatibleVideoConfig(host)
|
||||
val prefSwitch = pref as SwitchPreferenceCompat
|
||||
pref.setOnPreferenceClickListener {
|
||||
if (prefSwitch.isChecked) {
|
||||
val recodeVideoPreference = host.findPref("recode_video") as SwitchPreferenceCompat
|
||||
|
||||
if (recodeVideoPreference.isChecked) {
|
||||
recodeVideoPreference.performClick()
|
||||
}
|
||||
|
||||
val audioCodecPref = host.findPref("audio_codec") as? ListPreference
|
||||
val videoCodecPref = host.findPref("video_codec") as? ListPreference
|
||||
val videoContainerPref = host.findPref("video_format") as? ListPreference
|
||||
|
||||
prefs.edit(commit = true) {
|
||||
putString("audio_codec_tmp", audioCodecPref?.value ?: "")
|
||||
putString("video_codec_tmp", videoCodecPref?.value ?: "")
|
||||
putString("video_format_tmp", videoContainerPref?.value ?: "")
|
||||
}
|
||||
|
||||
val audioCodecs = context.getStringArray(R.array.audio_codec)
|
||||
val audioCodecValues = context.getStringArray(R.array.audio_codec_values)
|
||||
val videoCodecs = context.getStringArray(R.array.video_codec)
|
||||
val videoCodecValues = context.getStringArray(R.array.video_codec_values)
|
||||
|
||||
val newAudioCodec = "M4A"
|
||||
val newVideoCodec = "AVC (H264)"
|
||||
|
||||
prefs.edit(commit = true) {
|
||||
putString("audio_codec", audioCodecValues[audioCodecs.indexOf(newAudioCodec)])
|
||||
putString("video_codec", videoCodecValues[videoCodecs.indexOf(newVideoCodec)])
|
||||
putString("video_format", "")
|
||||
}
|
||||
host.refreshUI()
|
||||
host.requestRecreateActivity()
|
||||
} else {
|
||||
prefs.edit(commit = true) {
|
||||
putString("audio_codec", prefs.getString("audio_codec_tmp", ""))
|
||||
putString("video_codec", prefs.getString("video_codec_tmp", ""))
|
||||
putString("video_format", prefs.getString("video_format_tmp", ""))
|
||||
}
|
||||
host.refreshUI()
|
||||
host.requestRecreateActivity()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCompatibleVideoConfig(host: SettingHost) {
|
||||
val audioCodecPref = host.findPref("audio_codec")
|
||||
val videoCodecPref = host.findPref("video_codec")
|
||||
val videoContainerPref = host.findPref("video_format")
|
||||
val compatibleVideoPreference = host.findPref("compatible_video") as SwitchPreferenceCompat
|
||||
|
||||
audioCodecPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
videoCodecPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
videoContainerPref?.isEnabled = !compatibleVideoPreference.isChecked
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,223 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.search
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.SeekBarPreference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.SearchSettingsItem
|
||||
import com.deniscerri.ytdl.ui.more.settings.DefaultPreferenceActions
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsRegistry
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
||||
class SettingsSearchAdapter(
|
||||
private var items: List<SearchSettingsItem>,
|
||||
private val activity: SettingsActivity
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun updateList(newList: List<SearchSettingsItem>) {
|
||||
items = newList
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TYPE_DEFAULT = 0
|
||||
private const val TYPE_SWITCH = 1
|
||||
private const val TYPE_SEEKBAR = 2
|
||||
private const val TYPE_HEADER = 3
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
if (items[position].isHeader) return TYPE_HEADER
|
||||
|
||||
return when (items[position].preference) {
|
||||
is SwitchPreferenceCompat -> TYPE_SWITCH
|
||||
is SeekBarPreference -> TYPE_SEEKBAR
|
||||
else -> TYPE_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
TYPE_HEADER -> {
|
||||
val view = inflater.inflate(R.layout.preference_search_result_title, parent, false)
|
||||
HeaderViewHolder(view)
|
||||
}
|
||||
TYPE_SWITCH -> {
|
||||
val view = inflater.inflate(R.layout.preference_search_result_switch, parent, false)
|
||||
SwitchViewHolder(view)
|
||||
}
|
||||
TYPE_SEEKBAR -> {
|
||||
val view = inflater.inflate(R.layout.preference_search_result_seekbar, parent, false)
|
||||
SeekbarViewHolder(view)
|
||||
}
|
||||
else -> {
|
||||
val view = inflater.inflate(R.layout.preference_search_result_regular, parent, false)
|
||||
DefaultViewHolder(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: RecyclerView.ViewHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>
|
||||
) {
|
||||
if (payloads.contains("SKIP_BIND_LOGIC")) {
|
||||
bindVisualsOnly(holder, position)
|
||||
} else {
|
||||
super.onBindViewHolder(holder, position, payloads)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
bindVisualsOnly(holder, position)
|
||||
}
|
||||
|
||||
private fun bindVisualsOnly(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
val pref = item.preference
|
||||
if (pref.title.isNullOrBlank())
|
||||
|
||||
if (!item.isHeader && item.canRebind) {
|
||||
item.module?.bindLogic(pref, activity)
|
||||
}
|
||||
|
||||
// Handle Visuals (Dependencies)
|
||||
holder.itemView.isEnabled = pref.isEnabled
|
||||
holder.itemView.alpha = if (pref.isEnabled) 1.0f else 0.5f
|
||||
|
||||
holder.itemView.setOnLongClickListener {
|
||||
val bundle = Bundle().apply {
|
||||
putString("highlight_key", pref.key)
|
||||
}
|
||||
|
||||
val destinationId = activity.getDestinationIdForXml(item.xmlId)
|
||||
val navHostFragment = activity.supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment
|
||||
val navController = navHostFragment.findNavController()
|
||||
activity.closeSearchView()
|
||||
navController.navigate(destinationId, bundle)
|
||||
true
|
||||
}
|
||||
|
||||
// Bind the UI
|
||||
when (holder) {
|
||||
is HeaderViewHolder -> {
|
||||
holder.title.text = item.groupTitle
|
||||
}
|
||||
is SwitchViewHolder -> {
|
||||
holder.switchWidget.setOnCheckedChangeListener(null)
|
||||
|
||||
holder.title.text = pref.title
|
||||
holder.summary.text = pref.summary
|
||||
holder.summary.isVisible = !pref.summary.isNullOrBlank()
|
||||
holder.switchWidget.isChecked = (pref as SwitchPreferenceCompat).isChecked
|
||||
holder.switchWidget.isFocusable = pref.isEnabled
|
||||
holder.switchWidget.isClickable = pref.isEnabled
|
||||
holder.icon.isVisible = pref.icon != null
|
||||
pref.icon?.apply { holder.icon.icon = this }
|
||||
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
holder.switchWidget.performClick()
|
||||
}
|
||||
holder.switchWidget.setOnCheckedChangeListener { _, isChecked ->
|
||||
pref.callChangeListener(isChecked)
|
||||
pref.isChecked = isChecked
|
||||
activity.refreshUI()
|
||||
}
|
||||
}
|
||||
is SeekbarViewHolder -> {
|
||||
holder.title.text = pref.title
|
||||
holder.summary.text = pref.summary
|
||||
holder.summary.isVisible = !pref.summary.isNullOrBlank()
|
||||
holder.icon.isVisible = pref.icon != null
|
||||
pref.icon?.apply { holder.icon.icon = this }
|
||||
|
||||
holder.seekbar.max = (pref as SeekBarPreference).max
|
||||
holder.seekbar.progress = pref.value
|
||||
holder.seekbarValue.text = pref.value.toString()
|
||||
holder.seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
holder.seekbarValue.text = progress.toString()
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {
|
||||
}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {
|
||||
seekBar?.apply {
|
||||
pref.value = progress
|
||||
pref.callChangeListener(progress)
|
||||
holder.seekbarValue.text = progress.toString()
|
||||
activity.refreshUI()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
is DefaultViewHolder -> {
|
||||
holder.title.text = pref.title
|
||||
holder.summary.text = pref.summary
|
||||
holder.summary.isVisible = !pref.summary.isNullOrBlank()
|
||||
holder.icon.isVisible = pref.icon != null
|
||||
pref.icon?.apply { holder.icon.icon = this }
|
||||
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
if (pref.onPreferenceClickListener == null || item.module == null) {
|
||||
val didLaunchDialog = DefaultPreferenceActions.onPreferenceDisplayDialog( activity, pref) {
|
||||
activity.refreshUI()
|
||||
}
|
||||
if (!didLaunchDialog) {
|
||||
holder.itemView.performLongClick()
|
||||
}
|
||||
} else {
|
||||
pref.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val title: TextView = view.findViewById(R.id.preference_title)
|
||||
}
|
||||
|
||||
class SwitchViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val title: TextView = view.findViewById(R.id.preference_title)
|
||||
val summary: TextView = view.findViewById(R.id.preference_summary)
|
||||
val switchWidget: SwitchCompat = view.findViewById(R.id.preference_switch)
|
||||
var icon: MaterialButton = view.findViewById(R.id.preference_icon)
|
||||
}
|
||||
|
||||
class DefaultViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val title: TextView = view.findViewById(R.id.preference_title)
|
||||
val summary: TextView = view.findViewById(R.id.preference_summary)
|
||||
var icon: MaterialButton = view.findViewById(R.id.preference_icon)
|
||||
}
|
||||
|
||||
class SeekbarViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val title: TextView = view.findViewById(R.id.preference_title)
|
||||
val summary: TextView = view.findViewById(R.id.preference_summary)
|
||||
var icon: MaterialButton = view.findViewById(R.id.preference_icon)
|
||||
var seekbar: SeekBar = view.findViewById(R.id.seekBar)
|
||||
var seekbarValue: TextView = view.findViewById(R.id.seekbarValue)
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
package com.deniscerri.ytdl.ui.more.settings.updating
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.BuildConfig
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.SettingsViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingModule
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingHost
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
object UpdateSettingsModule : SettingModule {
|
||||
override fun bindLogic(pref: Preference,host: SettingHost) {
|
||||
val context = pref.context
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val updateUtil = UpdateUtil(context)
|
||||
val ytdlpViewModel = ViewModelProvider(host.hostViewModelStoreOwner)[YTDLPViewModel::class.java]
|
||||
val settingsViewModel = ViewModelProvider(host.hostViewModelStoreOwner)[SettingsViewModel::class.java]
|
||||
when(pref.key) {
|
||||
"ytdlp_source_label" -> {
|
||||
pref.apply {
|
||||
summary = preferences.getString("ytdlp_source_label", "")!!.ifEmpty { context.getString(R.string.update_ytdl_stable) }
|
||||
setOnPreferenceClickListener {
|
||||
UiUtil.showYTDLSourceBottomSheet(host.getHostContext(), preferences) { t, r ->
|
||||
summary = t
|
||||
preferences.edit().putString("ytdlp_source", r).apply()
|
||||
preferences.edit().putString("ytdlp_source_label", t).apply()
|
||||
val ytdlVersionPreference = host.findPref("ytdl-version")!!
|
||||
initYTDLUpdate(context, host, updateUtil, ytdlpViewModel, preferences, ytdlVersionPreference)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
"ytdl-version" -> {
|
||||
pref.apply {
|
||||
host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
summary = context.getString(R.string.loading)
|
||||
summary = withContext(Dispatchers.IO){
|
||||
ytdlpViewModel.getVersion(preferences.getString("ytdlp_source", "stable")!!)
|
||||
}
|
||||
if (summary?.isBlank() == true) {
|
||||
setYTDLPVersion(context, host, ytdlpViewModel, preferences, pref)
|
||||
}
|
||||
setOnPreferenceClickListener {
|
||||
initYTDLUpdate(context, host, updateUtil, ytdlpViewModel, preferences, pref)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"update_ytdl" -> {
|
||||
pref.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val ytdlVersionPreference = host.findPref("ytdl-version")!!
|
||||
initYTDLUpdate(context, host, updateUtil, ytdlpViewModel, preferences, ytdlVersionPreference)
|
||||
true
|
||||
}
|
||||
}
|
||||
"changelog" -> {
|
||||
pref.setOnPreferenceClickListener {
|
||||
host.requestNavigate(R.id.changeLogFragment)
|
||||
false
|
||||
}
|
||||
}
|
||||
"packages" -> {
|
||||
pref.apply {
|
||||
summary = "Python, FFmpeg, Aria2c, NodeJS"
|
||||
setOnPreferenceClickListener {
|
||||
host.requestNavigate(R.id.packagesFragment)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
"version" -> {
|
||||
pref.apply {
|
||||
val nativeLibraryDir = context.applicationInfo?.nativeLibraryDir
|
||||
summary = "${BuildConfig.VERSION_NAME} (${nativeLibraryDir?.split("/lib/")?.get(1)})"
|
||||
|
||||
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
host.hostLifecycleOwner.lifecycleScope.launch{
|
||||
val updateUtil = UpdateUtil(context)
|
||||
val res = withContext(Dispatchers.IO){
|
||||
updateUtil.tryGetNewVersion()
|
||||
}
|
||||
if (res.isFailure) {
|
||||
Snackbar.make(host.hostView!!, res.exceptionOrNull()?.message ?: context.getString(R.string.network_error), Snackbar.LENGTH_LONG).show()
|
||||
}else{
|
||||
if (preferences.getBoolean("automatic_backup", false)) {
|
||||
withContext(Dispatchers.IO){
|
||||
settingsViewModel.backup()
|
||||
}
|
||||
}
|
||||
UiUtil.showNewAppUpdateDialog(res.getOrNull()!!, host.getHostContext(), preferences)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setYTDLPVersion(
|
||||
context: Context,
|
||||
host: SettingHost,
|
||||
ytdlpViewModel: YTDLPViewModel,
|
||||
preferences: SharedPreferences,
|
||||
pref: Preference
|
||||
) {
|
||||
host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
pref.summary = context.getString(R.string.loading)
|
||||
val version = withContext(Dispatchers.IO){
|
||||
ytdlpViewModel.getVersion(preferences.getString("ytdlp_source", "stable")!!)
|
||||
}
|
||||
preferences.edit(commit = true) {
|
||||
putString("ytdl-version", version)
|
||||
}
|
||||
pref.summary = version
|
||||
host.refreshUI()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initYTDLUpdate(
|
||||
context: Context,
|
||||
host: SettingHost,
|
||||
updateUtil: UpdateUtil,
|
||||
ytdlpViewModel: YTDLPViewModel,
|
||||
preferences: SharedPreferences,
|
||||
ytdlVersionPreference: Preference,
|
||||
channel: String? = null
|
||||
) = host.hostLifecycleOwner.lifecycleScope.launch {
|
||||
val view = host.hostView!!
|
||||
|
||||
Snackbar.make(view, context.getString(R.string.ytdl_updating_started),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
runCatching {
|
||||
val res = updateUtil.updateYTDL(channel)
|
||||
when (res.status) {
|
||||
UpdateUtil.YTDLPUpdateStatus.DONE -> {
|
||||
Snackbar.make(view, res.message, Snackbar.LENGTH_LONG).show()
|
||||
setYTDLPVersion(context, host, ytdlpViewModel, preferences, ytdlVersionPreference)
|
||||
val infoJsonPath = FileUtil.getInfoJsonPath(context)
|
||||
File(infoJsonPath).deleteRecursively()
|
||||
}
|
||||
UpdateUtil.YTDLPUpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(view,
|
||||
context.getString(R.string.you_are_in_latest_version),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
UpdateUtil.YTDLPUpdateStatus.ERROR -> {
|
||||
val msg = res.message
|
||||
view.apply {
|
||||
val snackBar = Snackbar.make(this, msg, Snackbar.LENGTH_LONG)
|
||||
snackBar.setAction(R.string.copy_log){
|
||||
UiUtil.copyToClipboard(msg, host.getHostContext())
|
||||
}
|
||||
val snackbarView: View = snackBar.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
snackBar.show()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
val msg = it.message ?: context.getString(R.string.errored)
|
||||
view.apply {
|
||||
val snackBar = Snackbar.make(this, msg, Snackbar.LENGTH_LONG)
|
||||
snackBar.setAction(R.string.copy_log){
|
||||
UiUtil.copyToClipboard(msg, host.getHostContext())
|
||||
}
|
||||
val snackbarView: View = snackBar.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
snackBar.show()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/preference_icon"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:outlineProvider="none"
|
||||
android:stateListAnimator="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
app:iconTint="?android:colorAccent"
|
||||
android:visibility="gone"
|
||||
app:iconSize="25dp"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="10"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/preference_icon"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:outlineProvider="none"
|
||||
android:stateListAnimator="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
app:iconTint="?android:colorAccent"
|
||||
android:visibility="gone"
|
||||
app:iconSize="25dp"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="10"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:padding="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/seekbarValue"
|
||||
android:max="100" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/seekbarValue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingVertical="16dp"
|
||||
android:minHeight="48dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/preference_icon"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:outlineProvider="none"
|
||||
android:stateListAnimator="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="gone"
|
||||
app:iconTint="?android:colorAccent"
|
||||
app:iconSize="25dp"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:ellipsize="end"
|
||||
android:textStyle="bold"
|
||||
android:maxLines="2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_summary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="10"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/preference_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:focusable="false"
|
||||
android:clickable="false" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingHorizontal="50dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:textSize="13sp"
|
||||
tools:text="Title"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:theme="@style/BaseTheme">
|
||||
|
||||
<item
|
||||
android:id="@+id/search"
|
||||
android:visible="false"
|
||||
android:title="@string/search"
|
||||
android:icon="@drawable/ic_search"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
Loading…
Reference in New Issue