more features & u can change cache folder now

This commit is contained in:
deniscerri 2025-02-09 14:05:00 +01:00
parent 60b42a0751
commit a5438eaff3
No known key found for this signature in database
GPG key ID: 95C43D517D830350
14 changed files with 263 additions and 111 deletions

View file

@ -159,7 +159,7 @@ dependencies {
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
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.6'
implementation 'androidx.compose.ui:ui-android:1.7.7'
implementation 'androidx.preference:preference-ktx:1.2.1'
testImplementation "junit:junit:$junitVer"
androidTestImplementation "junit:junit:$junitVer"

View file

@ -3,7 +3,8 @@ package com.deniscerri.ytdl.database.models
data class YoutubePlayerClientItem(
var playerClient: String,
var poTokens: MutableList<YoutubePoTokenItem>,
var enabled: Boolean = true
var enabled: Boolean = true,
var useOnlyPoToken: Boolean = false
)
data class YoutubePoTokenItem(

View file

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.RelativeLayout
@ -36,6 +37,9 @@ import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -45,7 +49,6 @@ import com.google.android.material.textfield.TextInputLayout
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
@ -118,7 +121,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
private fun initChips() {
val new = view?.findViewById<Chip>(R.id.newCookie)
new?.setOnClickListener {
showDialog(null)
showBottomSheet(null)
}
}
@ -172,17 +175,17 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
}
}
private fun showDialog(item: CookieItem?){
private fun showBottomSheet(item: CookieItem?){
lifecycleScope.launch {
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.cookies))
val layout = layoutInflater.inflate(R.layout.cookie_details, null)
val editText = layout.findViewById<EditText>(R.id.url_edittext)
layout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
val layout = BottomSheetDialog(requireContext())
layout.requestWindowFeature(Window.FEATURE_NO_TITLE)
layout.setContentView(R.layout.cookie_bottom_sheet)
val editText = layout.findViewById<EditText>(R.id.url_edittext)!!
val text = item?.url ?: "https://"
editText.setText(text)
editText.setSelection(editText.text.length)
val current = layout.findViewById<MaterialCardView>(R.id.current)
val current = layout.findViewById<MaterialCardView>(R.id.current)!!
current.isVisible = item != null
item?.apply {
current.findViewById<TextView>(R.id.currentText).apply {
@ -190,45 +193,51 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
enableTextHighlight()
}
}
builder.setView(layout)
item?.apply {
builder.setNeutralButton(
getString(android.R.string.copy)
) { dialog: DialogInterface?, which: Int ->
UiUtil.copyToClipboard(cookiesViewModel.cookieHeader + "\n" + item.content, requireActivity())
}
}
val clipboard = layout.findViewById<MaterialButton>(R.id.clipboard)!!
clipboard.isVisible = item != null
builder.setPositiveButton(
if (item == null) getString(R.string.get_cookies) else getString(R.string.update)
) { dialog: DialogInterface?, which: Int ->
val getCookies = layout.findViewById<MaterialButton>(R.id.getCookiesBtn)!!
getCookies.setOnClickListener {
val myIntent = Intent(requireContext(), WebViewActivity::class.java)
myIntent.putExtra("url", editText.text.toString())
layout.dismiss()
startActivity(myIntent)
}
// 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()
item?.apply {
clipboard.setOnClickListener {
UiUtil.copyToClipboard(cookiesViewModel.cookieHeader + "\n" + item.content, requireActivity())
layout.dismiss()
}
getCookies.text = getString(R.string.update)
}
dialog.show()
editText.doOnTextChanged { text, start, before, count ->
getCookies.isEnabled = editText.text.isNotEmpty()
}
val imm = mainActivity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
layout.show()
layout.behavior.state = BottomSheetBehavior.STATE_EXPANDED
layout.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
getCookies.isEnabled = editText.text.isNotEmpty()
}
}
override fun onItemClick(cookie: CookieItem) {
showDialog(cookie)
showBottomSheet(cookie)
}
override fun onSelected(cookie: CookieItem) {

View file

@ -38,6 +38,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
private var musicPath: Preference? = null
private var videoPath: Preference? = null
private var commandPath: Preference? = null
private var cachePath: Preference? = null
private var accessAllFiles : Preference? = null
private var noFragments: SwitchPreferenceCompat? = null
private var keepFragments: SwitchPreferenceCompat? = null
@ -62,6 +63,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
musicPath = findPreference("music_path")
videoPath = findPreference("video_path")
commandPath = findPreference("command_path")
cachePath = findPreference("cache_path")
accessAllFiles = findPreference("access_all_files")
noFragments = findPreference("no_part")
keepFragments = findPreference("keep_cache")
@ -80,6 +82,9 @@ class FolderSettingsFragment : BaseSettingsFragment() {
if (preferences.getString("command_path", "")!!.isEmpty()) {
editor.putString("command_path", FileUtil.getDefaultCommandPath())
}
if (preferences.getString("cache_path", "")!!.isEmpty()) {
editor.putString("cache_path", FileUtil.getCachePath(requireContext()))
}
if (FileUtil.hasAllFilesAccess()) {
accessAllFiles!!.isVisible = false
@ -119,6 +124,20 @@ class FolderSettingsFragment : BaseSettingsFragment() {
commandPathResultLauncher.launch(intent)
true
}
cachePath!!.summary = FileUtil.formatPath(preferences.getString("cache_path", "")!!)
cachePath!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.cache_directory), getString(R.string.cache_directory_warning)) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
cachePathResultLauncher.launch(intent)
}
true
}
if(VERSION.SDK_INT >= 30){
accessAllFiles!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
@ -271,6 +290,20 @@ class FolderSettingsFragment : BaseSettingsFragment() {
changePath(commandPath, result.data, COMMAND_PATH_CODE)
}
}
private var cachePathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let {
activity?.contentResolver?.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
changePath(cachePath, result.data, CACHE_PATH_CODE)
}
}
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
val path = data!!.data.toString()
@ -281,6 +314,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
MUSIC_PATH_CODE -> editor.putString("music_path", path)
VIDEO_PATH_CODE -> editor.putString("video_path", path)
COMMAND_PATH_CODE -> editor.putString("command_path", path)
CACHE_PATH_CODE -> editor.putString("cache_path", path)
}
editor.apply()
}
@ -289,5 +323,6 @@ class FolderSettingsFragment : BaseSettingsFragment() {
const val MUSIC_PATH_CODE = 33333
const val VIDEO_PATH_CODE = 55555
const val COMMAND_PATH_CODE = 77777
const val CACHE_PATH_CODE = 99999
}
}

View file

@ -22,6 +22,7 @@ import androidx.core.content.FileProvider
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.documentfile.provider.DocumentFile
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.callback.FolderCallback
@ -39,6 +40,7 @@ import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly
import okhttp3.internal.format
import okhttp3.internal.lowercase
import java.io.File
import java.net.URLDecoder
@ -283,11 +285,16 @@ object FileUtil {
}
fun getCachePath(context: Context) : String {
val externalPath = context.getExternalFilesDir(null)
return if (externalPath == null){
context.cacheDir.absolutePath + "/downloads/"
}else{
externalPath.absolutePath + "/downloads/"
val preference = PreferenceManager.getDefaultSharedPreferences(context).getString("cache_path", "")!!
if (preference.isEmpty() || !File(formatPath(preference)).canWrite()) {
val externalPath = context.getExternalFilesDir(null)
return if (externalPath == null){
context.cacheDir.absolutePath + "/downloads/"
}else{
externalPath.absolutePath + "/downloads/"
}
}else {
return formatPath(preference)
}
}

View file

@ -1761,6 +1761,17 @@ object UiUtil {
deleteDialog.show()
}
fun showGenericConfirmDialog(context: Context, title: String, content:String, accepted: () -> Unit){
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(title)
deleteDialog.setMessage(content)
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(context.getString(R.string.continue_anyway)) { _: DialogInterface?, _: Int ->
accepted()
}
deleteDialog.show()
}
fun showGenericDeleteAllDialog(context: Context, accepted: () -> Unit){
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete_multiple_items))
@ -2312,6 +2323,9 @@ object UiUtil {
val deleteBtn : Button = bottomSheet.findViewById(R.id.client_delete)!!
deleteBtn.isVisible = currentValue != null
val useOnlyPOToken : MaterialSwitch = bottomSheet.findViewById(R.id.use_only_po_token)!!
useOnlyPOToken.isChecked = currentValue?.useOnlyPoToken ?: false
val contentLinear : LinearLayout = bottomSheet.findViewById(R.id.contentLinear)!!
val defaultChips = context.getStringArray(R.array.youtube_player_clients).toMutableSet()
@ -2374,7 +2388,12 @@ object UiUtil {
return@setOnClickListener
}
val obj = YoutubePlayerClientItem(titleVal, mutableListOf())
if (useOnlyPOToken.isChecked && (poTokenInputs.filter { it.tag.toString().startsWith("potoken") }.all { it.editText!!.text.isBlank() })) {
poTokenInputs.first().error = "You need to write at least one PO Token"
return@setOnClickListener
}
val obj = YoutubePlayerClientItem(titleVal, mutableListOf(), true, useOnlyPOToken.isChecked)
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()))
}

View file

@ -344,6 +344,9 @@ class YTDLPUtil(private val context: Context) {
request.addOption("--print", "formats")
request.addOption("-a", urlsFile.absolutePath)
request.applyDefaultOptionsForFetchingData()
if (urls.all { it.isYoutubeURL() }) {
request.setYoutubeExtractorArgs()
}
val txt = parseYTDLRequestString(request)
println(txt)
@ -413,6 +416,9 @@ class YTDLPUtil(private val context: Context) {
request.addOption("--print", "%(formats)s")
request.addOption("--print", "%(duration)s")
request.applyDefaultOptionsForFetchingData()
if (url.isYoutubeURL()) {
request.setYoutubeExtractorArgs()
}
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
@ -495,6 +501,9 @@ class YTDLPUtil(private val context: Context) {
request.addOption("--get-url")
request.addOption("--print", "%(.{urls,chapters})s")
request.applyDefaultOptionsForFetchingData()
if (url.isYoutubeURL()) {
request.setYoutubeExtractorArgs()
}
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val json = JSONObject(youtubeDLResponse.out)
@ -1245,7 +1254,10 @@ class YTDLPUtil(private val context: Context) {
for (value in configuredPlayerClients) {
if (value.enabled) {
playerClients.add(value.playerClient)
if (!value.useOnlyPoToken) {
playerClients.add(value.playerClient)
}
value.poTokens.forEach { pt ->
poTokens.add("${value.playerClient}.${pt.context}+${pt.token}")
}

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" 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,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
</vector>

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:scrollbars="none"
android:orientation="vertical"
android:layout_height="wrap_content"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="@+id/scr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:paddingTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/current"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="5dp"
app:layout_constraintTop_toBottomOf="@+id/title">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cookies"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:textIsSelectable="true"
android:id="@+id/currentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</ScrollView>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginTop="5dp"
android:hint="@string/url"
app:layout_constraintTop_toBottomOf="@+id/scr">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:inputType="textMultiLine"
android:maxLines="2000" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/clipboard"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_marginStart="20dp"
android:layout_height="wrap_content"
app:icon="@drawable/ic_copy"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/getCookiesBtn"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:layout_margin="20dp"
android:text="@string/get_cookies"
app:icon="@drawable/baseline_cookie_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -1,67 +0,0 @@
<?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:padding="10dp"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/current"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginVertical="5dp"
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:orientation="vertical"
android:padding="10dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintHeight_max="300dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/currentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textIsSelectable="true"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/current">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -17,8 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="15dp"
android:paddingTop="15dp">
android:padding="15dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/drag_view"
@ -43,7 +42,7 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textSize="17sp"
android:textStyle="bold"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toStartOf="@+id/materialSwitch"
@ -56,7 +55,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="3"
android:textSize="12sp"
android:textSize="14sp"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toStartOf="@+id/materialSwitch"
app:layout_constraintStart_toEndOf="@id/drag_view"

View file

@ -170,6 +170,12 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_only_po_token"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/use_only_po_token"/>
</LinearLayout>
</LinearLayout>

View file

@ -457,4 +457,7 @@
<string name="layout">Layout</string>
<string name="list">List</string>
<string name="grid">Grid</string>
<string name="use_only_po_token">Use only PO Token (Ignore Player Client)</string>
<string name="cache_directory">Cache Folder</string>
<string name="cache_directory_warning">Changing the Cache Folder might cause unexpected issues. If the app cannot write to the configured path, it will resort to the default internal cache directory</string>
</resources>

View file

@ -20,6 +20,12 @@
app:key="command_path"
app:title="@string/command_directory" />
<Preference
android:defaultValue=""
app:icon="@drawable/baseline_folder_24"
app:key="cache_path"
app:title="@string/cache_directory" />
<Preference
app:icon="@drawable/ic_access_folder"
app:key="access_all_files"