From a5438eaff317f4d98d7f7bba816167c8183fddeb Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sun, 9 Feb 2025 14:05:00 +0100 Subject: [PATCH] more features & u can change cache folder now --- app/build.gradle | 2 +- .../models/YoutubePlayerClientItem.kt | 3 +- .../ytdl/ui/more/CookiesFragment.kt | 71 ++++++----- .../more/settings/FolderSettingsFragment.kt | 35 ++++++ .../java/com/deniscerri/ytdl/util/FileUtil.kt | 17 ++- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 21 +++- .../ytdl/util/extractors/YTDLPUtil.kt | 14 ++- .../main/res/drawable/baseline_help_24.xml | 5 + .../main/res/layout/cookie_bottom_sheet.xml | 117 ++++++++++++++++++ app/src/main/res/layout/cookie_details.xml | 67 ---------- .../main/res/layout/player_client_item.xml | 7 +- .../res/layout/youtube_player_clients.xml | 6 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/folders_preference.xml | 6 + 14 files changed, 263 insertions(+), 111 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_help_24.xml create mode 100644 app/src/main/res/layout/cookie_bottom_sheet.xml delete mode 100644 app/src/main/res/layout/cookie_details.xml diff --git a/app/build.gradle b/app/build.gradle index 9f690dd4..143685c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt index d9cd3061..d1f4d492 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt @@ -3,7 +3,8 @@ package com.deniscerri.ytdl.database.models data class YoutubePlayerClientItem( var playerClient: String, var poTokens: MutableList, - var enabled: Boolean = true + var enabled: Boolean = true, + var useOnlyPoToken: Boolean = false ) data class YoutubePoTokenItem( diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt index 052f3998..a7d715b2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt @@ -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(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(R.id.url_edittext) - layout.findViewById(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(R.id.url_edittext)!! val text = item?.url ?: "https://" editText.setText(text) editText.setSelection(editText.text.length) - val current = layout.findViewById(R.id.current) + val current = layout.findViewById(R.id.current)!! current.isVisible = item != null item?.apply { current.findViewById(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(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(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) { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt index ce7b172c..b22c6415 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt @@ -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 } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt index 0629f0ef..61a2e1e8 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt @@ -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) } } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index b2c4a4b8..c3579d94 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -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())) } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt index 84216d98..c6b709a2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt @@ -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 = 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}") } diff --git a/app/src/main/res/drawable/baseline_help_24.xml b/app/src/main/res/drawable/baseline_help_24.xml new file mode 100644 index 00000000..e6086731 --- /dev/null +++ b/app/src/main/res/drawable/baseline_help_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/cookie_bottom_sheet.xml b/app/src/main/res/layout/cookie_bottom_sheet.xml new file mode 100644 index 00000000..da7ddb00 --- /dev/null +++ b/app/src/main/res/layout/cookie_bottom_sheet.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +