even more features

This commit is contained in:
deniscerri 2025-01-26 23:04:29 +01:00
parent 7754e6f1bd
commit 1e329d3a8a
No known key found for this signature in database
GPG key ID: 95C43D517D830350
25 changed files with 1312 additions and 85 deletions

View file

@ -139,7 +139,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs_nio:2.1.2"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs_nio:2.1.4"
// implementation "com.github.yausername.youtubedl-android:library:$youtubedlAndroidVer"
// implementation "com.github.yausername.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
// implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer"
@ -150,16 +150,16 @@ dependencies {
implementation "io.github.junkfood02.youtubedl-android:aria2c:0.17.2"
implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.constraintlayout:constraintlayout:2.2.0"
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation "androidx.navigation:navigation-fragment-ktx:$navVer"
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.core:core-ktx:1.15.0'
implementation 'androidx.test.ext:junit-ktx:1.2.1'
implementation 'androidx.compose.ui:ui-android:1.7.4'
implementation 'androidx.compose.ui:ui-android:1.7.6'
implementation 'androidx.preference:preference-ktx:1.2.1'
testImplementation "junit:junit:$junitVer"
androidTestImplementation "junit:junit:$junitVer"
@ -184,7 +184,7 @@ dependencies {
implementation 'androidx.paging:paging-runtime-ktx:3.3.2'
implementation "androidx.room:room-paging:$roomVer"
androidTestImplementation "androidx.room:room-testing:$roomVer"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.6'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
implementation "androidx.compose.runtime:runtime:$composeVer"
androidTestImplementation 'com.google.truth:truth:1.1.5'
@ -196,7 +196,7 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.6"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
implementation "com.neoutils.highlight:highlight-view:2.2.0"
// For media playback using ExoPlayer
@ -212,8 +212,8 @@ dependencies {
implementation "com.anggrayudi:storage:1.5.5"
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
implementation 'androidx.compose.material3:material3-android:1.3.0'
implementation 'androidx.compose.material3:material3-android:1.3.1'
implementation "io.noties.markwon:core:4.6.2"
implementation("org.greenrobot:eventbus:3.3.1")
implementation("com.github.teamnewpipe:newpipeextractor:0.24.2")
implementation("com.github.teamnewpipe:newpipeextractor:0.24.4")
}

View file

@ -0,0 +1,12 @@
package com.deniscerri.ytdl.database.models
data class YoutubePlayerClientItem(
var playerClient: String,
var poTokens: MutableList<YoutubePoTokenItem>,
var enabled: Boolean = true
)
data class YoutubePoTokenItem(
var context: String,
var token: String
)

View file

@ -0,0 +1,121 @@
package com.deniscerri.ytdl.ui.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CookieItem
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.databinding.FormatItemBinding
import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter.ViewHolder
import com.deniscerri.ytdl.util.Extensions.popup
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.card.MaterialCardView
class FormatAdapter(onItemClickListener: OnItemClickListener, activity: Activity, private val downloadType: DownloadViewModel.Type) : ListAdapter<Format?, FormatAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private var selectedVideoFormat: Format?
private val selectedAudioFormats: MutableList<Format>
private val usingGrid: Boolean
init {
this.onItemClickListener = onItemClickListener
this.activity = activity
this.selectedVideoFormat = null
this.usingGrid = false
this.selectedAudioFormats = mutableListOf()
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val item: MaterialCardView
init {
item = itemView.findViewById(R.id.format_card_constraintLayout)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (usingGrid){
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.format_item_grid, parent, false)
ViewHolder(
cardView,
onItemClickListener
)
}else{
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.format_item, parent, false)
ViewHolder(
cardView,
onItemClickListener
)
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) ?: return
val card = holder.item
card.popup()
UiUtil.populateFormatCard(activity, card, item)
card.setOnClickListener {
when(downloadType) {
DownloadViewModel.Type.audio -> {
onItemClickListener.onItemClick(item)
}
DownloadViewModel.Type.video -> {
if(item.isAudio()) {
if (card.isChecked) {
selectedAudioFormats.remove(item)
}else {
selectedAudioFormats.add(item)
}
}else {
if (card.isChecked) {
onItemClickListener.onItemClick(item)
}else {
selectedVideoFormat = item
card.isChecked = true
}
}
}
else -> {}
}
}
}
private fun Format.isAudio() : Boolean {
return this.vcodec.isNotBlank() && this.vcodec != "none"
}
interface OnItemClickListener {
fun onItemClick(item: Format)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<Format> = object : DiffUtil.ItemCallback<Format>() {
override fun areItemsTheSame(oldItem: Format, newItem: Format): Boolean {
return oldItem.format_id == newItem.format_id
}
override fun areContentsTheSame(oldItem: Format, newItem: Format): Boolean {
return oldItem.format_id == newItem.format_id
}
}
}
}

View file

@ -0,0 +1,126 @@
package com.deniscerri.ytdl.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
import com.deniscerri.ytdl.util.Extensions.popup
import com.google.android.material.card.MaterialCardView
import com.google.android.material.materialswitch.MaterialSwitch
class YoutubePlayerClientAdapter(onItemClickListener: OnItemClickListener, activity: Activity, private var itemTouchHelper: ItemTouchHelper) : ListAdapter<YoutubePlayerClientItem?, YoutubePlayerClientAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
var showDragHandle: Boolean
private val sharedPreferences: SharedPreferences
init {
this.onItemClickListener = onItemClickListener
this.activity = activity
this.showDragHandle = false
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val item: MaterialCardView
init {
item = itemView.findViewById(R.id.card)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.player_client_item, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)!!
holder.itemView.tag = item.playerClient
val card = holder.item
val title = card.findViewById<TextView>(R.id.title)
title.text = item.playerClient
val content = card.findViewById<TextView>(R.id.content)
if (item.poTokens.isNotEmpty()) {
val text = item.poTokens.joinToString("\n") { "PO Token (${it.context}): ${it.token}" }
content.text = text
}else {
content.isVisible = false
}
val switch = card.findViewById<MaterialSwitch>(R.id.materialSwitch)
switch.isChecked = item.enabled
switch.setOnClickListener {
onItemClickListener.onStatusToggle(item, switch.isChecked, position)
}
//DRAG HANDLE
val dragView = card.findViewById<View>(R.id.drag_view)
dragView.isVisible = showDragHandle
dragView.setOnTouchListener { view, motionEvent ->
view.performClick()
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN){
itemTouchHelper.startDrag(holder)
}
true
}
card.setOnClickListener {
onItemClickListener.onItemClick(item, position)
}
card.setOnLongClickListener {
onItemClickListener.onDeleteClick(item, position)
true
}
}
@SuppressLint("NotifyDataSetChanged")
fun toggleShowDragHandle(){
showDragHandle = !showDragHandle
notifyDataSetChanged()
}
interface OnItemClickListener {
fun onItemClick(item: YoutubePlayerClientItem, index: Int)
fun onStatusToggle(item: YoutubePlayerClientItem, enabled: Boolean, index: Int)
fun onDeleteClick(item: YoutubePlayerClientItem, index: Int)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<YoutubePlayerClientItem> = object : DiffUtil.ItemCallback<YoutubePlayerClientItem>() {
override fun areItemsTheSame(oldItem: YoutubePlayerClientItem, newItem: YoutubePlayerClientItem): Boolean {
return oldItem.playerClient == newItem.playerClient
}
override fun areContentsTheSame(oldItem: YoutubePlayerClientItem, newItem: YoutubePlayerClientItem): Boolean {
return oldItem.playerClient == newItem.playerClient && oldItem.enabled == newItem.enabled && oldItem.poTokens.joinToString(
"\n"
) { it.token } == newItem.poTokens.joinToString(
"\n"
) { it.token }
}
}
}
}

View file

@ -2,13 +2,18 @@ package com.deniscerri.ytdl.ui.downloadcard
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.*
import android.content.DialogInterface
import android.content.SharedPreferences
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.Window
import android.widget.*
import android.widget.Button
import android.widget.GridLayout
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.isVisible
@ -52,8 +57,8 @@ class FormatSelectionBottomSheetDialog(
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var sharedPreferences: SharedPreferences
private lateinit var videoFormatList : LinearLayout
private lateinit var audioFormatList : LinearLayout
private lateinit var videoFormatList : GridLayout
private lateinit var audioFormatList : GridLayout
private lateinit var okBtn : Button
private lateinit var refreshBtn: Button
private lateinit var videoTitle : TextView
@ -80,6 +85,8 @@ class FormatSelectionBottomSheetDialog(
private lateinit var genericAudioFormats : List<Format>
private lateinit var genericVideoFormats : List<Format>
private var usingGrid: Boolean = false
enum class FormatSorting {
filesize, container, codec, id
}
@ -135,6 +142,7 @@ class FormatSelectionBottomSheetDialog(
sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
filterBy = FormatCategory.valueOf(sharedPreferences.getString("format_filter", "ALL")!!)
filterBtn = view.findViewById(R.id.format_filter)
usingGrid = sharedPreferences.getBoolean("format_list_grid", false)
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
@ -438,7 +446,32 @@ class FormatSelectionBottomSheetDialog(
}
//format layout
val listLayout = filterSheet.findViewById<TextView>(R.id.layout_list)!!
val gridLayout = filterSheet.findViewById<TextView>(R.id.layout_grid)!!
if (usingGrid) {
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
}else{
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
}
listLayout.setOnClickListener {
usingGrid = false
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
addFormatsToView()
filterSheet.dismiss()
}
gridLayout.setOnClickListener {
usingGrid = true
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
addFormatsToView()
filterSheet.dismiss()
}
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
@ -535,8 +568,6 @@ class FormatSelectionBottomSheetDialog(
val canMultiSelectAudio = items.first()?.type == Type.video && finalFormats.find { it.vcodec.isBlank() || it.vcodec == "none" } != null
videoFormatList.removeAllViews()
audioFormatList.removeAllViews()
if (!canMultiSelectAudio) {
audioFormatList.visibility = View.GONE
@ -561,6 +592,14 @@ class FormatSelectionBottomSheetDialog(
videoFormatList.removeAllViews()
audioFormatList.removeAllViews()
if (usingGrid) {
videoFormatList.columnCount = 2
audioFormatList.columnCount = 2
}else{
videoFormatList.columnCount = 1
audioFormatList.columnCount = 1
}
if (finalFormats.isEmpty()){
finalFormats = if (items.first()?.type == Type.audio){
genericAudioFormats
@ -571,7 +610,23 @@ class FormatSelectionBottomSheetDialog(
for (i in 0.. finalFormats.lastIndex){
val format = finalFormats[i]
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
lateinit var formatItem: View
if (usingGrid){
formatItem = LayoutInflater.from(context).inflate(R.layout.format_item_grid, null)
formatItem.layoutParams = GridLayout.LayoutParams(
GridLayout.spec(GridLayout.UNDEFINED, 1f),
GridLayout.spec(GridLayout.UNDEFINED, 1f)).apply {
width = 0
}
}else{
formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
formatItem.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT,
1.0f
)
}
formatItem.tag = "${format.format_id}${format.format_note}"
UiUtil.populateFormatCard(requireContext(), formatItem as MaterialCardView, format, null)
if (selectedVideo == format) formatItem.isChecked = true

View file

@ -1,28 +0,0 @@
package com.deniscerri.ytdl.ui.more.settings
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.graphics.Typeface
import android.os.Bundle
import android.widget.LinearLayout
import android.widget.TextView
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.ui.adapter.SortableTextItemAdapter
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class AdvancedSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.advanced
@SuppressLint("RestrictedApi")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.advanced_preferences, rootKey)
}
}

View file

@ -334,7 +334,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
}
findPreference<MultiSelectListPreference>("swipe_gesture")?.apply {
val s = getString(R.string.preferred_search_engine_summary)
val s = getString(R.string.swipe_gestures_summary)
if (values.size == entries.size) {
summary = "${s}\n[${getString(R.string.all)}]"
}else if (values.size > 0) {

View file

@ -0,0 +1,23 @@
package com.deniscerri.ytdl.ui.more.settings.advanced
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.ui.more.settings.BaseSettingsFragment
import com.deniscerri.ytdl.util.UiUtil
class AdvancedSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.advanced
@SuppressLint("RestrictedApi")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.advanced_preferences, rootKey)
findPreference<Preference>("yt_player_client")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.youtubePlayerClientFragment)
false
}
}
}

View file

@ -0,0 +1,235 @@
package com.deniscerri.ytdl.ui.more.settings.advanced
import android.animation.AnimatorSet
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.RelativeLayout
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
import com.deniscerri.ytdl.ui.adapter.YoutubePlayerClientAdapter
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.common.reflect.TypeToken
import com.google.gson.Gson
class YoutubePlayerClientFragment : Fragment(), YoutubePlayerClientAdapter.OnItemClickListener {
private lateinit var recyclerView: RecyclerView
private lateinit var listAdapter: YoutubePlayerClientAdapter
private lateinit var noResults : RelativeLayout
private lateinit var settingsActivity: SettingsActivity
private lateinit var preferences: SharedPreferences
private lateinit var currentList: MutableList<YoutubePlayerClientItem>
private lateinit var currentListRaw: String
private lateinit var dragHandle: View
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
settingsActivity = activity as SettingsActivity
settingsActivity.changeTopAppbarTitle(getString(R.string.player_client))
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return inflater.inflate(R.layout.fragment_youtube_player_clients, container, false)
}
@SuppressLint("RestrictedApi")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
noResults = view.findViewById(R.id.no_results)
dragHandle = view.findViewById(R.id.drag)
val itemTouchHelper = ItemTouchHelper(dragDropHelper)
listAdapter = YoutubePlayerClientAdapter(this,settingsActivity, itemTouchHelper)
recyclerView = view.findViewById(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = listAdapter
itemTouchHelper.attachToRecyclerView(recyclerView)
currentListRaw = preferences.getString("youtube_player_clients", "[]")!!
val itemType = object : TypeToken<List<YoutubePlayerClientItem>>() {}.type
currentList = Gson().fromJson<List<YoutubePlayerClientItem>>(currentListRaw, itemType).toMutableList()
listAdapter.submitList(currentList.toList())
val newClient = view.findViewById<Chip>(R.id.newClient)
newClient.setOnClickListener {
UiUtil.showYoutubePlayerClientSheet(
settingsActivity, preferences, null,
newValue = { newItem ->
currentList.add(newItem)
currentListRaw = Gson().toJson(currentList).toString()
updateRecords()
listAdapter.submitList(currentList.toList())
checkNoResults()
},
deleted = {
}
)
}
checkNoResults()
dragHandle.setOnClickListener {
listAdapter.toggleShowDragHandle()
}
}
private fun updateRecords() {
preferences.edit().putString("youtube_player_clients", currentListRaw).apply()
}
private fun checkNoResults() {
val hasNoResults = currentList.isEmpty()
noResults.isVisible = hasNoResults
recyclerView.isVisible = !hasNoResults
dragHandle.isVisible = !hasNoResults
}
override fun onItemClick(item: YoutubePlayerClientItem, index: Int) {
UiUtil.showYoutubePlayerClientSheet(
settingsActivity, preferences, item,
newValue = { newItem ->
currentList.remove(item)
currentList.add(newItem)
currentListRaw = Gson().toJson(currentList).toString()
updateRecords()
listAdapter.submitList(currentList.toList())
},
deleted = {
onDeleteClick(item, index)
}
)
}
override fun onStatusToggle(item: YoutubePlayerClientItem, enabled: Boolean, index: Int) {
currentList[currentList.indexOf(item)].enabled = enabled
currentListRaw = Gson().toJson(currentList).toString()
updateRecords()
listAdapter.submitList(currentList.toList())
}
override fun onDeleteClick(item: YoutubePlayerClientItem, index: Int) {
UiUtil.showGenericDeleteDialog(requireContext(), item.playerClient) {
currentList.remove(item)
currentListRaw = Gson().toJson(currentList).toString()
updateRecords()
listAdapter.submitList(currentList.toList())
checkNoResults()
}
}
var movedToNewPositionTag = ""
private val dragDropHelper: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val fromPosition = viewHolder.bindingAdapterPosition
val toPosition = target.bindingAdapterPosition
movedToNewPositionTag = target.itemView.tag.toString()
listAdapter.notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
override fun onSelectedChanged(
viewHolder: RecyclerView.ViewHolder?,
actionState: Int
) {
super.onSelectedChanged(viewHolder, actionState)
if (ItemTouchHelper.ACTION_STATE_DRAG == actionState) {
/**
* Change alpha, scale and elevation on drag.
*/
(viewHolder?.itemView as? MaterialCardView)?.also {
AnimatorSet().apply {
this.duration = 100L
this.interpolator = AccelerateDecelerateInterpolator()
playTogether(
UiUtil.getAlphaAnimator(it, 0.7f),
UiUtil.getScaleXAnimator(it, 1.02f),
UiUtil.getScaleYAnimator(it, 1.02f),
UiUtil.getElevationAnimator(it, R.dimen.elevation_6dp)
)
}.start()
}
}
}
override fun clearView(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
) {
super.clearView(recyclerView, viewHolder)
/**
* Clear alpha, scale and elevation after drag/swipe
*/
(viewHolder.itemView as? MaterialCardView)?.also {
AnimatorSet().apply {
this.duration = 100L
this.interpolator = AccelerateDecelerateInterpolator()
playTogether(
UiUtil.getAlphaAnimator(it, 1f),
UiUtil.getScaleXAnimator(it, 1f),
UiUtil.getScaleYAnimator(it, 1f),
UiUtil.getElevationAnimator(it, R.dimen.elevation_2dp)
)
}.start()
}
val currentIndex = currentList.indexOfFirst { it.playerClient == viewHolder.itemView.tag.toString() }
val newIndex = currentList.indexOfFirst { it.playerClient == movedToNewPositionTag }
val newList = mutableListOf<YoutubePlayerClientItem>()
if (currentIndex != newIndex && currentIndex >= 0 && newIndex >= 0) {
Log.e("AAAAAAAAAAAAA", "${currentIndex} ${newIndex}")
if(currentIndex > newIndex) {
newList.addAll(currentList.subList(0, newIndex))
newList.add(currentList[currentIndex])
currentList.remove(currentList[currentIndex])
newList.addAll(currentList.subList(newIndex, currentList.size))
}else{
val toMove = currentList[currentIndex]
newList.addAll(currentList.subList(0, currentIndex))
newList.addAll(currentList.subList(currentIndex + 1, newIndex + 1))
newList.add(toMove)
newList.addAll(currentList.subList(newIndex + 1, currentList.size))
}
currentList.clear()
currentList.addAll(newList)
currentListRaw = Gson().toJson(currentList).toString()
updateRecords()
listAdapter.submitList(currentList.toList())
listAdapter.notifyDataSetChanged()
}
}
override fun isLongPressDragEnabled(): Boolean {
return false
}
}
}

View file

@ -15,6 +15,7 @@ import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.text.format.DateFormat
import android.util.DisplayMetrics
@ -46,6 +47,8 @@ import androidx.core.text.HtmlCompat
import androidx.core.text.parseAsHtml
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentManager
@ -60,6 +63,8 @@ import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.models.TemplateShortcut
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
import com.deniscerri.ytdl.database.models.YoutubePoTokenItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
@ -86,10 +91,13 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.google.gson.Gson
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -2284,6 +2292,103 @@ object UiUtil {
parentView.addView(child)
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun showYoutubePlayerClientSheet(context: Activity, preferences: SharedPreferences, currentValue: YoutubePlayerClientItem?, newValue: (item: YoutubePlayerClientItem) -> Unit, deleted: () -> Unit){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.youtube_player_clients)
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val chipGroup : ChipGroup = bottomSheet.findViewById(R.id.chipGroup)!!
val suggestedLabel : View = bottomSheet.findViewById(R.id.suggestedLabel)!!
val okBtn : Button = bottomSheet.findViewById(R.id.client_create)!!
val deleteBtn : Button = bottomSheet.findViewById(R.id.client_delete)!!
deleteBtn.isVisible = currentValue != null
val contentLinear : LinearLayout = bottomSheet.findViewById(R.id.contentLinear)!!
val defaultChips = context.getStringArray(R.array.youtube_player_clients).toMutableSet()
title.isEndIconVisible = false
title.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
chipGroup.children.forEach { (it as Chip).isChecked = false }
chipGroup.children.firstOrNull { (it as Chip).text == p0.toString() }?.apply {
(this as Chip).isChecked = true
}
}
})
val existingConfigsRaw = preferences.getString("youtube_player_clients", "[]")
val itemType = object : com.google.common.reflect.TypeToken<List<YoutubePlayerClientItem>>() {}.type
val existingConfigs = Gson().fromJson<List<YoutubePlayerClientItem>>(existingConfigsRaw, itemType).toMutableList()
defaultChips.filter { it.isNotBlank() }.forEach {
if (!existingConfigs.any { it2 -> it2.playerClient == it }) {
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
tmp.text = it
tmp.setOnClickListener {
title.editText!!.setText(tmp.text.toString())
}
chipGroup.addView(tmp)
}
}
if (chipGroup.children.count() == 0) {
suggestedLabel.isVisible = false
}
val poTokenInputs = contentLinear.children.filter { it is TextInputLayout }.map { it as TextInputLayout }.toList()
for(p in poTokenInputs) {
p.setEndIconOnClickListener {
val clipboard: ClipboardManager = context.getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
p.editText!!.setText(clipboard.primaryClip?.getItemAt(0)?.text)
}
}
currentValue?.apply {
title.editText!!.setText(this.playerClient)
for (t in this.poTokens) {
poTokenInputs.firstOrNull { it.tag == "potoken_${t.context}" }?.editText?.setText(t.token)
}
okBtn.text = context.getString(R.string.edit)
}
okBtn.setOnClickListener {
val titleVal = title.editText!!.text.toString()
if (titleVal.isBlank()) {
title.error = "Player Client tag shouldn't be empty"
return@setOnClickListener
}
if(existingConfigs.any { it2 -> it2.playerClient == titleVal } && ( currentValue == null || currentValue.playerClient != titleVal )) {
title.error = "Player Client is already created"
return@setOnClickListener
}
val obj = YoutubePlayerClientItem(titleVal, mutableListOf())
poTokenInputs.filter { it.editText!!.text.isNotBlank() && it.tag.toString().startsWith("potoken") }.forEach {
obj.poTokens.add(YoutubePoTokenItem(it.tag.toString().split("potoken_")[1], it.editText!!.text.toString()))
}
bottomSheet.cancel()
newValue(obj)
}
deleteBtn.setOnClickListener {
bottomSheet.cancel()
deleted()
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(

View file

@ -16,6 +16,7 @@ import com.deniscerri.ytdl.database.models.ChapterItem
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.ResultItem
import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.util.Extensions.getIntByAny
@ -1237,27 +1238,40 @@ class YTDLPUtil(private val context: Context) {
}
private fun YoutubeDLRequest.setYoutubeExtractorArgs() {
val playerClient = sharedPreferences.getString("yt_player_client", "")!!.split(",").filter { it.isNotBlank() }.toMutableList()
val extractorArgs = mutableListOf<String>()
val playerClients = mutableListOf<String>()
val poTokens = mutableListOf<String>()
val poToken = sharedPreferences.getString("youtube_po_token", "")!!
if (poToken.isNotBlank() && !playerClient.contains("web")) {
playerClient.add("web")
val configuredPlayerClientsRaw = sharedPreferences.getString("youtube_player_clients", "[]")!!
val itemType = object : com.google.common.reflect.TypeToken<List<YoutubePlayerClientItem>>() {}.type
val configuredPlayerClients = Gson().fromJson<List<YoutubePlayerClientItem>>(configuredPlayerClientsRaw, itemType).toMutableList()
for (value in configuredPlayerClients) {
if (value.enabled) {
playerClients.add(value.playerClient)
value.poTokens.forEach { pt ->
poTokens.add("${value.playerClient}.${pt.context}+${pt.token}")
}
}
}
if (playerClient.isNotEmpty()){
extractorArgs.add("player_client=${playerClient.joinToString(",")}")
if (playerClients.isNotEmpty()){
extractorArgs.add("player_client=${playerClients.joinToString(",")}")
}
val lang = Locale.getDefault().language
val langTag = Locale.getDefault().toLanguageTag()
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs.add("lang=$lang")
}else if (context.getStringArray(R.array.subtitle_langs).contains(langTag)) {
extractorArgs.add("lang=$langTag")
if (poTokens.isNotEmpty()) {
extractorArgs.add("po_token=${poTokens.joinToString(",")}")
}
if (poToken.isNotBlank()) {
extractorArgs.add("po_token=web+$poToken")
val useLanguageForMetadata = sharedPreferences.getBoolean("use_app_language_for_metadata", true)
if (useLanguageForMetadata) {
val lang = Locale.getDefault().language
val langTag = Locale.getDefault().toLanguageTag()
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs.add("lang=$lang")
}else if (context.getStringArray(R.array.subtitle_langs).contains(langTag)) {
extractorArgs.add("lang=$langTag")
}
}
val otherArgs = sharedPreferences.getString("youtube_other_extractor_args", "")!!

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:colorAccent" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View file

@ -110,8 +110,9 @@
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toStartOf="@+id/active_pause_cancel_btns"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview">
app:layout_constraintTop_toTopOf="@+id/active_pause_cancel_btns">
<TextView
android:id="@+id/title"
@ -120,7 +121,6 @@
android:ellipsize="end"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:shadowColor="@color/black"
android:shadowDx="2"
@ -200,8 +200,6 @@
</LinearLayout>
<TextView
android:id="@+id/output"
android:layout_width="match_parent"
@ -222,7 +220,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -83,7 +83,6 @@
</LinearLayout>
<LinearLayout
android:id="@+id/format_source_linear"
android:layout_width="match_parent"
@ -103,5 +102,54 @@
</LinearLayout>
<LinearLayout
android:id="@+id/format_layout_linear"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="20dp"
android:text="@string/layout"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/layout_list"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:drawablePadding="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="@string/list"
app:drawableStartCompat="@drawable/ic_check" />
<TextView
android:id="@+id/layout_grid"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:drawablePadding="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="@string/grid"
app:drawableStartCompat="@drawable/empty" />
</LinearLayout>
</LinearLayout>

View file

@ -22,11 +22,12 @@
<TextView
android:id="@+id/container"
android:layout_width="60dp"
android:layout_height="0dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorPrimaryInverse"
android:textColor="@color/white"
android:clickable="false"
android:layout_height="55dp"
app:layout_constraintDimensionRatio="H,1:1"
android:gravity="center"
android:minWidth="30dp"
app:cornerRadius="10dp"

View file

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/format_card_constraintLayout"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:paddingVertical="5dp"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/container"
android:layout_width="50dp"
app:layout_constraintHorizontal_weight="1"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorPrimaryInverse"
android:textColor="@color/white"
android:clickable="false"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,1:1"
android:gravity="center"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/format_loading_progress"
android:visibility="gone"
android:layout_width="0dp"
android:indeterminate="true"
app:trackColor="#000"
android:layout_gravity="bottom"
android:alpha="0.3"
android:scaleY="200"
app:layout_constraintTop_toTopOf="@id/container"
app:layout_constraintBottom_toBottomOf="@id/container"
app:layout_constraintStart_toStartOf="@id/container"
app:layout_constraintEnd_toEndOf="@id/container"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/format_note"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="top"
android:maxLines="2"
android:paddingHorizontal="5dp"
android:textDirection="locale"
android:textSize="17sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/container"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/audio_formats"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorPrimary"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:drawableStartCompat="@drawable/ic_music_formatcard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/container"
app:layout_constraintTop_toBottomOf="@+id/format_note" />
<androidx.constraintlayout.widget.Barrier
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/barrier"
app:barrierDirection="bottom"
app:constraint_referenced_ids="container, audio_formats" />
<com.google.android.material.chip.ChipGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
app:layout_constraintEnd_toEndOf="parent"
app:chipSpacingHorizontal="0dp"
app:chipSpacingVertical="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container">
<TextView
android:id="@+id/format_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/backgroundColor"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/codec"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorSecondary"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -134,11 +134,12 @@
android:paddingVertical="20dp"
android:text="@string/video" />
<LinearLayout
<GridLayout
android:id="@+id/video_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
android:columnCount="1"
android:alignmentMode="alignBounds"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
@ -160,11 +161,12 @@
android:paddingVertical="20dp"
android:text="@string/audio" />
<LinearLayout
<GridLayout
android:id="@+id/audio_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
android:columnCount="1"
android:alignmentMode="alignBounds"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.google.android.material.chip.Chip
android:id="@+id/newClient"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:layout_marginBottom="10dp"
android:text="@string/add"
app:chipIcon="@drawable/ic_plus" />
<TextView
android:id="@+id/drag"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginHorizontal="15dp"
android:layout_marginBottom="10dp"
android:padding="10dp"
app:drawableStartCompat="@drawable/ic_drag_handle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:orientation="vertical"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/newClient"
android:layout_alignParentBottom="true" />
</RelativeLayout>
<include layout="@layout/no_results"
android:visibility="gone"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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/card"
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="15dp"
android:paddingTop="15dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/drag_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:clickable="false"
android:focusable="false"
android:paddingVertical="16dp"
android:paddingStart="0dp"
android:paddingEnd="20dp"
android:visibility="gone"
android:tintMode="src_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_drag_handle"
app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toStartOf="@+id/materialSwitch"
app:layout_constraintStart_toEndOf="@id/drag_view"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:textSize="12sp"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toStartOf="@+id/materialSwitch"
app:layout_constraintStart_toEndOf="@id/drag_view"
app:layout_constraintTop_toBottomOf="@+id/title" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/materialSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
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="wrap_content"
android:layout_height="wrap_content"
android:text="@string/player_client"
android:textSize="25sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="YouTube"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title"
tools:ignore="HardcodedText" />
<Button
android:id="@+id/client_delete"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:outlineProvider="none"
app:icon="@drawable/baseline_delete_24"
android:contentDescription="@string/Remove"
app:layout_constraintBottom_toBottomOf="@id/client_create"
app:layout_constraintEnd_toStartOf="@+id/client_create"
app:layout_constraintTop_toTopOf="@id/client_create" />
<Button
android:id="@+id/client_create"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/create"
app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/contentLinear"
android:padding="15dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title"
android:tag="title"
android:paddingBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/suggestedLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/suggested" />
<HorizontalScrollView
android:layout_width="match_parent"
android:paddingBottom="10dp"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
app:chipSpacingVertical="-10dp"
app:singleSelection="true"
app:chipSpacing="0dp"
android:layout_height="wrap_content"
app:singleLine="true">
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<TextView
android:id="@+id/poTokenLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="PO Token"
tools:ignore="HardcodedText" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/po_token_gvs"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="potoken_gvs"
android:hint="PO Token (GVS)"
app:endIconDrawable="@drawable/ic_clipboard"
app:endIconMode="custom"
android:paddingBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/po_token_player"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="potoken_player"
android:hint="PO Token (Player)"
app:endIconDrawable="@drawable/ic_clipboard"
app:endIconMode="custom"
android:paddingBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -47,6 +47,14 @@
</fragment>
<fragment
android:id="@+id/advancedSettingsFragment"
android:name="com.deniscerri.ytdl.ui.more.settings.AdvancedSettingsFragment"
android:label="AdvancedSettingsFragment" />
android:name="com.deniscerri.ytdl.ui.more.settings.advanced.AdvancedSettingsFragment"
android:label="AdvancedSettingsFragment" >
<action
android:id="@+id/action_advancedSettingsFragment_to_youtubePlayerClientFragment"
app:destination="@id/youtubePlayerClientFragment" />
</fragment>
<fragment
android:id="@+id/youtubePlayerClientFragment"
android:name="com.deniscerri.ytdl.ui.more.settings.advanced.YoutubePlayerClientFragment"
android:label="YoutubePlayerClientFragment" />
</navigation>

View file

@ -1527,4 +1527,18 @@
<item>NEWPIPE</item>
</string-array>
<string-array name="youtube_player_clients">
<item>android</item>
<item>ios</item>
<item>web</item>
<item>mweb</item>
<item>android_vr</item>
<item>web_safari</item>
<item>web_embedded</item>
<item>tv</item>
<item>tv_embedded</item>
<item>default</item>
<item>all</item>
</string-array>
</resources>

View file

@ -451,4 +451,10 @@
<string name="minutes">Minutes</string>
<string name="prefer_container_over_codec_audio">Prefer Container over Codec For Audio Downloads</string>
<string name="prefer_container_over_codec_audio_summary">With this enabled, the preferred audio codec preference will be ignored and only container will change</string>
<string name="player_client">Player Client</string>
<string name="player_client_summary"><![CDATA[YouTube Player Clients & PO Token]]></string>
<string name="use_app_language_for_metadata">Use App Language for Metadata</string>
<string name="layout">Layout</string>
<string name="list">List</string>
<string name="grid">Grid</string>
</resources>

View file

@ -3,19 +3,19 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="YouTube">
<EditTextPreference
<Preference
android:defaultValue=""
android:icon="@drawable/ic_code"
android:icon="@drawable/baseline_person_24"
android:key="yt_player_client"
app:useSimpleSummaryProvider="true"
android:title="Player Client" />
android:summary="@string/player_client_summary"
android:title="@string/player_client" />
<EditTextPreference
android:defaultValue=""
android:icon="@drawable/ic_code"
android:key="youtube_po_token"
app:useSimpleSummaryProvider="true"
android:title="PO Token [Web]" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
android:icon="@drawable/ic_language"
android:defaultValue="true"
android:key="use_app_language_for_metadata"
android:title="@string/use_app_language_for_metadata" />
<EditTextPreference
android:defaultValue=""
@ -27,6 +27,7 @@
<PreferenceCategory android:title="@string/command_templates">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
android:icon="@drawable/ic_terminal"
android:key="enable_data_fetching_extra_commands"
android:summary="@string/data_fetching_extra_command_summary"
@ -35,6 +36,7 @@
<PreferenceCategory android:title="@string/downloading" android:enabled="false">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
android:icon="@drawable/if_file_rename"
android:key="disable_write_info_json"
android:summary="@string/disable_write_info_json_summary"

View file

@ -25,7 +25,7 @@ buildscript {
retrofitVer = "2.9.0"
kodeinVer = "7.16.0"
navVer = "2.8.3"
media3_version = "1.4.1"
media3_version = "1.5.1"
agp_version = '8.1.0'
agp_version1 = '7.4.2'
roomVer = '2.5.2'