added multiple pages to settings & more...

Dismiss download multiple sheet when all items are dismissed
Add backup for history, queued downloads, cancelled downloads, cookies, shortcuts, templates
Add long click format to show details in audio video tab
removed check icon from checked state in material cards
Implement format fetching on playlist from invidious
Invert selected items in playlist bottom sheet
Make filename templates for both audio and video and add them as a chip on both tabs
Fixed errors when there is no internet and u ask for the latest version of the app
Fixed "default" format error
This commit is contained in:
deniscerri 2023-05-13 22:17:43 +02:00
parent 510798d492
commit 880abd503e
No known key found for this signature in database
GPG key ID: 95C43D517D830350
62 changed files with 2242 additions and 1324 deletions

View file

@ -58,7 +58,7 @@ android {
buildTypes { buildTypes {
release { release {
minifyEnabled true minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable false debuggable false
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
@ -178,4 +178,5 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation "com.google.android.exoplayer:exoplayer:2.18.6" implementation "com.google.android.exoplayer:exoplayer:2.18.6"
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4' implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
} }

View file

@ -61,7 +61,6 @@
public <init>(android.content.Context,androidx.work.WorkerParameters); public <init>(android.content.Context,androidx.work.WorkerParameters);
} }
-dontobfuscate
-dontwarn com.google.android.exoplayer2.** -dontwarn com.google.android.exoplayer2.**
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. # Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken

View file

@ -29,15 +29,8 @@ class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this
createNotificationChannels() createNotificationChannels()
PreferenceManager.setDefaultValues(
this,
"root_preferences",
MODE_PRIVATE,
R.xml.root_preferences,
false
)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
applicationScope = CoroutineScope(SupervisorJob()) applicationScope = CoroutineScope(SupervisorJob())
@ -80,5 +73,6 @@ class App : Application() {
companion object { companion object {
private const val TAG = "App" private const val TAG = "App"
private lateinit var applicationScope: CoroutineScope private lateinit var applicationScope: CoroutineScope
lateinit var instance: App
} }
} }

View file

@ -123,7 +123,6 @@ class MainActivity : BaseActivity() {
startActivity(intent) startActivity(intent)
} }
R.id.moreFragment -> { R.id.moreFragment -> {
//navController.navigate(R.id.settingsFragment)
val intent = Intent(context, SettingsActivity::class.java) val intent = Intent(context, SettingsActivity::class.java)
startActivity(intent) startActivity(intent)
} }

View file

@ -17,6 +17,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import java.util.* import java.util.*
@ -119,6 +120,16 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
} }
} }
fun invertSelected(items: List<ResultItem?>?){
val invertedList = mutableListOf<String>()
items?.forEach {
if (!checkedItems.contains(it!!.url)) invertedList.add(it.url)
}
checkedItems.clear()
checkedItems.addAll(invertedList)
notifyDataSetChanged()
}
fun checkRange(start: Int, end: Int){ fun checkRange(start: Int, end: Int){
checkedItems.clear() checkedItems.clear()
if (start == end ){ if (start == end ){

View file

@ -3,18 +3,19 @@ package com.deniscerri.ytdlnis.database.dao
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface DownloadDao { interface DownloadDao {
@Query("SELECT * FROM downloads ORDER BY status") @Query("SELECT * FROM downloads ORDER BY status")
fun getAllDownloads() : LiveData<List<DownloadItem>> fun getAllDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloads() : LiveData<List<DownloadItem>> fun getActiveDownloads() : Flow<List<DownloadItem>>
@Query("SELECT COUNT(*) FROM downloads WHERE status='Active'") @Query("SELECT COUNT(*) FROM downloads WHERE status='Active'")
fun getActiveDownloadsCount() : LiveData<Int> fun getActiveDownloadsCount() : Flow<Int>
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloadsList() : List<DownloadItem> fun getActiveDownloadsList() : List<DownloadItem>
@ -23,16 +24,22 @@ interface DownloadDao {
fun getActiveAndQueuedDownloadsList() : List<DownloadItem> fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
fun getQueuedDownloads() : LiveData<List<DownloadItem>> fun getQueuedDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
fun getQueuedDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC") @Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
fun getCancelledDownloads() : LiveData<List<DownloadItem>> fun getCancelledDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
fun getCancelledDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC") @Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
fun getErroredDownloads() : LiveData<List<DownloadItem>> fun getErroredDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC") @Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC")
fun getProcessingDownloads() : LiveData<List<DownloadItem>> fun getProcessingDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1") @Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
fun getDownloadById(id: Long) : DownloadItem fun getDownloadById(id: Long) : DownloadItem

View file

@ -29,6 +29,9 @@ interface HistoryDao {
@Query("SELECT * FROM history") @Query("SELECT * FROM history")
fun getAllHistory() : LiveData<List<HistoryItem>> fun getAllHistory() : LiveData<List<HistoryItem>>
@Query("SELECT * FROM history")
fun getAllHistoryList() : List<HistoryItem>
@Query("SELECT * FROM history WHERE id=:id LIMIT 1") @Query("SELECT * FROM history WHERE id=:id LIMIT 1")
suspend fun getHistoryItem(id: Int) : HistoryItem suspend fun getHistoryItem(id: Int) : HistoryItem

View file

@ -3,14 +3,15 @@ package com.deniscerri.ytdlnis.database.dao
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface ResultDao { interface ResultDao {
@Query("SELECT * FROM results") @Query("SELECT * FROM results")
fun getResults() : LiveData<List<ResultItem>> fun getResults() : Flow<List<ResultItem>>
@Query("SELECT COUNT(id) FROM results") @Query("SELECT COUNT(id) FROM results")
fun getCount() : LiveData<Int> fun getCount() : Flow<Int>
@Query("SELECT COUNT(id) FROM results") @Query("SELECT COUNT(id) FROM results")
fun getCountInt() :Int fun getCountInt() :Int

View file

@ -3,5 +3,5 @@ package com.deniscerri.ytdlnis.database.models
data class AudioPreferences( data class AudioPreferences(
var embedThumb: Boolean = true, var embedThumb: Boolean = true,
var splitByChapters: Boolean = false, var splitByChapters: Boolean = false,
var sponsorBlockFilters: ArrayList<String> var sponsorBlockFilters: ArrayList<String> = arrayListOf()
) )

View file

@ -4,7 +4,7 @@ data class VideoPreferences (
var embedSubs: Boolean = true, var embedSubs: Boolean = true,
var addChapters: Boolean = true, var addChapters: Boolean = true,
var splitByChapters: Boolean = false, var splitByChapters: Boolean = false,
var sponsorBlockFilters: ArrayList<String>, var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
var writeSubs: Boolean = false, var writeSubs: Boolean = false,
var subsLanguages: String = "en.*,.*-orig", var subsLanguages: String = "en.*,.*-orig",
var removeAudio: Boolean = false var removeAudio: Boolean = false

View file

@ -5,15 +5,16 @@ import com.deniscerri.ytdlnis.database.Converters
import com.deniscerri.ytdlnis.database.dao.DownloadDao import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import kotlinx.coroutines.flow.Flow
class DownloadRepository(private val downloadDao: DownloadDao) { class DownloadRepository(private val downloadDao: DownloadDao) {
val allDownloads : LiveData<List<DownloadItem>> = downloadDao.getAllDownloads() val allDownloads : Flow<List<DownloadItem>> = downloadDao.getAllDownloads()
val activeDownloads : LiveData<List<DownloadItem>> = downloadDao.getActiveDownloads() val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads()
val activeDownloadsCount : LiveData<Int> = downloadDao.getActiveDownloadsCount() val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCount()
val queuedDownloads : LiveData<List<DownloadItem>> = downloadDao.getQueuedDownloads() val queuedDownloads : Flow<List<DownloadItem>> = downloadDao.getQueuedDownloads()
val cancelledDownloads : LiveData<List<DownloadItem>> = downloadDao.getCancelledDownloads() val cancelledDownloads : Flow<List<DownloadItem>> = downloadDao.getCancelledDownloads()
val erroredDownloads : LiveData<List<DownloadItem>> = downloadDao.getErroredDownloads() val erroredDownloads : Flow<List<DownloadItem>> = downloadDao.getErroredDownloads()
val processingDownloads : LiveData<List<DownloadItem>> = downloadDao.getProcessingDownloads() val processingDownloads : Flow<List<DownloadItem>> = downloadDao.getProcessingDownloads()
enum class Status { enum class Status {
Active, Queued, Error, Processing, Cancelled Active, Queued, Error, Processing, Cancelled
@ -53,6 +54,14 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
return downloadDao.getActiveAndQueuedDownloadsList() return downloadDao.getActiveAndQueuedDownloadsList()
} }
fun getQueuedDownloads() : List<DownloadItem> {
return downloadDao.getQueuedDownloadsList();
}
fun getCancelledDownloads() : List<DownloadItem> {
return downloadDao.getCancelledDownloadsList()
}
suspend fun deleteCancelled(){ suspend fun deleteCancelled(){
downloadDao.deleteCancelled() downloadDao.deleteCancelled()
} }

View file

@ -19,6 +19,10 @@ class HistoryRepository(private val historyDao: HistoryDao) {
return historyDao.getHistoryItem(id) return historyDao.getHistoryItem(id)
} }
fun getAll() : List<HistoryItem> {
return historyDao.getAllHistoryList()
}
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) : List<HistoryItem> { fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: HistorySort) : List<HistoryItem> {
return when(sortType){ return when(sortType){
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString()) HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())

View file

@ -9,10 +9,11 @@ import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.CommandTemplate import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import kotlinx.coroutines.flow.Flow
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) { class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
private val tag: String = "ResultRepository" private val tag: String = "ResultRepository"
val allResults : LiveData<List<ResultItem>> = resultDao.getResults() val allResults : Flow<List<ResultItem>> = resultDao.getResults()
var itemCount = MutableLiveData(-1) var itemCount = MutableLiveData(-1)
suspend fun insert(it: ResultItem){ suspend fun insert(it: ResultItem){

View file

@ -8,6 +8,7 @@ import android.net.ConnectivityManager
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.* import androidx.work.*
@ -52,13 +53,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
allDownloads = repository.allDownloads allDownloads = repository.allDownloads.asLiveData()
queuedDownloads = repository.queuedDownloads queuedDownloads = repository.queuedDownloads.asLiveData()
activeDownloads = repository.activeDownloads activeDownloads = repository.activeDownloads.asLiveData()
activeDownloadsCount = repository.activeDownloadsCount activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
processingDownloads = repository.processingDownloads processingDownloads = repository.processingDownloads.asLiveData()
cancelledDownloads = repository.cancelledDownloads cancelledDownloads = repository.cancelledDownloads.asLiveData()
erroredDownloads = repository.erroredDownloads erroredDownloads = repository.erroredDownloads.asLiveData()
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString() videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
formatIDPreference = sharedPreferences.getString("format_id", "").toString() formatIDPreference = sharedPreferences.getString("format_id", "").toString()
@ -114,7 +115,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val addChapters = sharedPreferences.getBoolean("add_chapters", false) val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val customFileNameTemplate = sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") val customFileNameTemplate = when(type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s")
else -> ""
}
val downloadPath = when(type){ val downloadPath = when(type){
Type.audio -> sharedPreferences.getString("music_path", getApplication<App>().resources.getString(R.string.music_path)) Type.audio -> sharedPreferences.getString("music_path", getApplication<App>().resources.getString(R.string.music_path))
@ -205,7 +210,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val addChapters = sharedPreferences.getBoolean("add_chapters", false) val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val customFileNameTemplate = sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s")
else -> ""
}
val downloadPath = when(historyItem.type){ val downloadPath = when(historyItem.type){
Type.audio -> sharedPreferences.getString("music_path", getApplication<App>().resources.getString(R.string.music_path)) Type.audio -> sharedPreferences.getString("music_path", getApplication<App>().resources.getString(R.string.music_path))
@ -345,6 +354,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
repository.cancelQueued() repository.cancelQueued()
} }
fun getQueued() : List<DownloadItem> {
return repository.getQueuedDownloads()
}
fun getCancelled() : List<DownloadItem> {
return repository.getCancelledDownloads()
}
private fun cloneFormat(item: Format) : Format { private fun cloneFormat(item: Format) : Format {
val string = Gson().toJson(item, Format::class.java) val string = Gson().toJson(item, Format::class.java)
return Gson().fromJson(string, Format::class.java) return Gson().fromJson(string, Format::class.java)

View file

@ -48,7 +48,6 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
return _items return _items
} }
fun setSorting(sort: HistorySortType){ fun setSorting(sort: HistorySortType){
if (sortType.value != sort){ if (sortType.value != sort){
sortOrder.value = HistorySort.DESC sortOrder.value = HistorySort.DESC
@ -76,6 +75,10 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
_items.postValue(repository.getFiltered(query, format, site, sortType, sort)) _items.postValue(repository.getFiltered(query, format, site, sortType, sort))
} }
fun getAll() : List<HistoryItem> {
return repository.getAll()
}
fun insert(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){ fun insert(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){
repository.insert(item) repository.insert(item)
} }

View file

@ -7,6 +7,7 @@ import android.util.Log
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.App import com.deniscerri.ytdlnis.App
@ -35,7 +36,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
val commandDao = DBManager.getInstance(application).commandTemplateDao val commandDao = DBManager.getInstance(application).commandTemplateDao
repository = ResultRepository(dao, commandDao, getApplication<Application>().applicationContext) repository = ResultRepository(dao, commandDao, getApplication<Application>().applicationContext)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao) searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults items = repository.allResults.asLiveData()
loadingItems.postValue(false) loadingItems.postValue(false)
itemCount = repository.itemCount itemCount = repository.itemCount
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)

View file

@ -21,12 +21,14 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.DefaultLoadControl import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem.fromUri import com.google.android.exoplayer2.MediaItem.fromUri
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo import com.google.android.exoplayer2.mediacodec.MediaCodecInfo
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
@ -131,9 +133,18 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
val trackSelector = DefaultTrackSelector(requireContext()) val trackSelector = DefaultTrackSelector(requireContext())
val loadControl = DefaultLoadControl() val loadControl = DefaultLoadControl()
player = ExoPlayer.Builder(requireContext(), renderersFactory) player = ExoPlayer.Builder(requireContext())
.setUsePlatformDiagnostics(false)
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.setLoadControl(loadControl) .setLoadControl(loadControl)
.setHandleAudioBecomingNoisy(true)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
.build()
,
true)
.build() .build()
val frame = view.findViewById<MaterialCardView>(R.id.frame_layout) val frame = view.findViewById<MaterialCardView>(R.id.frame_layout)
val videoView = view.findViewById<StyledPlayerView>(R.id.video_view) val videoView = view.findViewById<StyledPlayerView>(R.id.video_view)
@ -207,9 +218,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
progress.visibility = View.GONE progress.visibility = View.GONE
populateSuggestedChapters() populateSuggestedChapters()
player.prepare().apply { player.prepare()
player.play() player.play()
}
}catch (e: Exception){ }catch (e: Exception){
progress.visibility = View.GONE progress.visibility = View.GONE
frame.visibility = View.GONE frame.visibility = View.GONE

View file

@ -3,18 +3,25 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView import android.widget.AutoCompleteTextView
import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -142,10 +149,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) { override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) {
downloadItem.format = item.first() downloadItem.format = item.first()
uiUtil.populateFormatCard(formatCard, item.first()) uiUtil.populateFormatCard(formatCard, item.first())
if (containers.contains(item.first().container)){
downloadItem.format.container = item.first().container
containerAutoCompleteTextView.setText(item.first().container, false)
}
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
resultItem.formats.removeAll(formats.toSet()) resultItem.formats.removeAll(formats.toSet())
@ -162,6 +165,10 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
bottomSheet.show(parentFragmentManager, "formatSheet") bottomSheet.show(parentFragmentManager, "formatSheet")
} }
} }
formatCard.setOnLongClickListener {
uiUtil.showFormatDetails(downloadItem.format, requireActivity())
true
}
container?.isEnabled = true container?.isEnabled = true
@ -173,12 +180,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
) )
) )
downloadItem.format.container = containerPreference!! downloadItem.format.container = if (containerPreference == getString(R.string.defaultValue)) "" else containerPreference!!
containerAutoCompleteTextView.setText(downloadItem.format.container, false) containerAutoCompleteTextView.setText(downloadItem.format.container.ifEmpty { getString(R.string.defaultValue) }, false)
(container!!.editText as AutoCompleteTextView?)!!.onItemClickListener = (container!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
downloadItem.format.container = containers[index] downloadItem.format.container = containers[index]
if (containers[index] == getString(R.string.defaultValue)) downloadItem.format.container = ""
} }
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb) val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
@ -199,6 +207,41 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
downloadItem.audioPreferences.splitByChapters = splitByChapters.isChecked downloadItem.audioPreferences.splitByChapters = splitByChapters.isChecked
} }
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
filenameTemplate.setOnClickListener {
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.file_name_template))
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = getString(R.string.file_name_template)
editText.setText(downloadItem.customFileNameTemplate)
editText.setSelection(editText.text.length)
builder.setView(inputLayout)
builder.setPositiveButton(
getString(R.string.ok)
) { dialog: DialogInterface?, which: Int ->
downloadItem.customFileNameTemplate = editText.text.toString()
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
getString(R.string.cancel)
) { dialog: DialogInterface?, which: Int -> }
val dialog = builder.create()
editText.doOnTextChanged { text, start, before, count ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
}
dialog.show()
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
val sponsorblock = view.findViewById<Chip>(R.id.sponsorblock_filters) val sponsorblock = view.findViewById<Chip>(R.id.sponsorblock_filters)
sponsorblock!!.setOnClickListener { sponsorblock!!.setOnClickListener {
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())

View file

@ -71,7 +71,9 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
behavior = BottomSheetBehavior.from(view.parent as View) behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.state = BottomSheetBehavior.STATE_EXPANDED if(resources.getBoolean(R.bool.isTablet)){
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
} }

View file

@ -79,7 +79,9 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
behavior = BottomSheetBehavior.from(view.parent as View) behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.state = BottomSheetBehavior.STATE_EXPANDED if(resources.getBoolean(R.bool.isTablet)){
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
} }
listAdapter = listAdapter =
@ -409,11 +411,16 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
val deletedItem = items[position] val deletedItem = items[position]
items.remove(deletedItem) items.remove(deletedItem)
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG) if (items.isNotEmpty()){
.setAction(getString(R.string.undo)) { Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
items.add(position, deletedItem) .setAction(getString(R.string.undo)) {
listAdapter.submitList(items.toList()) items.add(position, deletedItem)
}.show() listAdapter.submitList(items.toList())
}.show()
}else{
dismiss()
}
} }
} }

View file

@ -148,11 +148,6 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
val listener = object : OnFormatClickListener { val listener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) { override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) {
downloadItem.format = item.first() downloadItem.format = item.first()
if (containers.contains(item.first().container)){
downloadItem.format.container = item.first().container
containerAutoCompleteTextView.setText(item.first().container, false)
}
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
resultItem.formats.removeAll(formats.toSet()) resultItem.formats.removeAll(formats.toSet())
@ -171,6 +166,11 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
} }
} }
formatCard.setOnLongClickListener {
uiUtil.showFormatDetails(downloadItem.format, requireActivity())
true
}
container?.isEnabled = true container?.isEnabled = true
containerAutoCompleteTextView?.setAdapter( containerAutoCompleteTextView?.setAdapter(
ArrayAdapter( ArrayAdapter(
@ -187,6 +187,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
(container!!.editText as AutoCompleteTextView?)!!.onItemClickListener = (container!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
downloadItem.format.container = containers[index] downloadItem.format.container = containers[index]
if (containers[index] == getString(R.string.defaultValue)) downloadItem.format.container = ""
} }
@ -313,6 +314,41 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
cut.isEnabled = false cut.isEnabled = false
} }
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
filenameTemplate.setOnClickListener {
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.file_name_template))
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = getString(R.string.file_name_template)
editText.setText(downloadItem.customFileNameTemplate)
editText.setSelection(editText.text.length)
builder.setView(inputLayout)
builder.setPositiveButton(
getString(R.string.ok)
) { dialog: DialogInterface?, which: Int ->
downloadItem.customFileNameTemplate = editText.text.toString()
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
getString(R.string.cancel)
) { dialog: DialogInterface?, which: Int -> }
val dialog = builder.create()
editText.doOnTextChanged { text, start, before, count ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
}
dialog.show()
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles) val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages) val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
if (downloadItem.videoPreferences.writeSubs) { if (downloadItem.videoPreferences.writeSubs) {

View file

@ -210,110 +210,13 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
dismiss() dismiss()
} }
formatItem.setOnLongClickListener { formatItem.setOnLongClickListener {
val bottomSheet = BottomSheetDialog(requireContext()) uiUtil.showFormatDetails(format, requireActivity())
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.format_details_sheet)
val formatIdParent = bottomSheet.findViewById<LinearLayout>(R.id.format_id_parent)
val containerParent = bottomSheet.findViewById<LinearLayout>(R.id.container_parent)
val codecParent = bottomSheet.findViewById<LinearLayout>(R.id.codec_parent)
val filesizeParent = bottomSheet.findViewById<LinearLayout>(R.id.filesize_parent)
val formatnoteParent = bottomSheet.findViewById<LinearLayout>(R.id.format_note_parent)
val fpsParent = bottomSheet.findViewById<LinearLayout>(R.id.fps_parent)
val asrParent = bottomSheet.findViewById<LinearLayout>(R.id.asr_parent)
if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE
else {
formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id
formatIdParent?.setOnClickListener {
copyToClipboard(format.format_id)
}
}
if (format.container.isBlank()) containerParent?.visibility = View.GONE
else {
containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container
containerParent?.setOnClickListener {
copyToClipboard(format.container)
}
}
val codecField =
if (format.encoding != "") {
format.encoding.uppercase()
}else if (format.vcodec != "none" && format.vcodec != ""){
format.vcodec.uppercase()
} else {
format.acodec.uppercase()
}
if (codecField.isBlank()) codecParent?.visibility = View.GONE
else {
codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField
codecParent?.setOnClickListener {
copyToClipboard(codecField)
}
}
if (format.filesize != 0L) filesizeParent?.visibility = View.GONE
else {
filesizeParent?.findViewById<TextView>(R.id.filesize_value)?.text = fileUtil.convertFileSize(format.filesize)
filesizeParent?.setOnClickListener {
copyToClipboard(fileUtil.convertFileSize(format.filesize))
}
}
if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE
else {
formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note
formatnoteParent?.setOnClickListener {
copyToClipboard(format.format_note)
}
}
if (format.fps.isNullOrBlank()) fpsParent?.visibility = View.GONE
else {
fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps
fpsParent?.setOnClickListener {
copyToClipboard(format.fps!!)
}
}
if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE
else {
asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr
asrParent?.setOnClickListener {
copyToClipboard(format.asr!!)
}
}
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
)
true true
} }
linearLayout.addView(formatItem) linearLayout.addView(formatItem)
} }
} }
private fun copyToClipboard(text: String){
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(text, text)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, requireContext().getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
override fun onCancel(dialog: DialogInterface) { override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog) super.onCancel(dialog)
cleanUp() cleanUp()

View file

@ -2,13 +2,16 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.ClipboardManager
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -21,8 +24,10 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -115,12 +120,14 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
} }
} }
val checkAll = view.findViewById<FloatingActionButton>(R.id.check_all) val checkAll = view.findViewById<ExtendedFloatingActionButton>(R.id.check_all)
checkAll!!.setOnClickListener { checkAll!!.setOnClickListener {
if (listAdapter.getCheckedItems().size != items.size){ if (listAdapter.getCheckedItems().size != items.size){
fromTextInput.editText!!.setText("1") fromTextInput.editText!!.setText("1")
toTextInput.editText!!.setText(items.size.toString()) toTextInput.editText!!.setText(items.size.toString())
listAdapter.checkAll() listAdapter.checkAll()
fromTextInput.isEnabled = true
toTextInput.isEnabled = true
selectedText.text = resources.getString(R.string.all_items_selected) selectedText.text = resources.getString(R.string.all_items_selected)
}else{ }else{
reset() reset()
@ -160,6 +167,24 @@ class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem?>,
} }
} }
view.findViewById<BottomAppBar>(R.id.bottomAppBar).setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId
if (itemId == R.id.invert_selected) {
listAdapter.invertSelected(items)
val checkedItems = listAdapter.getCheckedItems()
if (checkedItems.size == items.size){
selectedText.text = resources.getString(R.string.all_items_selected)
}else{
selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
}
if(checkedItems.isNotEmpty() && checkedItems.size < items.size){
fromTextInput.isEnabled = false
toTextInput.isEnabled = false
}
}
true
}
} }
override fun onCancel(dialog: DialogInterface) { override fun onCancel(dialog: DialogInterface) {

View file

@ -32,6 +32,7 @@ import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputLayout
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -124,6 +125,7 @@ class CookiesActivity : BaseActivity(), CookieAdapter.OnItemClickListener {
builder.setTitle(getString(R.string.cookies)) builder.setTitle(getString(R.string.cookies))
val inputLayout = layoutInflater.inflate(R.layout.textinput, null) val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext) val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
val text = if (url.isNullOrBlank()) "https://" else url val text = if (url.isNullOrBlank()) "https://" else url
editText.setText(text) editText.setText(text)
editText.setSelection(editText.text.length) editText.setSelection(editText.text.length)

View file

@ -0,0 +1,158 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context.POWER_SERVICE
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.os.Environment
import android.os.PowerManager
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.time.LocalDateTime
import java.util.Locale
class AppearanceSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.appearance
private var language: ListPreference? = null
private var theme: ListPreference? = null
private var accent: ListPreference? = null
private var highContrast: SwitchPreferenceCompat? = null
private var locale: ListPreference? = null
private var updateUtil: UpdateUtil? = null
private var fileUtil: FileUtil? = null
private var activeDownloadCount = 0
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.appearance_preferences, rootKey)
updateUtil = UpdateUtil(requireContext())
fileUtil = FileUtil()
WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){
activeDownloadCount = 0
it.forEach {w ->
if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++
}
}
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
language = findPreference("app_language")
theme = findPreference("ytdlnis_theme")
accent = findPreference("theme_accent")
highContrast = findPreference("high_contrast")
locale = findPreference("locale")
if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){
val values = resources.getStringArray(R.array.language_values)
val entries = mutableListOf<String>()
values.forEach {
entries.add(Locale(it).getDisplayName(Locale(it)))
}
language!!.entries = entries.toTypedArray()
}else{
language!!.isVisible = false
}
if(language!!.value == null) language!!.value = Locale.getDefault().language
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
language!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
true
}
theme!!.summary = theme!!.entry
theme!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
when(newValue){
"System" -> {
theme!!.summary = getString(R.string.system)
}
"Dark" -> {
theme!!.summary = getString(R.string.dark)
}
else -> {
theme!!.summary = getString(R.string.light)
}
}
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
this.startActivity(intent)
requireActivity().finishAffinity()
true
}
accent!!.summary = accent!!.entry
accent!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
this.startActivity(intent)
requireActivity().finishAffinity()
true
}
highContrast!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
this.startActivity(intent)
requireActivity().finishAffinity()
true
}
}
}

View file

@ -0,0 +1,13 @@
package com.deniscerri.ytdlnis.ui.more.settings
import androidx.preference.PreferenceFragmentCompat
abstract class BaseSettingsFragment : PreferenceFragmentCompat() {
abstract val title: Int
override fun onStart() {
super.onStart()
(activity as? SettingsActivity)?.changeTopAppbarTitle(getString(title))
}
}

View file

@ -0,0 +1,67 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.app.Activity
import android.content.Context.POWER_SERVICE
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import androidx.preference.Preference
import androidx.preference.SeekBarPreference
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DownloadSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.downloading
private var concurrentDownloads: SeekBarPreference? = null
private var ignoreBatteryOptimization: Preference? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
ignoreBatteryOptimization = findPreference("ignore_battery")
val packageName: String = requireContext().packageName
val pm = requireContext().applicationContext.getSystemService(POWER_SERVICE) as PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) {
ignoreBatteryOptimization!!.isVisible = false
}
concurrentDownloads = findPreference("concurrent_downloads")
concurrentDownloads!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
MaterialAlertDialogBuilder(requireContext())
.setTitle(resources.getString(R.string.warning))
.setMessage(resources.getString(R.string.workmanager_updated))
.setNegativeButton(resources.getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
.setPositiveButton(resources.getString(R.string.restart)) { _, _ ->
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(intent)
if (context is Activity) {
(context as Activity).finish()
}
Runtime.getRuntime().exit(0)
}
.show()
true
}
ignoreBatteryOptimization = findPreference("ignore_battery")
ignoreBatteryOptimization!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + requireContext().packageName)
startActivity(intent)
true
}
}
}

View file

@ -0,0 +1,179 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.app.Activity
import android.content.Intent
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.preference.Preference
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.snackbar.Snackbar
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 accessAllFiles : Preference? = null
private var clearCache: Preference? = null
private var fileUtil: FileUtil? = null
private var activeDownloadCount = 0
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.folders_preference, rootKey)
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
fileUtil = FileUtil()
musicPath = findPreference("music_path")
videoPath = findPreference("video_path")
commandPath = findPreference("command_path")
accessAllFiles = findPreference("access_all_files")
clearCache = findPreference("clear_cache")
if (preferences.getString("music_path", "")!!.isEmpty()) {
editor.putString("music_path", getString(R.string.music_path))
}
if (preferences.getString("video_path", "")!!.isEmpty()) {
editor.putString("video_path", getString(R.string.video_path))
}
if (preferences.getString("command_path", "")!!.isEmpty()) {
editor.putString("command_path", getString(R.string.command_path))
}
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
VERSION.SDK_INT < 30) {
accessAllFiles!!.isVisible = false
}
editor.apply()
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
}
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
}
var cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
clearCache!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
if (activeDownloadCount == 0){
File(requireContext().cacheDir.absolutePath + "/downloads").deleteRecursively()
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
}else{
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
}
true
}
}
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 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)
}
editor.apply()
}
companion object {
const val MUSIC_PATH_CODE = 33333
const val VIDEO_PATH_CODE = 55555
const val COMMAND_PATH_CODE = 77777
}
}

View file

@ -0,0 +1,501 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Environment
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.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.util.Calendar
class MainSettingsFragment : PreferenceFragmentCompat() {
private var backup : Preference? = null
private var restore : Preference? = null
private var updateUtil: UpdateUtil? = null
private var fileUtil: FileUtil? = null
private var activeDownloadCount = 0
private lateinit var resultViewModel: ResultViewModel
private lateinit var historyViewModel: HistoryViewModel
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var cookieViewModel: CookieViewModel
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
val navController = findNavController()
val appearance = findPreference<Preference>("appearance")
appearance?.summary = "${getString(R.string.language)}, ${getString(R.string.Theme)}, ${getString(R.string.accents)}"
appearance?.setOnPreferenceClickListener {
navController.navigate(R.id.action_mainSettingsFragment_to_appearanceSettingsFragment)
true
}
val folders = findPreference<Preference>("folders")
folders?.summary = "${getString(R.string.music_directory)}, ${getString(R.string.video_directory)}, ${getString(R.string.command_directory)}"
folders?.setOnPreferenceClickListener {
navController.navigate(R.id.action_mainSettingsFragment_to_folderSettingsFragment)
true
}
val downloading = findPreference<Preference>("downloading")
downloading?.summary = "${getString(R.string.quick_download)}, ${getString(R.string.concurrent_downloads)}, ${getString(R.string.limit_rate)}"
downloading?.setOnPreferenceClickListener {
navController.navigate(R.id.action_mainSettingsFragment_to_downloadSettingsFragment)
true
}
val processing = findPreference<Preference>("processing")
processing?.summary = "${getString(R.string.sponsorblock)}, ${getString(R.string.embed_subtitles)}, ${getString(R.string.add_chapters)}"
processing?.setOnPreferenceClickListener {
navController.navigate(R.id.action_mainSettingsFragment_to_processingSettingsFragment)
true
}
val updating = findPreference<Preference>("updating")
updating?.summary = "${getString(R.string.update_ytdl)}, ${getString(R.string.format_source)}, ${getString(R.string.update_app)}"
updating?.setOnPreferenceClickListener {
navController.navigate(R.id.action_mainSettingsFragment_to_updateSettingsFragment)
true
}
//about section -------------------------
updateUtil = UpdateUtil(requireContext())
fileUtil = FileUtil()
WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){
activeDownloadCount = 0
it.forEach {w ->
if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++
}
}
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
backup = findPreference("backup")
restore = findPreference("restore")
backup!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.select_backup_categories))
val values = resources.getStringArray(R.array.backup_category_values)
val entries = resources.getStringArray(R.array.backup_category_entries)
val checkedItems : ArrayList<Boolean> = arrayListOf()
values.forEach { _ ->
checkedItems.add(true)
}
builder.setMultiChoiceItems(
entries,
checkedItems.toBooleanArray()
) { _, which, isChecked ->
checkedItems[which] = isChecked
}
builder.setPositiveButton(
getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
lifecycleScope.launch(Dispatchers.IO) {
val json = JsonObject()
json.addProperty("app", "YTDLnis_backup")
for (i in 0 until checkedItems.size) {
if (checkedItems[i]) {
when(values[i]){
"settings" -> json.add("settings", backupSettings(preferences))
"downloads" -> json.add("downloads", backupHistory())
"queued" -> json.add("queued", backupQueuedDownloads() )
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
"cookies" -> json.add("cookies", backupCookies() )
"templates" -> json.add("templates", backupCommandTemplates() )
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
"searchHistory" -> json.add("search_history", backupSearchHistory() )
}
}
}
val currentTime = Calendar.getInstance()
val saveFile = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS), "YTDLnis_${BuildConfig.VERSION_NAME}_${currentTime.get(
Calendar.YEAR)}-${currentTime.get(Calendar.MONTH) + 1}-${currentTime.get(
Calendar.DAY_OF_MONTH)}.json")
saveFile.delete()
saveFile.createNewFile()
saveFile.writeText(Gson().toJson(json))
Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG).show()
}
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
true
}
restore!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json"
}
appRestoreResultLauncher.launch(intent)
true
}
}
private fun backupSettings(preferences: SharedPreferences) : JsonArray {
runCatching {
val prefs = preferences.all
val arr = JsonArray()
prefs.forEach {
val obj = JsonObject()
obj.addProperty("key", it.key)
obj.addProperty("value", it.value.toString())
obj.addProperty("type", it.value!!::class.simpleName)
arr.add(obj)
}
return arr
}
return JsonArray()
}
private suspend fun backupHistory() : JsonArray {
runCatching {
val historyItems = withContext(Dispatchers.IO) {
historyViewModel.getAll()
}
val arr = JsonArray()
historyItems.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupQueuedDownloads() : JsonArray {
runCatching {
val items = withContext(Dispatchers.IO) {
downloadViewModel.getQueued()
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupCancelledDownloads() : JsonArray {
runCatching {
val items = withContext(Dispatchers.IO) {
downloadViewModel.getCancelled()
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupCookies() : JsonArray {
runCatching {
val items = withContext(Dispatchers.IO) {
cookieViewModel.getAll()
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupCommandTemplates() : JsonArray {
runCatching {
val items = withContext(Dispatchers.IO) {
commandTemplateViewModel.getAll()
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupShortcuts() : JsonArray {
runCatching {
val items = withContext(Dispatchers.IO) {
commandTemplateViewModel.getAllShortcuts()
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupSearchHistory() : JsonArray {
runCatching {
val historyItems = withContext(Dispatchers.IO) {
resultViewModel.getSearchHistory()
}
val arr = JsonArray()
historyItems.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private var appRestoreResultLauncher = 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
)
}
lifecycleScope.launch {
val preferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
runCatching {
val ip = requireContext().contentResolver.openInputStream(result.data!!.data!!)
val r = BufferedReader(InputStreamReader(ip))
val total: java.lang.StringBuilder = java.lang.StringBuilder()
var line: String?
while (r.readLine().also { line = it } != null) {
total.append(line).append('\n')
}
val json = Gson().fromJson(total.toString(), JsonObject::class.java)
val finalMessage = StringBuilder("${getString(R.string.restore_complete)}\n")
//preference restore
if(json.has("settings")){
val prefs = json.getAsJsonArray("settings")
val preferencesKeys = preferences.all.map { it.key }
prefs.forEach {
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
if (preferencesKeys.contains(key)){
when(it.asJsonObject.get("type").toString().replace("\"", "")){
"String" -> {
val value = it.asJsonObject.get("value").toString().replace("\"", "")
editor.putString(key, value)
}
"Boolean" -> {
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
editor.putBoolean(key, value)
}
"Int" -> {
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
editor.putInt(key, value)
}
"HashSet" -> {
val value = hashSetOf(it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])".toRegex(), ""))
editor.putStringSet(key, value)
}
}
}
}
finalMessage.append("${getString(R.string.settings)}: ${prefs.count()}\n")
}
//history restore
if(json.has("downloads")){
val items = json.getAsJsonArray("downloads")
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java)
item.id = 0L
withContext(Dispatchers.IO){
historyViewModel.insert(item)
}
}
finalMessage.append("${getString(R.string.downloads)}: ${items.count()}\n")
}
//queued downloads restore
if(json.has("queued")){
val items = json.getAsJsonArray("queued")
val queued = mutableListOf<DownloadItem>()
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
item.id = 0L
queued.add(item)
}
if (queued.isNotEmpty()) {
finalMessage.append("${getString(R.string.in_queue)}: ${queued.count()}\n")
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(queued)}
}
}
//cancelled downloads restore
if(json.has("cancelled")){
val items = json.getAsJsonArray("cancelled")
val cancelled = mutableListOf<DownloadItem>()
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
item.id = 0L
cancelled.add(item)
}
if(cancelled.isNotEmpty()){
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
}
}
//cookies restore
if(json.has("cookies")){
val items = json.getAsJsonArray("cookies")
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java)
item.id = 0L
withContext(Dispatchers.IO){
cookieViewModel.insert(item)
}
}
if(items.count() > 0){
finalMessage.append("${getString(R.string.cookies)}: ${items.count()}\n")
}
}
//command template restore
if(json.has("templates")){
val items = json.getAsJsonArray("templates")
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
item.id = 0L
withContext(Dispatchers.IO){
commandTemplateViewModel.insert(item)
}
}
if(items.count() > 0){
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
}
}
//shortcuts restore
if(json.has("shortcuts")){
val items = json.getAsJsonArray("shortcuts")
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), TemplateShortcut::class.java)
item.id = 0L
withContext(Dispatchers.IO){
commandTemplateViewModel.insertShortcut(item)
}
}
if(items.count() > 0){
finalMessage.append("${getString(R.string.shortcuts)}: ${items.count()}\n")
}
}
//search history restore
if(json.has("search_history")){
val items = json.getAsJsonArray("search_history")
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), SearchHistoryItem::class.java)
withContext(Dispatchers.IO){
resultViewModel.addSearchQueryToHistory(item.query)
}
}
if(items.count() > 0){
finalMessage.append("${getString(R.string.search_history)}: ${items.count()}\n")
}
}
finalMessage.append("${getString(R.string.restart)}?")
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.restore))
builder.setMessage(finalMessage)
builder.setPositiveButton(
getString(R.string.restart)
) { _: DialogInterface?, _: Int ->
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(intent)
if (context is Activity) {
(context as Activity).finish()
}
Runtime.getRuntime().exit(0)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
}.onFailure {
it.printStackTrace()
Snackbar.make(requireView(), getString(R.string.couldnt_parse_file), Snackbar.LENGTH_LONG).show()
}
}
}
}
}

View file

@ -0,0 +1,20 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import com.deniscerri.ytdlnis.R
class ProcessingSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.processing
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.processing_preferences, rootKey)
findPreference<EditTextPreference>("file_name_template")?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
findPreference<EditTextPreference>("file_name_template")?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
findPreference<EditTextPreference>("file_name_template_audio")?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
findPreference<EditTextPreference>("file_name_template_audio")?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
}
}

View file

@ -3,28 +3,56 @@ package com.deniscerri.ytdlnis.ui.more.settings
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.activity.addCallback
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commit
import androidx.fragment.app.replace
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.databinding.ActivitySettingsBinding
import com.deniscerri.ytdlnis.ui.BaseActivity import com.deniscerri.ytdlnis.ui.BaseActivity
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
private var fm: FragmentManager? = null
private var topAppBar: MaterialToolbar? = null
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
var context: Context? = null var context: Context? = null
lateinit var binding: ActivitySettingsBinding
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
context = baseContext context = baseContext
topAppBar = findViewById(R.id.settings_toolbar) binding = ActivitySettingsBinding.inflate(layoutInflater)
topAppBar!!.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() } setContentView(binding.root)
fm = supportFragmentManager
fm!!.beginTransaction() val navHostFragment = supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment
.replace(R.id.settings_frame_layout, SettingsFragment()) val navController = navHostFragment.findNavController()
.commit()
val listener = NavController.OnDestinationChangedListener { controller, destination, arguments ->
if (destination.id == R.id.mainSettingsFragment){
changeTopAppbarTitle(getString(R.string.settings))
}
}
navController.addOnDestinationChangedListener(listener)
binding.settingsToolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
onBackPressedDispatcher.addCallback(this) {
if (navController.currentDestination?.id == R.id.mainSettingsFragment) {
navController.popBackStack()
finishAndRemoveTask()
}else{
navController.navigateUp()
}
}
navController.navigate(R.id.mainSettingsFragment)
}
fun changeTopAppbarTitle(text: String) {
if (this::binding.isInitialized) binding.collapsingToolbar.title = text
} }
} }

View file

@ -1,522 +0,0 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ClipboardManager
import android.content.Context.POWER_SERVICE
import android.content.Intent
import android.net.Uri
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.os.Environment
import android.os.PowerManager
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import java.io.File
import java.util.Locale
class SettingsFragment : PreferenceFragmentCompat() {
private var language: ListPreference? = null
private var theme: ListPreference? = null
private var accent: ListPreference? = null
private var highContrast: SwitchPreferenceCompat? = null
private var ignoreBatteryOptimization: Preference? = null
private var musicPath: Preference? = null
private var videoPath: Preference? = null
private var commandPath: Preference? = null
private var accessAllFiles : Preference? = null
private var clearCache: Preference? = null
private var locale: ListPreference? = null
private var concurrentFragments: SeekBarPreference? = null
private var concurrentDownloads: SeekBarPreference? = null
private var audioFormat: ListPreference? = null
private var videoFormat: ListPreference? = null
private var videoQuality: ListPreference? = null
private var formatID: EditTextPreference? = null
private var updateYTDL: Preference? = null
private var formatSource: ListPreference? = null
private var exportPreferences : Preference? = null
private var importPreferences : Preference? = null
private var ytdlVersion: Preference? = null
private var version: Preference? = null
private var updateUtil: UpdateUtil? = null
private var fileUtil: FileUtil? = null
private var activeDownloadCount = 0
private val jsonFormat = Json { prettyPrint = true }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
updateUtil = UpdateUtil(requireContext())
fileUtil = FileUtil()
WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){
activeDownloadCount = 0
it.forEach {w ->
if (w.state == WorkInfo.State.RUNNING) activeDownloadCount++
}
}
initPreferences()
initListeners()
}
private fun initPreferences() {
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
language = findPreference("app_language")
theme = findPreference("ytdlnis_theme")
accent = findPreference("theme_accent")
highContrast = findPreference("high_contrast")
ignoreBatteryOptimization = findPreference("ignore_battery")
musicPath = findPreference("music_path")
videoPath = findPreference("video_path")
commandPath = findPreference("command_path")
accessAllFiles = findPreference("access_all_files")
clearCache = findPreference("clear_cache")
locale = findPreference("locale")
concurrentFragments = findPreference("concurrent_fragments")
concurrentDownloads = findPreference("concurrent_downloads")
audioFormat = findPreference("audio_format")
videoFormat = findPreference("video_format")
videoQuality = findPreference("video_quality")
formatID = findPreference("format_id")
updateYTDL = findPreference("update_ytdl")
formatSource = findPreference("formats_source")
exportPreferences = findPreference("export_preferences")
importPreferences = findPreference("import_preferences")
ytdlVersion = findPreference("ytdl-version")
ytdlVersion!!.summary = preferences.getString("ytdl-version", "NULL")
version = findPreference("version")
version!!.summary = BuildConfig.VERSION_NAME
if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){
val values = resources.getStringArray(R.array.language_values)
val entries = mutableListOf<String>()
values.forEach {
entries.add(Locale(it).getDisplayName(Locale(it)))
}
language!!.entries = entries.toTypedArray()
}else{
language!!.isVisible = false
}
val packageName: String = requireContext().packageName
val pm = requireContext().applicationContext.getSystemService(POWER_SERVICE) as PowerManager
if (pm.isIgnoringBatteryOptimizations(packageName)) {
ignoreBatteryOptimization!!.isVisible = false
}
if (preferences.getString("music_path", "")!!.isEmpty()) {
editor.putString("music_path", getString(R.string.music_path))
}
if (preferences.getString("video_path", "")!!.isEmpty()) {
editor.putString("video_path", getString(R.string.video_path))
}
if (preferences.getString("command_path", "")!!.isEmpty()) {
editor.putString("command_path", getString(R.string.command_path))
}
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
VERSION.SDK_INT < 30) {
accessAllFiles!!.isVisible = false
}
editor.apply()
}
@SuppressLint("BatteryLife", "InlinedApi")
private fun initListeners() {
val preferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
if(language!!.value == null) language!!.value = Locale.getDefault().language
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
language!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
true
}
theme!!.summary = theme!!.entry
theme!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
when(newValue){
"System" -> {
theme!!.summary = getString(R.string.system)
}
"Dark" -> {
theme!!.summary = getString(R.string.dark)
}
else -> {
theme!!.summary = getString(R.string.light)
}
}
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
this.startActivity(intent)
requireActivity().finishAffinity()
true
}
accent!!.summary = accent!!.entry
accent!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
this.startActivity(intent)
requireActivity().finishAffinity()
true
}
highContrast!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
this.startActivity(intent)
requireActivity().finishAffinity()
true
}
ignoreBatteryOptimization!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val intent = Intent()
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = Uri.parse("package:" + requireContext().packageName)
startActivity(intent)
true
}
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
}
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
}
var cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
clearCache!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
if (activeDownloadCount == 0){
File(requireContext().cacheDir.absolutePath + "/downloads").deleteRecursively()
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
clearCache!!.summary = "(${fileUtil!!.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
}else{
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
}
true
}
locale!!.summary = locale!!.value
concurrentDownloads!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
MaterialAlertDialogBuilder(requireContext())
.setTitle(resources.getString(R.string.warning))
.setMessage(resources.getString(R.string.workmanager_updated))
.setNegativeButton(resources.getString(R.string.cancel)) { dialog, _ ->
dialog.cancel()
}
.setPositiveButton(resources.getString(R.string.restart)) { _, _ ->
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(intent)
if (context is Activity) {
(context as Activity).finish()
}
Runtime.getRuntime().exit(0)
}
.show()
true
}
audioFormat!!.summary = preferences.getString("audio_format", "")!!.replace("Default", getString(R.string.defaultValue))
audioFormat!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
audioFormat!!.summary = newValue.toString().replace("Default", getString(R.string.defaultValue))
true
}
videoFormat!!.summary = preferences.getString("video_format", "")!!.replace("Default", getString(R.string.defaultValue))
videoFormat!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
videoFormat!!.summary = newValue.toString().replace("Default", getString(R.string.defaultValue))
true
}
videoQuality!!.summary = preferences.getString("video_quality", "")
videoQuality!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
videoQuality!!.summary = newValue.toString()
true
}
formatID!!.summary = formatID!!.text
formatID!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
formatID!!.summary = newValue.toString()
true
}
YoutubeDL.getInstance().version(context)?.let {
editor.putString("ytdl-version", it)
editor.apply()
ytdlVersion!!.summary = it
}
updateYTDL!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
lifecycleScope.launch {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytdl_updating_started),
Snackbar.LENGTH_LONG).show()
when (updateUtil!!.updateYoutubeDL()) {
YoutubeDL.UpdateStatus.DONE -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytld_update_success),
Snackbar.LENGTH_LONG).show()
YoutubeDL.getInstance().version(context)?.let {
editor.putString("ytdl-version", it)
editor.apply()
ytdlVersion!!.summary = it
}
}
YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(),
requireContext().getString(R.string.you_are_in_latest_version),
Snackbar.LENGTH_LONG).show()
else -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.errored),
Snackbar.LENGTH_LONG).show()
}
}
}
true
}
formatSource!!.summary = preferences.getString("formats_source", "")!!.replace("Default", getString(R.string.defaultValue))
formatSource!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
formatSource!!.summary = newValue.toString().replace("Default", getString(R.string.defaultValue))
true
}
exportPreferences!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val prefs = preferences.all
val json = buildJsonObject {
putJsonArray("YTDLnis_Preferences") {
prefs.forEach {
add(buildJsonObject {
put("key", it.key)
put("value", it.value.toString())
put("type", it.value!!::class.simpleName)
})
}
}
}
val string = jsonFormat.encodeToString(json)
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(string)
Snackbar.make(requireView(), getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
true
}
importPreferences!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
val clipboard = requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text.toString()
try{
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true) {
clear()
val preferencesKeys = preferences.all.map { it.key }
jsonFormat.decodeFromString<JsonObject>(clip).run {
val prefs = this.jsonObject["YTDLnis_Preferences"]!!.jsonArray
prefs.forEach {
val key : String = it.jsonObject["key"].toString().replace("\"", "")
if (preferencesKeys.contains(key)){
when(it.jsonObject["type"].toString().replace("\"", "")){
"String" -> {
val value = it.jsonObject["value"].toString().replace("\"", "")
putString(key, value)
}
"Boolean" -> {
val value = it.jsonObject["value"].toString().replace("\"", "").toBoolean()
putBoolean(key, value)
}
"Int" -> {
val value = it.jsonObject["value"].toString().replace("\"", "").toInt()
putInt(key, value)
}
"HashSet" -> {
val value = hashSetOf(it.jsonObject["value"].toString().replace("(\")|(\\[)|(])".toRegex(), ""))
putStringSet(key, value)
}
}
}
}
}
}
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(intent)
if (context is Activity) {
(context as Activity).finish()
}
Runtime.getRuntime().exit(0)
}catch (e: Exception){
e.printStackTrace()
}
true
}
version!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
lifecycleScope.launch{
withContext(Dispatchers.IO){
updateUtil!!.updateApp{ msg ->
lifecycleScope.launch(Dispatchers.Main){
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
}
}
}
}
true
}
}
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 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)
}
editor.apply()
}
companion object {
const val MUSIC_PATH_CODE = 33333
const val VIDEO_PATH_CODE = 55555
const val COMMAND_PATH_CODE = 77777
}
}

View file

@ -0,0 +1,89 @@
package com.deniscerri.ytdlnis.ui.more.settings
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class UpdateSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.updating
private var updateYTDL: Preference? = null
private var ytdlVersion: Preference? = null
private var version: Preference? = null
private var updateUtil: UpdateUtil? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.updating_preferences, rootKey)
updateUtil = UpdateUtil(requireContext())
val preferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
updateYTDL = findPreference("update_ytdl")
ytdlVersion = findPreference("ytdl-version")
version = findPreference("version")
version!!.summary = BuildConfig.VERSION_NAME
YoutubeDL.getInstance().version(context)?.let {
editor.putString("ytdl-version", it)
editor.apply()
ytdlVersion!!.summary = it
}
updateYTDL!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
lifecycleScope.launch {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytdl_updating_started),
Snackbar.LENGTH_LONG).show()
when (updateUtil!!.updateYoutubeDL()) {
YoutubeDL.UpdateStatus.DONE -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.ytld_update_success),
Snackbar.LENGTH_LONG).show()
YoutubeDL.getInstance().version(context)?.let {
editor.putString("ytdl-version", it)
editor.apply()
ytdlVersion!!.summary = it
}
}
YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(),
requireContext().getString(R.string.you_are_in_latest_version),
Snackbar.LENGTH_LONG).show()
else -> {
Snackbar.make(requireView(),
requireContext().getString(R.string.errored),
Snackbar.LENGTH_LONG).show()
}
}
}
true
}
version!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
lifecycleScope.launch{
withContext(Dispatchers.IO){
updateUtil!!.updateApp{ msg ->
lifecycleScope.launch(Dispatchers.Main){
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
}
}
}
}
true
}
}
}

View file

@ -525,49 +525,63 @@ class InfoUtil(private val context: Context) {
urls.forEach { urls.forEach {
urlsFile.appendText(it+"\n") urlsFile.appendText(it+"\n")
} }
try {
val request = YoutubeDLRequest(emptyList())
request.addOption("--print", "%(formats)s")
request.addOption("-a", urlsFile.absolutePath)
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
val cookiesFile = File(context.cacheDir, "cookies.txt")
if (cookiesFile.exists()){
request.addOption("--cookies", cookiesFile.absolutePath)
}
YoutubeDL.getInstance().execute(request){ progress, _, line -> val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
try{ val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
val formats = mutableListOf<Format>() val allYoutubeLinks = urls.any {p.matcher(it).find() }
val listOfStrings = JSONArray(line) if (formatSource == "yt-dlp" || !allYoutubeLinks){
for (f in 0 until listOfStrings.length()){ try {
val format = listOfStrings.get(f) as JSONObject val request = YoutubeDLRequest(emptyList())
try{ request.addOption("--print", "%(formats)s")
if (format.getString("filesize") == "None") continue request.addOption("-a", urlsFile.absolutePath)
}catch (e: Exception) { continue } request.addOption("--skip-download")
val formatProper = Gson().fromJson(format.toString(), Format::class.java) request.addOption("-R", "1")
if (formatProper.filesize > 0){ request.addOption("--socket-timeout", "5")
if ( !formatProper!!.format_note!!.contains("audio only", true)) { val cookiesFile = File(context.cacheDir, "cookies.txt")
formatProper.format_note = format.getString("format_note") if (cookiesFile.exists()){
}else{ request.addOption("--cookies", cookiesFile.absolutePath)
formatProper.format_note = "${format.getString("format_note")} audio"
}
formatProper.container = format.getString("ext")
formats.add(formatProper)
}
}
progress(formats)
}catch (e: Exception){
progress(emptyList())
} }
YoutubeDL.getInstance().execute(request){ progress, _, line ->
try{
val formats = mutableListOf<Format>()
val listOfStrings = JSONArray(line)
for (f in 0 until listOfStrings.length()){
val format = listOfStrings.get(f) as JSONObject
try{
if (format.getString("filesize") == "None") continue
}catch (e: Exception) { continue }
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
if (formatProper.filesize > 0){
if ( !formatProper!!.format_note!!.contains("audio only", true)) {
formatProper.format_note = format.getString("format_note")
}else{
formatProper.format_note = "${format.getString("format_note")} audio"
}
formatProper.container = format.getString("ext")
formats.add(formatProper)
}
}
progress(formats)
}catch (e: Exception){
progress(emptyList())
}
}
} catch (e: Exception) {
Looper.prepare().run {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
}
e.printStackTrace()
} }
} catch (e: Exception) { }else{
Looper.prepare().run { urls.forEach {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() val id = getIDFromYoutubeURL(it)
val res = genericRequest(invidousURL + "videos/" + id)
val vid = createVideoFromInvidiousJSON(res)
progress(vid!!.formats)
} }
e.printStackTrace()
} }
urlsFile.delete() urlsFile.delete()
} }
@ -741,6 +755,7 @@ class InfoUtil(private val context: Context) {
val url = "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&client=firefox&q=$query" val url = "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&client=firefox&q=$query"
// invidousURL + "search/suggestions?q=" + query // invidousURL + "search/suggestions?q=" + query
val res = genericArrayRequest(url) val res = genericArrayRequest(url)
Log.e("aa", res.toString())
if (res.length() == 0) return ArrayList() if (res.length() == 0) return ArrayList()
val suggestionList = ArrayList<String>() val suggestionList = ArrayList<String>()
try { try {

View file

@ -303,6 +303,107 @@ class UiUtil(private val fileUtil: FileUtil) {
datePicker.show(fragmentManager, "datepicker") datePicker.show(fragmentManager, "datepicker")
} }
fun showFormatDetails(format: Format, activity: Activity){
val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.format_details_sheet)
val formatIdParent = bottomSheet.findViewById<LinearLayout>(R.id.format_id_parent)
val containerParent = bottomSheet.findViewById<LinearLayout>(R.id.container_parent)
val codecParent = bottomSheet.findViewById<LinearLayout>(R.id.codec_parent)
val filesizeParent = bottomSheet.findViewById<LinearLayout>(R.id.filesize_parent)
val formatnoteParent = bottomSheet.findViewById<LinearLayout>(R.id.format_note_parent)
val fpsParent = bottomSheet.findViewById<LinearLayout>(R.id.fps_parent)
val asrParent = bottomSheet.findViewById<LinearLayout>(R.id.asr_parent)
if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE
else {
formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id
formatIdParent?.setOnClickListener {
copyToClipboard(format.format_id, activity)
}
}
if (format.container.isBlank()) containerParent?.visibility = View.GONE
else {
containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container
containerParent?.setOnClickListener {
copyToClipboard(format.container, activity)
}
}
val codecField =
if (format.encoding != "") {
format.encoding.uppercase()
}else if (format.vcodec != "none" && format.vcodec != ""){
format.vcodec.uppercase()
} else {
format.acodec.uppercase()
}
if (codecField.isBlank()) codecParent?.visibility = View.GONE
else {
codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField
codecParent?.setOnClickListener {
copyToClipboard(codecField, activity)
}
}
if (format.filesize != 0L) filesizeParent?.visibility = View.GONE
else {
filesizeParent?.findViewById<TextView>(R.id.filesize_value)?.text = fileUtil.convertFileSize(format.filesize)
filesizeParent?.setOnClickListener {
copyToClipboard(fileUtil.convertFileSize(format.filesize), activity)
}
}
if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE
else {
formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note
formatnoteParent?.setOnClickListener {
copyToClipboard(format.format_note, activity)
}
}
if (format.fps.isNullOrBlank()) fpsParent?.visibility = View.GONE
else {
fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps
fpsParent?.setOnClickListener {
copyToClipboard(format.fps!!, activity)
}
}
if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE
else {
asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr
asrParent?.setOnClickListener {
copyToClipboard(format.asr!!, activity)
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
private fun copyToClipboard(text: String, activity: Activity){
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(text, text)
clipboard.setPrimaryClip(clip)
Toast.makeText(activity, activity.getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: CommandTemplate) -> Unit) { suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: CommandTemplate) -> Unit) {
val bottomSheet = BottomSheetDialog(activity) val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)

View file

@ -47,7 +47,7 @@ class UpdateUtil(var context: Context) {
version = res.getString("tag_name") version = res.getString("tag_name")
body = res.getString("body") body = res.getString("body")
} catch (e: JSONException) { } catch (e: JSONException) {
result(e.message.toString()) result(context.getString(R.string.network_error))
return return
} }
val versionNameInt = version.split("v")[1].replace(".","").toInt() val versionNameInt = version.split("v")[1].replace(".","").toInt()

View file

@ -178,8 +178,8 @@ class DownloadWorker(
when(type){ when(type){
DownloadViewModel.Type.audio -> { DownloadViewModel.Type.audio -> {
var audioQualityId : String = downloadItem.format.format_id var audioQualityId : String = downloadItem.format.format_id
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality)) audioQualityId = "" if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality) || audioQualityId == "best") audioQualityId = ""
else if (audioQualityId == context.getString(R.string.worst_quality)) audioQualityId = "worstaudio" else if (audioQualityId == context.getString(R.string.worst_quality) || audioQualityId == "worst") audioQualityId = "worstaudio"
val ext = downloadItem.format.container val ext = downloadItem.format.container
if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId) if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId)
@ -236,15 +236,15 @@ class DownloadWorker(
Log.e(TAG, videoFormatID) Log.e(TAG, videoFormatID)
var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best" var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best"
if (videoFormatID.isNotEmpty()) { if (videoFormatID.isNotEmpty()) {
if (videoFormatID == context.resources.getString(R.string.best_quality)) videoFormatID = "bestvideo" if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo"
else if (videoFormatID == context.resources.getString(R.string.worst_quality)) videoFormatID = "worst" else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst"
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]" else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
if (!downloadItem.videoPreferences.removeAudio) formatArgument = "$videoFormatID+bestaudio/best/$videoFormatID" if (!downloadItem.videoPreferences.removeAudio) formatArgument = "$videoFormatID+bestaudio/best/$videoFormatID"
} }
Log.e(TAG, formatArgument) Log.e(TAG, formatArgument)
request.addOption("-f", formatArgument) request.addOption("-f", formatArgument)
val format = downloadItem.format.container val format = downloadItem.format.container
if(format.isNotEmpty()){ if(format.isNotEmpty() && format != "Default" && format != context.getString(R.string.defaultValue)){
request.addOption("--merge-output-format", format) request.addOption("--merge-output-format", format)
if (format != "webm") { if (format != "webm") {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)

View file

@ -11,7 +11,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="Incognito" android:text="@string/incognito"
android:textStyle="bold" android:textStyle="bold"
android:translationZ="20dp" android:translationZ="20dp"
android:visibility="gone" android:visibility="gone"

View file

@ -11,7 +11,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="Incognito" android:text="@string/incognito"
android:textStyle="bold" android:textStyle="bold"
android:translationZ="20dp" android:translationZ="20dp"
android:visibility="gone" android:visibility="gone"

View file

@ -29,6 +29,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/logs_recyclerview" android:id="@+id/logs_recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:scrollbars="vertical"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
> >

View file

@ -11,7 +11,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="Incognito" android:text="@string/incognito"
android:textStyle="bold" android:textStyle="bold"
android:visibility="gone" android:visibility="gone"
android:padding="5dp" android:padding="5dp"

View file

@ -6,32 +6,44 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity"> tools:context="com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity">
<FrameLayout <androidx.fragment.app.FragmentContainerView
android:id="@+id/settings_frame_layout" android:id="@+id/frame_layout"
android:layout_width="match_parent" android:name="androidx.navigation.fragment.NavHostFragment"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent" app:defaultNavHost="true"
app:navGraph="@navigation/settings_nav"
app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toBottomOf="@+id/incognito_header"
/>
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:liftOnScroll="true" android:fitsSystemWindows="true">
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/settings_toolbar" android:id="@+id/collapsing_toolbar"
android:elevation="0dp" style="?attr/collapsingToolbarLayoutLargeStyle"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:title="@string/settings"
android:layout_width="match_parent" android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back" app:title="@string/settings"
android:layout_height="match_parent"/> app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/settings_toolbar"
android:elevation="0dp"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll"
app:navigationIcon="@drawable/ic_back"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View file

@ -1,154 +1,147 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView android:layout_width="match_parent"
android:scrollbars="none"
android:orientation="vertical"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:scrollbars="none"
android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" android:orientation="vertical">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:maxLines="2"
android:singleLine="false"
android:text="@string/download"
android:textSize="25sp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:singleLine="false"
android:text="@string/configure_download"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
<Button
android:id="@+id/bottomsheet_schedule_button"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_clock"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bottomsheet_download_button"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/download"
app:icon="@drawable/ic_down"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/download_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="start"
app:tabMode="scrollable">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/audio" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/video" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/command" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/download_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textAlignment="textStart"
android:singleLine="true"
android:text="@string/app_name"
android:textSize="15sp"
app:icon="@drawable/ic_link" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:gravity="end"
android:orientation="vertical"> android:visibility="gone"
android:paddingBottom="10dp"
<androidx.constraintlayout.widget.ConstraintLayout android:paddingHorizontal="20dp"
android:layout_width="match_parent" android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:maxLines="2"
android:singleLine="false"
android:text="@string/download"
android:textSize="25sp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:singleLine="false"
android:text="@string/configure_download"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
<Button
android:id="@+id/bottomsheet_schedule_button"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_clock"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bottomsheet_download_button"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/download"
app:icon="@drawable/ic_down"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/download_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="fill"
app:tabMode="scrollable">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/audio" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/video" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/command" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/download_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button <Button
android:id="@+id/bottom_sheet_link" android:id="@+id/update_item"
style="@style/Widget.Material3.Button.TextButton.Icon" style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:autoLink="all"
android:textAlignment="textStart" android:text="@string/update"
android:singleLine="true" app:icon="@drawable/ic_update"
android:text="@string/app_name" app:layout_constraintBottom_toBottomOf="parent"
android:textSize="15sp" app:layout_constraintEnd_toEndOf="parent"
app:icon="@drawable/ic_link" /> app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:gravity="end"
android:visibility="gone"
android:paddingBottom="10dp"
android:paddingHorizontal="20dp"
android:layout_height="wrap_content">
<Button
android:id="@+id/update_item"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/update"
app:icon="@drawable/ic_update"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,6 +6,7 @@
android:checkable="true" android:checkable="true"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
app:checkedIcon="@null"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -156,6 +156,21 @@
android:checked="false" android:checked="false"
android:text="@string/split_by_chapters"/> android:text="@string/split_by_chapters"/>
<com.google.android.material.chip.Chip
android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_cut"
android:text="@string/cut"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
@ -180,7 +195,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="SponsorBlock" android:text="@string/sponsorblock"
app:chipIconVisible="true" app:chipIconVisible="true"
app:chipIcon="@drawable/ic_money" app:chipIcon="@drawable/ic_money"
android:minWidth="30dp" android:minWidth="30dp"
@ -190,14 +205,14 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/cut" android:id="@+id/filename_template"
style="@style/Widget.Material3.Chip.Assist.Elevated" style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="@string/file_name_template"
app:chipIconVisible="true" app:chipIconVisible="true"
app:chipIcon="@drawable/ic_cut" app:chipIcon="@drawable/ic_edit"
android:text="@string/cut"
android:minWidth="30dp" android:minWidth="30dp"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -205,7 +220,6 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>

View file

@ -33,7 +33,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="10dp"> android:paddingHorizontal="10dp"
android:baselineAligned="false">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput" android:id="@+id/author_textinput"
@ -57,8 +58,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="45" android:layout_weight="45"
android:labelFor="@id/container_textview"
android:hint="@string/container" android:hint="@string/container"
android:paddingStart="10dp"> android:paddingStart="10dp"
android:paddingEnd="0dp">
<AutoCompleteTextView <AutoCompleteTextView
android:id="@+id/container_textview" android:id="@+id/container_textview"
@ -175,14 +178,12 @@
app:singleLine="true"> app:singleLine="true">
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/sponsorblock_filters" android:id="@+id/remove_audio"
style="@style/Widget.Material3.Chip.Assist.Elevated" style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:text="SponsorBlock" android:text="@string/remove_audio"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_money"
android:minWidth="30dp" android:minWidth="30dp"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -227,6 +228,36 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true"> app:singleLine="true">
<com.google.android.material.chip.Chip
android:id="@+id/sponsorblock_filters"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/sponsorblock"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_money"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/filename_template"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/file_name_template"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_edit"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/save_subtitles" android:id="@+id/save_subtitles"
style="@style/Widget.Material3.Chip.Filter.Elevated" style="@style/Widget.Material3.Chip.Filter.Elevated"
@ -249,19 +280,6 @@
android:checked="false" android:checked="false"
android:text="@string/subtitle_languages"/> android:text="@string/subtitle_languages"/>
<com.google.android.material.chip.Chip
android:id="@+id/remove_audio"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/remove_audio"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>

View file

@ -10,6 +10,7 @@
app:liftOnScroll="true" app:liftOnScroll="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:elevation="0dp" android:elevation="0dp"
app:liftOnScrollTargetViewId="@id/recyclerviewhistorys"
app:elevation="0dp" app:elevation="0dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

View file

@ -21,6 +21,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:cardCornerRadius="10dp" app:cardCornerRadius="10dp"
app:cardElevation="5dp" app:cardElevation="5dp"
app:checkedIcon="@null"
app:cardMaxElevation="12dp" app:cardMaxElevation="12dp"
android:checkable="true" android:checkable="true"
app:strokeWidth="0dp" app:strokeWidth="0dp"

View file

@ -20,6 +20,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:cardCornerRadius="20dp" app:cardCornerRadius="20dp"
app:checkedIcon="@null"
app:cardElevation="5dp" app:cardElevation="5dp"
app:cardMaxElevation="12dp" app:cardMaxElevation="12dp"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"

View file

@ -20,23 +20,26 @@
style="@style/Widget.Material3.BottomAppBar" style="@style/Widget.Material3.BottomAppBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:menu="@menu/select_playlist_menu"
android:layout_gravity="bottom"> android:layout_gravity="bottom">
<TextView <TextView
android:id="@+id/selected" android:id="@+id/selected"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginHorizontal="30dp"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</com.google.android.material.bottomappbar.BottomAppBar> </com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/check_all" android:id="@+id/check_all"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:text="@string/select_all"
app:icon="@drawable/ic_select_all"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_anchor="@id/bottomAppBar" app:layout_anchor="@id/bottomAppBar"/>
app:srcCompat="@drawable/ic_select_all" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
@ -83,6 +86,7 @@
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -10,7 +10,6 @@
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="URL"
android:padding="10dp" android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View file

@ -0,0 +1,10 @@
<?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:title="@string/invert_selected"
android:icon="@drawable/ic_terminal"
app:showAsAction="never"
android:id="@+id/invert_selected"/>
</menu>

View file

@ -26,13 +26,21 @@
android:label="More" android:label="More"
tools:layout="@layout/fragment_more" > tools:layout="@layout/fragment_more" >
<action <action
android:id="@+id/action_moreFragment_to_settingsFragment" android:id="@+id/action_moreFragment_to_terminalActivity"
app:destination="@id/settingsFragment" /> app:destination="@id/terminalActivity" />
<action
android:id="@+id/action_moreFragment_to_cookiesActivity"
app:destination="@id/cookiesActivity" />
<action
android:id="@+id/action_moreFragment_to_downloadQueueActivity"
app:destination="@id/downloadQueueActivity" />
<action
android:id="@+id/action_moreFragment_to_downloadLogListActivity"
app:destination="@id/downloadLogListActivity" />
<action
android:id="@+id/action_moreFragment_to_commandTemplatesActivity"
app:destination="@id/commandTemplatesActivity" />
</fragment> </fragment>
<fragment
android:id="@+id/settingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.SettingsFragment"
android:label="SettingsFragment" />
<activity <activity
android:id="@+id/downloadQueueActivity" android:id="@+id/downloadQueueActivity"
android:name="com.deniscerri.ytdlnis.ui.downloads.DownloadQueueActivity" android:name="com.deniscerri.ytdlnis.ui.downloads.DownloadQueueActivity"
@ -50,11 +58,6 @@
android:id="@+id/cookiesActivity" android:id="@+id/cookiesActivity"
android:name="com.deniscerri.ytdlnis.ui.more.CookiesActivity" android:name="com.deniscerri.ytdlnis.ui.more.CookiesActivity"
android:label="CookiesActivity" /> android:label="CookiesActivity" />
<activity
android:id="@+id/settingsActivity"
android:name="com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity"
android:label="activity_settings"
tools:layout="@layout/activity_settings" />
<activity <activity
android:id="@+id/terminalActivity" android:id="@+id/terminalActivity"
android:name="com.deniscerri.ytdlnis.ui.more.TerminalActivity" android:name="com.deniscerri.ytdlnis.ui.more.TerminalActivity"

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings_nav_graph"
app:startDestination="@id/mainSettingsFragment">
<fragment
android:id="@+id/appearanceSettingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.AppearanceSettingsFragment"
android:label="AppearanceSettingsFragment" />
<fragment
android:id="@+id/processingSettingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.ProcessingSettingsFragment"
android:label="ProcessingSettingsFragment" />
<fragment
android:id="@+id/folderSettingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.FolderSettingsFragment"
android:label="FolderSettingsFragment" />
<fragment
android:id="@+id/updateSettingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.UpdateSettingsFragment"
android:label="UpdateSettingsFragment" />
<fragment
android:id="@+id/downloadSettingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.DownloadSettingsFragment"
android:label="DownloadSettingsFragment" />
<fragment
android:id="@+id/mainSettingsFragment"
android:name="com.deniscerri.ytdlnis.ui.more.settings.MainSettingsFragment"
android:label="MainSettingsFragment" >
<action
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
app:destination="@id/appearanceSettingsFragment" />
<action
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
app:destination="@id/folderSettingsFragment" />
<action
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
app:destination="@id/downloadSettingsFragment" />
<action
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
app:destination="@id/processingSettingsFragment" />
<action
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
app:destination="@id/updateSettingsFragment" />
</fragment>
</navigation>

View file

@ -11,7 +11,7 @@
</string-array> </string-array>
<string-array name="audio_containers_values"> <string-array name="audio_containers_values">
<item /> <item>Default</item>
<item>mp3</item> <item>mp3</item>
<item>m4a</item> <item>m4a</item>
<item>aac</item> <item>aac</item>
@ -29,7 +29,7 @@
</string-array> </string-array>
<string-array name="video_containers_values"> <string-array name="video_containers_values">
<item /> <item>Default</item>
<item>mp4</item> <item>mp4</item>
<item>mkv</item> <item>mkv</item>
<item>webm</item> <item>webm</item>
@ -46,6 +46,17 @@
<item>@string/best_quality</item> <item>@string/best_quality</item>
</string-array> </string-array>
<string-array name="video_formats_values">
<item>worst</item>
<item>360p</item>
<item>480p</item>
<item>720p</item>
<item>1080p</item>
<item>1440p</item>
<item>2160p</item>
<item>best</item>
</string-array>
<string-array name="audio_formats"> <string-array name="audio_formats">
<item>@string/worst_quality</item> <item>@string/worst_quality</item>
<item>@string/best_quality</item> <item>@string/best_quality</item>
@ -683,4 +694,28 @@
<item>ZW</item> <item>ZW</item>
</array> </array>
<array name="backup_category_entries">
<item>@string/settings</item>
<item>@string/downloads</item>
<item>@string/in_queue</item>
<item>@string/cancelled</item>
<item>@string/cookies</item>
<item>@string/command_templates</item>
<item>@string/shortcuts</item>
<item>@string/search_history</item>
</array>
<array name="backup_category_values">
<item>settings</item>
<item>downloads</item>
<item>queued</item>
<item>cancelled</item>
<item>cookies</item>
<item>templates</item>
<item>shortcuts</item>
<item>searchHistory</item>
</array>
</resources> </resources>

View file

@ -255,4 +255,13 @@
<string name="keep_cache_summary">Don\'t delete temporary files after the download is finished</string> <string name="keep_cache_summary">Don\'t delete temporary files after the download is finished</string>
<string name="backup">Backup</string> <string name="backup">Backup</string>
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="network_error">Network Error</string>
<string name="incognito">Incognito</string>
<string name="sponsorblock">SponsorBlock</string>
<string name="couldnt_parse_file">Couldn\'t parse file</string>
<string name="restore_complete">App Restore Complete. The following items have been restored:</string>
<string name="select_backup_categories">Select backup categories</string>
<string name="search_history">Search History</string>
<string name="backup_created_successfully">Backup file saved in the downloads folder</string>
<string name="appearance">Appearance</string>
</resources> </resources>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ListPreference
android:entries="@array/language_values"
android:entryValues="@array/language_values"
android:icon="@drawable/ic_language"
app:key="app_language"
app:summary="en"
app:title="@string/language" />
<ListPreference
android:entries="@array/themes"
android:entryValues="@array/themes_values"
android:icon="@drawable/baseline_format_paint_24"
app:defaultValue="System"
app:key="ytdlnis_theme"
app:summary="@string/defaultValue"
app:title="@string/Theme" />
<ListPreference
android:entries="@array/accents"
android:entryValues="@array/accents_values"
android:icon="@drawable/baseline_color_lens_24"
app:defaultValue="blue"
app:key="theme_accent"
app:summary="Blue"
app:title="@string/accents" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_invert_colors_24"
android:key="high_contrast"
app:summary="@string/pure_theme_summary"
app:title="@string/high_contrast" />
</PreferenceScreen>

View file

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_incognito"
android:key="incognito"
app:summary="@string/incognito_summary"
app:title="@string/incognito" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_card"
android:key="download_card"
app:summary="@string/download_card_summary"
app:title="@string/show_download_card" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_speed"
android:key="quick_download"
app:summary="@string/quick_download_summary"
app:title="@string/quick_download" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/metered_networks"
android:key="metered_networks"
app:summary="@string/download_over_metered_networks_summary"
app:title="@string/download_over_metered_networks" />
<ListPreference
android:dependency="download_card"
android:defaultValue="video"
android:entries="@array/download_types"
android:entryValues="@array/download_types_values"
android:icon="@drawable/ic_download_type"
app:key="preferred_download_type"
app:summary="@string/preferred_download_type_summary"
app:title="@string/preferred_download_type" />
<ListPreference
android:defaultValue="ytsearch"
android:entries="@array/search_engines"
android:entryValues="@array/search_engines_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="search_engine"
app:summary="@string/preferred_search_engine_summary"
app:title="@string/preferred_search_engine" />
<ListPreference
android:defaultValue="Home"
android:entries="@array/start_destination"
android:entryValues="@array/start_destination_values"
app:icon="@drawable/baseline_add_to_home_screen_24"
app:key="start_destination"
app:title="@string/preferred_home_screen" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_recommend_24"
android:key="home_recommendations"
app:summary="@string/video_recommendations_summary"
app:title="@string/video_recommendations" />
<ListPreference
android:defaultValue=""
android:entries="@array/countries"
android:entryValues="@array/countries_values"
app:icon="@drawable/ic_language"
app:dependency="home_recommendations"
app:key="locale"
app:title="@string/preferred_locale" />
<EditTextPreference
android:icon="@drawable/ic_key"
app:key="api_key"
app:defaultValue=""
app:summary="@string/api_key_summary"
app:title="@string/api_key" />
<SeekBarPreference
app:dependency="aria2"
android:defaultValue="3"
android:icon="@drawable/ic_lines"
android:max="25"
app:key="concurrent_fragments"
app:min="1"
app:showSeekBarValue="true"
android:dependency="aria2"
app:summary="@string/concurrent_fragments_summary"
app:title="@string/concurrent_fragments" />
<SeekBarPreference
android:defaultValue="1"
android:icon="@drawable/ic_concurrent_downloads"
android:max="10"
app:key="concurrent_downloads"
app:min="1"
app:showSeekBarValue="true"
app:summary="@string/concurrent_downloads_summary"
app:title="@string/concurrent_downloads" />
<EditTextPreference
android:icon="@drawable/ic_down"
app:key="limit_rate"
app:summary="@string/limit_rate_summary"
app:title="@string/limit_rate" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_speed"
android:key="aria2"
app:summary="@string/aria2_summary"
android:disableDependentsState="true"
app:title="Aria2" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_baseline_file_open_24"
android:key="log_downloads"
app:summary="@string/log_downloads_summary"
app:title="@string/log_downloads" />
<Preference
app:icon="@drawable/ic_battery"
app:key="ignore_battery"
app:title="@string/ignore_battery_optimization" />
</PreferenceScreen>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:icon="@drawable/ic_music_downloaded"
app:key="music_path"
app:defaultValue="@string/music_path"
app:title="@string/music_directory" />
<Preference
app:icon="@drawable/ic_video_downloaded"
app:key="video_path"
app:defaultValue="@string/video_path"
app:title="@string/video_directory" />
<Preference
app:icon="@drawable/ic_terminal"
app:key="command_path"
app:defaultValue="@string/command_path"
app:title="@string/command_directory" />
<Preference
app:icon="@drawable/ic_access_folder"
app:key="access_all_files"
android:summary="@string/access_all_directories_summary"
app:title="@string/access_all_directories" />
<Preference
app:icon="@drawable/ic_folder_delete"
app:key="clear_cache"
android:summary="@string/clear_temporary_files_summary"
app:title="@string/clear_temporary_files" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_save_24"
android:key="keep_cache"
app:summary="@string/keep_cache_summary"
app:title="@string/keep_cache" />
</PreferenceScreen>

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<MultiSelectListPreference
app:defaultValue="@array/sponsorblock_settings_default"
app:icon="@drawable/ic_money"
app:dialogTitle="@string/select_sponsorblock_filtering"
app:entries="@array/sponsorblock_settings_entries"
app:entryValues="@array/sponsorblock_settings_values"
app:key="sponsorblock_filters"
app:summary="@string/select_sponsorblock_filtering"
app:title="SponsorBlock" />
<EditTextPreference
android:icon="@drawable/ic_textformat"
app:key="file_name_template"
app:useSimpleSummaryProvider="true"
app:defaultValue="%(uploader)s - %(title)s"
app:title="@string/file_name_template" />
<EditTextPreference
android:icon="@drawable/ic_textformat"
app:key="file_name_template_audio"
app:useSimpleSummaryProvider="true"
app:defaultValue="%(uploader)s - %(title)s"
app:title="@string/file_name_template" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/if_file_rename"
app:key="restrict_filenames"
app:summary="@string/restrict_filenames_summary"
app:title="@string/restrict_filenames" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_clock"
app:key="mtime"
app:summary="@string/enable_mtime_summary"
app:title="@string/enable_mtime" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_subtitles"
app:key="embed_subtitles"
app:summary="@string/embed_subs_summary"
app:title="@string/embed_subtitles" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_subtitles_alternative"
app:key="write_subtitles"
app:summary="@string/save_subs_desc"
app:title="@string/save_subs" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_image"
app:key="embed_thumbnail"
app:summary="@string/embed_thumb_summary"
app:title="@string/embed_thumb" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_chapters"
app:key="add_chapters"
app:summary="@string/add_chapters_summary"
app:title="@string/add_chapters" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_image"
app:key="write_thumbnail"
app:summary="@string/save_thumb_summary"
app:title="@string/save_thumb" />
<ListPreference
android:defaultValue="Default"
android:entries="@array/audio_containers"
android:entryValues="@array/audio_containers_values"
android:icon="@drawable/ic_code"
app:key="audio_format"
app:useSimpleSummaryProvider="true"
app:title="@string/audio_format" />
<ListPreference
android:defaultValue="Default"
android:entries="@array/video_containers"
android:entryValues="@array/video_containers_values"
android:icon="@drawable/ic_code"
app:key="video_format"
app:useSimpleSummaryProvider="true"
app:title="@string/video_format" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_music"
android:max="10"
app:key="audio_quality"
app:min="0"
app:showSeekBarValue="true"
app:summary="@string/audio_quality_summary"
app:title="@string/audio_quality" />
<ListPreference
android:defaultValue="best"
android:entries="@array/video_formats"
android:entryValues="@array/video_formats_values"
android:icon="@drawable/ic_video"
app:key="video_quality"
app:useSimpleSummaryProvider="true"
app:title="@string/video_quality" />
<EditTextPreference
app:key="format_id"
app:defaultValue=""
app:useSimpleSummaryProvider="true"
app:title="@string/preferred_format_id" />
</PreferenceScreen>

View file

@ -1,398 +1,44 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<ListPreference
android:entries="@array/language_values"
android:entryValues="@array/language_values"
android:icon="@drawable/ic_language"
app:key="app_language"
app:summary="en"
app:title="@string/language" />
<ListPreference
android:entries="@array/themes"
android:entryValues="@array/themes_values"
android:icon="@drawable/baseline_format_paint_24"
app:defaultValue="System"
app:key="ytdlnis_theme"
app:summary="@string/defaultValue"
app:title="@string/Theme" />
<ListPreference
android:entries="@array/accents"
android:entryValues="@array/accents_values"
android:icon="@drawable/baseline_color_lens_24"
app:defaultValue="blue"
app:key="theme_accent"
app:summary="Blue"
app:title="@string/accents" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_invert_colors_24"
android:key="high_contrast"
app:summary="@string/pure_theme_summary"
app:title="@string/high_contrast" />
<Preference <Preference
app:icon="@drawable/ic_battery" android:key="appearance"
app:key="ignore_battery" app:icon="@drawable/baseline_format_paint_24"
app:title="@string/ignore_battery_optimization" /> android:title="@string/appearance"/>
<PreferenceCategory android:title="@string/directories"> <Preference
android:key="folders"
app:icon="@drawable/baseline_folder_24"
android:title="@string/directories"/>
<Preference
android:key="downloading"
app:icon="@drawable/baseline_download_24"
android:title="@string/downloading"/>
<Preference <Preference
app:icon="@drawable/ic_music_downloaded" android:key="processing"
app:key="music_path" app:icon="@drawable/ic_code"
app:defaultValue="@string/music_path" android:title="@string/processing"/>
app:title="@string/music_directory" />
<Preference <Preference
app:icon="@drawable/ic_video_downloaded" android:key="updating"
app:key="video_path" app:icon="@drawable/ic_update"
app:defaultValue="@string/video_path" android:title="@string/updating"/>
app:title="@string/video_directory" />
<Preference
app:icon="@drawable/ic_terminal"
app:key="command_path"
app:defaultValue="@string/command_path"
app:title="@string/command_directory" />
<Preference
app:icon="@drawable/ic_access_folder"
app:key="access_all_files"
android:summary="@string/access_all_directories_summary"
app:title="@string/access_all_directories" />
<Preference
app:icon="@drawable/ic_folder_delete"
app:key="clear_cache"
android:summary="@string/clear_temporary_files_summary"
app:title="@string/clear_temporary_files" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_save_24"
android:key="keep_cache"
app:summary="@string/keep_cache_summary"
app:title="@string/keep_cache" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/downloading">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_incognito"
android:key="incognito"
app:summary="@string/incognito_summary"
app:title="Incognito" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_card"
android:key="download_card"
app:summary="@string/download_card_summary"
app:title="@string/show_download_card" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_speed"
android:key="quick_download"
app:summary="@string/quick_download_summary"
app:title="@string/quick_download" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/metered_networks"
android:key="metered_networks"
app:summary="@string/download_over_metered_networks_summary"
app:title="@string/download_over_metered_networks" />
<ListPreference
android:dependency="download_card"
android:defaultValue="video"
android:entries="@array/download_types"
android:entryValues="@array/download_types_values"
android:icon="@drawable/ic_download_type"
app:key="preferred_download_type"
app:summary="@string/preferred_download_type_summary"
app:title="@string/preferred_download_type" />
<ListPreference
android:defaultValue="ytsearch"
android:entries="@array/search_engines"
android:entryValues="@array/search_engines_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="search_engine"
app:summary="@string/preferred_search_engine_summary"
app:title="@string/preferred_search_engine" />
<ListPreference
android:defaultValue="Home"
android:entries="@array/start_destination"
android:entryValues="@array/start_destination_values"
app:icon="@drawable/baseline_add_to_home_screen_24"
app:key="start_destination"
app:title="@string/preferred_home_screen" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_recommend_24"
android:key="home_recommendations"
app:summary="@string/video_recommendations_summary"
app:title="@string/video_recommendations" />
<ListPreference
android:defaultValue=""
android:entries="@array/countries"
android:entryValues="@array/countries_values"
app:icon="@drawable/ic_language"
app:dependency="home_recommendations"
app:key="locale"
app:title="@string/preferred_locale" />
<EditTextPreference
android:icon="@drawable/ic_key"
app:key="api_key"
app:defaultValue=""
app:summary="@string/api_key_summary"
app:title="@string/api_key" />
<SeekBarPreference
app:dependency="aria2"
android:defaultValue="3"
android:icon="@drawable/ic_lines"
android:max="25"
app:key="concurrent_fragments"
app:min="1"
app:showSeekBarValue="true"
android:dependency="aria2"
app:summary="@string/concurrent_fragments_summary"
app:title="@string/concurrent_fragments" />
<SeekBarPreference
android:defaultValue="1"
android:icon="@drawable/ic_concurrent_downloads"
android:max="10"
app:key="concurrent_downloads"
app:min="1"
app:showSeekBarValue="true"
app:summary="@string/concurrent_downloads_summary"
app:title="@string/concurrent_downloads" />
<EditTextPreference
android:icon="@drawable/ic_down"
app:key="limit_rate"
app:summary="@string/limit_rate_summary"
app:title="@string/limit_rate" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_speed"
android:key="aria2"
app:summary="@string/aria2_summary"
android:disableDependentsState="true"
app:title="Aria2" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_baseline_file_open_24"
android:key="log_downloads"
app:summary="@string/log_downloads_summary"
app:title="@string/log_downloads" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/processing">
<MultiSelectListPreference
app:defaultValue="@array/sponsorblock_settings_default"
app:icon="@drawable/ic_money"
app:dialogTitle="@string/select_sponsorblock_filtering"
app:entries="@array/sponsorblock_settings_entries"
app:entryValues="@array/sponsorblock_settings_values"
app:key="sponsorblock_filters"
app:summary="@string/select_sponsorblock_filtering"
app:title="SponsorBlock" />
<EditTextPreference
android:icon="@drawable/ic_textformat"
app:key="file_name_template"
app:defaultValue="%(uploader)s - %(title)s"
app:title="@string/file_name_template" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/if_file_rename"
app:key="restrict_filenames"
app:summary="@string/restrict_filenames_summary"
app:title="@string/restrict_filenames" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_clock"
app:key="mtime"
app:summary="@string/enable_mtime_summary"
app:title="@string/enable_mtime" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_subtitles"
app:key="embed_subtitles"
app:summary="@string/embed_subs_summary"
app:title="@string/embed_subtitles" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_subtitles_alternative"
app:key="write_subtitles"
app:summary="@string/save_subs_desc"
app:title="@string/save_subs" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_image"
app:key="embed_thumbnail"
app:summary="@string/embed_thumb_summary"
app:title="@string/embed_thumb" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_chapters"
app:key="add_chapters"
app:summary="@string/add_chapters_summary"
app:title="@string/add_chapters" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/ic_image"
app:key="write_thumbnail"
app:summary="@string/save_thumb_summary"
app:title="@string/save_thumb" />
<ListPreference
android:defaultValue="Default"
android:entries="@array/audio_containers"
android:entryValues="@array/audio_containers_values"
android:icon="@drawable/ic_code"
app:key="audio_format"
app:summary="@string/defaultValue"
app:title="@string/audio_format" />
<ListPreference
android:defaultValue="Default"
android:entries="@array/video_containers"
android:entryValues="@array/video_containers_values"
android:icon="@drawable/ic_code"
app:key="video_format"
app:summary="@string/defaultValue"
app:title="@string/video_format" />
<SeekBarPreference
android:defaultValue="0"
android:icon="@drawable/ic_music"
android:max="10"
app:key="audio_quality"
app:min="0"
app:showSeekBarValue="true"
app:summary="@string/audio_quality_summary"
app:title="@string/audio_quality" />
<ListPreference
android:defaultValue="@string/best_quality"
android:entries="@array/video_formats"
android:entryValues="@array/video_formats"
android:icon="@drawable/ic_video"
app:key="video_quality"
app:summary="@string/best_quality"
app:title="@string/video_quality" />
<EditTextPreference
app:key="format_id"
app:defaultValue=""
app:title="@string/preferred_format_id" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/updating">
<Preference
app:icon="@drawable/ic_update"
app:key="update_ytdl"
app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" />
<Preference
app:icon="@drawable/ic_info"
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
android:defaultValue="false"
app:icon="@drawable/ic_nightly"
app:key="nightly_ytdl"
android:title="@string/update_ytdl_nightly"
/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_format"
android:key="update_formats"
app:summary="@string/update_formats_summary"
app:title="@string/update_formats" />
<ListPreference
android:defaultValue="yt-dlp"
android:entries="@array/formats_source"
android:entryValues="@array/formats_source_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="formats_source"
app:summary="yt-dlp"
app:title="@string/format_source" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_update_app"
android:key="update_app"
app:summary="@string/update_app_summary"
app:title="@string/update_app" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/about">
<PreferenceCategory android:title="@string/backup">
<Preference <Preference
app:icon="@drawable/baseline_save_alt_24" app:icon="@drawable/baseline_save_alt_24"
app:key="export_preferences" app:key="backup"
app:title="@string/backup"/> app:title="@string/backup"/>
<Preference <Preference
app:icon="@drawable/baseline_restore_page_24" app:icon="@drawable/baseline_restore_page_24"
app:key="import_preferences" app:key="restore"
app:title="@string/restore"/> app:title="@string/restore"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/about" >
<Preference <Preference
app:icon="@drawable/ic_discord" app:icon="@drawable/ic_discord"
app:key="discord" app:key="discord"
@ -434,11 +80,6 @@
android:data="https://github.com/deniscerri/ytdlnis" /> android:data="https://github.com/deniscerri/ytdlnis" />
</Preference> </Preference>
<Preference
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"/>
</PreferenceCategory> </PreferenceCategory>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:icon="@drawable/ic_update"
app:key="update_ytdl"
app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" />
<Preference
app:icon="@drawable/ic_info"
app:key="ytdl-version"
app:title="@string/ytdl_version"/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
android:defaultValue="false"
app:icon="@drawable/ic_nightly"
app:key="nightly_ytdl"
android:title="@string/update_ytdl_nightly"
/>
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_format"
android:key="update_formats"
app:summary="@string/update_formats_summary"
app:title="@string/update_formats" />
<ListPreference
android:defaultValue="yt-dlp"
android:entries="@array/formats_source"
android:entryValues="@array/formats_source_values"
android:icon="@drawable/baseline_manage_search_24"
app:key="formats_source"
app:useSimpleSummaryProvider="true"
app:title="@string/format_source" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_update_app"
android:key="update_app"
app:summary="@string/update_app_summary"
app:title="@string/update_app" />
<Preference
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"/>
</PreferenceScreen>