added auto preferred download type
Show collected file size in multi download card
fixed app chossing worst format and not best
fixed update util not working in release
fixed ytm playlists not loading
fixed sponsorblock not being checked in multiple download card
added playlist and playlist index metadata in download items
added -a txt path if preferred download type is command or if the txt file is shared in terminal
fixed app cancelling workmanager earlier than expected
added title and author sync between audio and video frgaments
pull/280/head
deniscerri 3 years ago
parent a1510cdcfa
commit 236f83d161
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -56,9 +56,7 @@
android:theme="@style/Theme.BottomSheet">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>

@ -18,6 +18,7 @@ import android.view.View
import android.view.WindowInsets
import android.widget.CheckBox
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
@ -39,7 +40,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
@ -54,8 +57,10 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
import java.io.Reader
import java.net.URI
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import kotlin.system.exitProcess
@ -332,26 +337,38 @@ class MainActivity : BaseActivity() {
}else{
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
val `is` = contentResolver.openInputStream(uri!!)
val textBuilder = StringBuilder()
val reader: Reader = BufferedReader(
InputStreamReader(
`is`, Charset.forName(
StandardCharsets.UTF_8.name()
if (preferences.getString("preferred_download_type", "video") == "command"){
val f = File(FileUtil.formatPath(uri?.path ?: ""))
if (!f.exists()){
Toast.makeText(context, "Couldn't read file", Toast.LENGTH_LONG).show()
return
}
val item = downloadViewModel.createEmptyResultItem(f.absolutePath)
val bottomSheet = DownloadBottomSheetDialog(item, DownloadViewModel.Type.command, null, true)
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
}else{
val `is` = contentResolver.openInputStream(uri!!)
val textBuilder = StringBuilder()
val reader: Reader = BufferedReader(
InputStreamReader(
`is`, Charset.forName(
StandardCharsets.UTF_8.name()
)
)
)
)
var c: Int
while (reader.read().also { c = it } != -1) {
textBuilder.append(c.toChar())
var c: Int
while (reader.read().also { c = it } != -1) {
textBuilder.append(c.toChar())
}
val bundle = Bundle()
bundle.putString("url", textBuilder.toString())
navController.popBackStack(R.id.homeFragment, true)
navController.navigate(
R.id.homeFragment,
bundle
)
}
val bundle = Bundle()
bundle.putString("url", textBuilder.toString())
navController.popBackStack(R.id.homeFragment, true)
navController.navigate(
R.id.homeFragment,
bundle
)
} catch (e: Exception) {
e.printStackTrace()
}

@ -104,6 +104,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
DownloadViewModel.Type.audio -> type.setIconResource(R.drawable.ic_music)
DownloadViewModel.Type.video -> type.setIconResource(R.drawable.ic_video)
DownloadViewModel.Type.command -> type.setIconResource(R.drawable.ic_terminal)
else -> {}
}
val formatDetailsChip = card.findViewById<Chip>(R.id.format_note)

@ -66,6 +66,7 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
DownloadViewModel.Type.audio -> downloadTypeIcon.setIconResource(R.drawable.ic_music)
DownloadViewModel.Type.video -> downloadTypeIcon.setIconResource(R.drawable.ic_video)
DownloadViewModel.Type.command -> downloadTypeIcon.setIconResource(R.drawable.ic_terminal)
else -> {}
}
val formatNote = card.findViewById<TextView>(R.id.format_note)

@ -9,7 +9,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
data class DownloadItem(
@PrimaryKey(autoGenerate = true)
var id: Long,
val url: String,
var url: String,
var title: String,
var author: String,
var thumb: String,

@ -1,13 +1,20 @@
package com.deniscerri.ytdlnis.database.models
import com.google.gson.annotations.SerializedName
data class GithubRelease(
@SerializedName(value = "tag_name")
var tag_name: String,
@SerializedName(value = "body")
var body: String,
@SerializedName(value = "assets")
var assets: List<GithubReleaseAsset>
)
data class GithubReleaseAsset(
@SerializedName(value = "name")
var name: String,
@SerializedName(value = "browser_download_url")
var browser_download_url: String
)

@ -85,9 +85,15 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val dao: DownloadDao
enum class Type {
audio, video, command
auto, audio, video, command
}
private val urlsForAudioType = listOf(
"music",
"audio",
"soundcloud"
)
init {
dbManager = DBManager.getInstance(application)
dao = dbManager.downloadDao
@ -194,6 +200,13 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return repository.getItemByID(id)
}
private fun getSuitableDownloadType(url: String): Type{
if (urlsForAudioType.any { url.contains(it) }){
return Type.audio
}
return Type.video
}
fun createDownloadItemFromResult(resultItem: ResultItem, givenType: Type) : DownloadItem {
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
val saveSubs = sharedPreferences.getBoolean("write_subtitles", false)
@ -202,6 +215,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
var type = givenType
if (type == Type.auto){
type = getSuitableDownloadType(resultItem.url)
}
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
val customFileNameTemplate = when(type) {
@ -320,6 +336,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!!
Type.command -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!!
else -> ""
}
list.forEach {
@ -329,6 +346,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Type.audio -> sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())
Type.video -> sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())
Type.command -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
else -> ""
}
if (it.downloadPath == currentDownloadPath) it.downloadPath = updatedDownloadPath
@ -359,6 +377,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Type.audio -> FileUtil.getDefaultAudioPath()
Type.video -> FileUtil.getDefaultVideoPath()
Type.command -> FileUtil.getDefaultCommandPath()
else -> ""
}
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
@ -404,7 +423,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Type.video -> {
return cloneFormat(
try {
val theFormats = formats.filter { !it.format_note.contains("audio", true) }.sortedBy { it.format_id }.ifEmpty { defaultVideoFormats }
val theFormats = formats.filter { !it.format_note.contains("audio", true) }.sortedBy { it.format_id }.ifEmpty { defaultVideoFormats }.sortedByDescending { it.filesize }
when (videoQualityPreference) {
"worst" -> {
theFormats.first()
@ -416,7 +435,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.substring(0, videoQualityPreference.length - 1)) }
requirements.add { it: Format -> it.vcodec.startsWith(videoCodec!!, true) }
theFormats.reversed().maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
theFormats.maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
}
}
}catch (e: Exception){
@ -468,8 +487,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
val list : MutableList<DownloadItem> = mutableListOf()
val preferredType = sharedPreferences.getString("preferred_download_type", "video")
var preferredType = sharedPreferences.getString("preferred_download_type", "video")
items.forEach {
if (preferredType == Type.auto.toString()) preferredType = getSuitableDownloadType(it!!.url).toString()
list.add(createDownloadItemFromResult(it!!, Type.valueOf(preferredType!!)))
}
return list

@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database.viewmodel
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import android.util.Patterns
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@ -111,14 +112,14 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
}
private fun getQueryType(inputQuery: String) : String {
var type = "Search"
val p = Pattern.compile("(^(https?)://(www.)?(music.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
val m = p.matcher(inputQuery)
if (m.find()) {
type = "YT_Video"
if (inputQuery.contains("playlist?list=") && !inputQuery.contains("music.youtu")) {
if (inputQuery.contains("playlist?list=")) {
type = "YT_Playlist"
}
} else if (inputQuery.contains("http")) {
} else if (Patterns.WEB_URL.matcher(inputQuery).matches()) {
type = "Default"
}
return type

@ -159,7 +159,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
homeAdapter!!.submitList(it)
resultsList = it
if(resultViewModel.repository.itemCount.value > 1 || resultViewModel.repository.itemCount.value == -1){
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && it[0].playlistTitle != getString(R.string.trendingPlaylist) && !loadingItems){
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && !loadingItems){
downloadAllFabCoordinator!!.visibility = VISIBLE
}else{
downloadAllFabCoordinator!!.visibility = GONE
@ -187,7 +187,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
recyclerView?.setPadding(0,0,0,100)
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty() && resultsList!![0]!!.playlistTitle != getString(R.string.trendingPlaylist)){
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
downloadAllFabCoordinator!!.visibility = VISIBLE
}else{
downloadAllFabCoordinator!!.visibility = GONE

@ -11,6 +11,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
@ -64,7 +65,6 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null)
dialog.setContentView(view)
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
@ -80,7 +80,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
overScrollMode = View.OVER_SCROLL_NEVER
}
val fragments = mutableListOf(
val fragments = mutableListOf<Fragment>(
DownloadAudioFragment(resultItem, downloadItem),
DownloadVideoFragment(resultItem, downloadItem)
)
@ -161,6 +161,9 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
tabLayout.selectTab(tabLayout.getTabAt(position))
runCatching {
updateTitleAuthorWhenSwitching()
}
}
})
@ -168,22 +171,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
val ok = view.findViewById<Button>(R.id.bottom_sheet_ok)
ok!!.setOnClickListener {
val item : DownloadItem = when(tabLayout.selectedTabPosition){
0 -> {
val f = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f.downloadItem
}
1 -> {
val f = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
f.downloadItem
}
else -> {
val f = fragmentManager.findFragmentByTag("f2") as DownloadCommandFragment
f.downloadItem
}
}
Log.e("aa", item.toString())
val item = getDownloadItem()
onDownloadItemUpdateListener.onDownloadItemUpdate(resultItem.id, item)
dismiss()
}
@ -200,6 +188,47 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){
0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.downloadItem
}
1 -> {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.downloadItem
}
else -> {
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
f.downloadItem
}
}
}
private fun updateTitleAuthorWhenSwitching(){
val prevDownloadItem = getDownloadItem(
if (viewPager2.currentItem == 1) 0 else 1
)
resultItem.title = prevDownloadItem.title
resultItem.author = prevDownloadItem.author
downloadItem.title = prevDownloadItem.title
downloadItem.author = prevDownloadItem.author
when(viewPager2.currentItem){
0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
1 -> {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
else -> {}
}
}
interface OnDownloadItemUpdateListener {
fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem)
@ -216,16 +245,6 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
cleanUp()
}
// private fun returnPreviousState(){
// CoroutineScope(Dispatchers.IO).launch {
// val result = resultViewModel.getItemByURL(currentItem.url)
// result.title = currentItem.title
// result.author = currentItem.author
// Log.e("TAG, ", currentItem.toString())
// resultViewModel.update(result)
// downloadViewModel.updateDownload(currentItem)
// }
// }
private fun cleanUp(){
kotlin.runCatching {

@ -37,20 +37,19 @@ import kotlinx.coroutines.withContext
import java.io.File
class DownloadAudioFragment(private var resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
class DownloadAudioFragment(private var resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment(), TitleAuthorSync {
private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var resultViewModel : ResultViewModel
private lateinit var title : TextInputLayout
private lateinit var author : TextInputLayout
private lateinit var saveDir : TextInputLayout
private lateinit var freeSpace : TextView
private lateinit var infoUtil: InfoUtil
lateinit var downloadItem : DownloadItem
lateinit var title : TextInputLayout
lateinit var author : TextInputLayout
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -238,6 +237,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
}
}
override fun updateTitleAuthor(t: String, a: String){
downloadItem.title = t
downloadItem.author = a
title.editText?.setText(t)
author.editText?.setText(a)
}
private var audioPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->

@ -8,12 +8,14 @@ import android.content.res.Configuration
import android.os.Bundle
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.util.Patterns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
@ -23,13 +25,13 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -38,7 +40,6 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import java.text.SimpleDateFormat
import java.util.*
@ -56,7 +57,6 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
private lateinit var downloadAudioFragment: DownloadAudioFragment
private lateinit var downloadVideoFragment: DownloadVideoFragment
private lateinit var downloadCommandFragment: DownloadCommandFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
@ -91,11 +91,12 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem)
downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem)
val fragments = mutableListOf(downloadAudioFragment, downloadVideoFragment)
val fragments = mutableListOf<Fragment>(downloadAudioFragment, downloadVideoFragment)
var commandTemplateNr = 0
lifecycleScope.launch{
withContext(Dispatchers.IO){
commandTemplateNr = commandTemplateDao.getTotalNumber()
if (!Patterns.WEB_URL.matcher(resultItem.url).matches()) commandTemplateNr++
if(commandTemplateNr > 0){
downloadCommandFragment = DownloadCommandFragment(resultItem, downloadItem)
fragments.add(downloadCommandFragment)
@ -113,6 +114,16 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
}
//check if the item is coming from a text file
val isCommandOnly = (type == Type.command && !Patterns.WEB_URL.matcher(resultItem.url).matches())
if (isCommandOnly){
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(0)?.isClickable = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(0)?.alpha = 0.3f
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.isClickable = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
}
val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter(
@ -173,6 +184,9 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
tabLayout.selectTab(tabLayout.getTabAt(position))
runCatching {
updateTitleAuthorWhenSwitching()
}
}
})
@ -186,7 +200,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
UiUtil.showDatePicker(fragmentManager) {
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem();
val item: DownloadItem = getDownloadItem()
item.downloadStartTime = it.timeInMillis
runBlocking {
downloadViewModel.queueDownloads(listOf(item))
@ -199,7 +213,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
download!!.setOnClickListener {
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem();
val item: DownloadItem = getDownloadItem()
runBlocking {
downloadViewModel.queueDownloads(listOf(item))
}
@ -223,45 +237,50 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
}
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = resultItem.url
link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), resultItem.url, null)
}
link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
true
}
val updateItem = view.findViewById<Button>(R.id.update_item)
if (quickDownload) {
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
updateItem.setOnClickListener {
if (activity is ShareActivity) {
dismiss()
val intent = Intent(context, ShareActivity::class.java)
intent.action = Intent.ACTION_SEND
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, resultItem.url)
intent.putExtra("quick_download", false)
startActivity(intent)
}else{
dismiss()
val bundle = Bundle()
bundle.putString("url", resultItem.url)
findNavController().popBackStack(R.id.homeFragment, true)
findNavController().navigate(
R.id.homeFragment,
bundle
)
if (Patterns.WEB_URL.matcher(resultItem.url).matches()){
link.text = resultItem.url
link.setOnClickListener{
UiUtil.openLinkIntent(requireContext(), resultItem.url, null)
}
link.setOnLongClickListener{
UiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
true
}
if (quickDownload) {
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
updateItem.setOnClickListener {
if (activity is ShareActivity) {
dismiss()
val intent = Intent(context, ShareActivity::class.java)
intent.action = Intent.ACTION_SEND
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_TEXT, resultItem.url)
intent.putExtra("quick_download", false)
startActivity(intent)
}else{
dismiss()
val bundle = Bundle()
bundle.putString("url", resultItem.url)
findNavController().popBackStack(R.id.homeFragment, true)
findNavController().navigate(
R.id.homeFragment,
bundle
)
}
}
}else{
(updateItem.parent as LinearLayout).visibility = View.GONE
}
}else{
(updateItem.parent as LinearLayout).visibility = View.GONE
link.visibility = View.GONE
updateItem.visibility = View.GONE
}
}
private fun getDownloadItem() : DownloadItem{
return when(tabLayout.selectedTabPosition){
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){
0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.downloadItem
@ -277,6 +296,29 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
}
}
private fun updateTitleAuthorWhenSwitching(){
val prevDownloadItem = getDownloadItem(
if (viewPager2.currentItem == 1) 0 else 1
)
resultItem.title = prevDownloadItem.title
resultItem.author = prevDownloadItem.author
downloadItem?.title = prevDownloadItem.title
downloadItem?.author = prevDownloadItem.author
when(viewPager2.currentItem){
0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
1 -> {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
else -> {}
}
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
cleanUp()

@ -8,6 +8,7 @@ import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.util.Patterns
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
@ -32,6 +33,7 @@ import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
@ -77,6 +79,11 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
}
}
if (!Patterns.WEB_URL.matcher(downloadItem.url).matches()){
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", false))
downloadItem.url = ""
}
try {
val templates : MutableList<CommandTemplate> = withContext(Dispatchers.IO){
commandTemplateViewModel.getAll().toMutableList()
@ -90,6 +97,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
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?) {
view.findViewById<MaterialCardView>(R.id.command_card).alpha = 0.3f
if (p0!!.isNotEmpty()){
chosenCommandView.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_delete_all)
}else{
@ -119,26 +127,34 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
chosenCommandView.editText!!.enableScrollText()
val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card)
populateCommandCard(commandTemplateCard, templates.first())
commandTemplateCard.setOnClickListener {
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){
chosenCommandView.editText!!.setText(it.content)
populateCommandCard(commandTemplateCard, it)
downloadItem.format = Format(
it.title,
"",
"",
"",
"",
0,
it.content
)
runCatching {
populateCommandCard(commandTemplateCard, templates.first())
commandTemplateCard.setOnClickListener {
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){
chosenCommandView.editText!!.setText(it.content)
populateCommandCard(commandTemplateCard, it)
downloadItem.format = Format(
it.title,
"",
"",
"",
"",
0,
it.content
)
}
}
}
}
if (downloadItem.url.isEmpty()){
view.findViewById<MaterialCardView>(R.id.command_card).alpha = 0.3f
}
}.onFailure {
commandTemplateCard.visibility = View.GONE
view.findViewById<Chip>(R.id.editSelected).isEnabled = false
view.findViewById<TextView>(R.id.command_txt).visibility = View.GONE
}
saveDir = view.findViewById(R.id.outputPath)
@ -166,7 +182,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
UiUtil.configureCommand(
view,
1,
if (downloadItem.url.isEmpty()) 0 else 1,
shortcutCount,
newTemplateClicked = {
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
@ -181,6 +197,9 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
0,
it.content
)
commandTemplateCard.visibility = View.VISIBLE
view.findViewById<TextView>(R.id.command_txt).visibility = View.VISIBLE
view.findViewById<Chip>(R.id.editSelected).isEnabled = true
populateCommandCard(commandTemplateCard, it)
}
},

@ -79,6 +79,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var bottomAppBar: BottomAppBar
private lateinit var filesize : TextView
private lateinit var sharedPreferences: SharedPreferences
@ -126,6 +127,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
filesize = view.findViewById<TextView>(R.id.filesize)
scheduleBtn.setOnClickListener{
@ -184,6 +186,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
DownloadViewModel.Type.command -> {
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
}
else -> {}
}
val formatListener = object : OnFormatClickListener {
@ -204,6 +208,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
}
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
}
@ -272,6 +277,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.audio).toMutableList()
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[3].icon?.alpha = 255
@ -282,6 +288,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.video).toMutableList()
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[3].icon?.alpha = 255
@ -295,6 +302,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
}
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[3].icon?.alpha = 30
@ -477,6 +485,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
}
DownloadViewModel.Type.command -> {
}
else -> {}
}
}
}
@ -486,6 +496,15 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
}
private fun updateFileSize(items: List<Long>){
if (items.all { it > 0L }){
filesize.visibility = View.VISIBLE
filesize.text = "${getString(R.string.file_size)}: ~ ${FileUtil.convertFileSize(items.sum())}"
}else{
filesize.visibility = View.GONE
}
}
private var pathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->

@ -38,15 +38,15 @@ import kotlinx.coroutines.withContext
import java.io.File
class DownloadVideoFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
class DownloadVideoFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment(), TitleAuthorSync {
private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var title : TextInputLayout
private lateinit var author : TextInputLayout
lateinit var title : TextInputLayout
lateinit var author : TextInputLayout
private lateinit var saveDir : TextInputLayout
private lateinit var freeSpace : TextView
private lateinit var infoUtil: InfoUtil
@ -266,6 +266,13 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
}
}
override fun updateTitleAuthor(t: String, a: String){
downloadItem.title = t
downloadItem.author = a
title.editText?.setText(t)
author.editText?.setText(a)
}
private var videoPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->

@ -0,0 +1,5 @@
package com.deniscerri.ytdlnis.ui.downloadcard
interface TitleAuthorSync {
fun updateTitleAuthor(t: String, a: String)
}

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.FileObserver
@ -297,7 +298,17 @@ class TerminalActivity : BaseActivity() {
Log.e(TAG, "$action $type")
if (action == Intent.ACTION_SEND && type != null) {
Log.e(TAG, action)
val txt = "yt-dlp " + intent.getStringExtra(Intent.EXTRA_TEXT)
val text = if (intent.getStringExtra(Intent.EXTRA_TEXT) == null){
val uri = if (Build.VERSION.SDK_INT >= 33){
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
}else{
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
"-a \"${FileUtil.formatPath(uri?.path ?: "")}\""
}else{
intent.getStringExtra(Intent.EXTRA_TEXT)
}
val txt = "yt-dlp $text"
input!!.setText(txt)
}
}

@ -1,24 +1,24 @@
package com.deniscerri.ytdlnis.util
import android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.copyFileTo
import com.anggrayudi.storage.file.copyFolderTo
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo
import com.anggrayudi.storage.file.moveTo
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -56,6 +56,8 @@ object FileUtil {
var dataValue = path
if (dataValue.startsWith("/storage/")) return dataValue
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "")
dataValue = dataValue.replace("^/document/".toRegex(), "")
dataValue = dataValue.replace("^primary:".toRegex(), "primary/")
dataValue = dataValue.replace("%3A".toRegex(), "/")
try {
dataValue = URLDecoder.decode(dataValue, StandardCharsets.UTF_8.name())

@ -118,7 +118,7 @@ class InfoUtil(private val context: Context) {
for (i in 0 until dataArray.length()){
val obj = dataArray.getJSONObject(i)
val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))
itm?.playlistTitle = "YTDLnis"
itm?.playlistTitle = res.getString("name") + "[${i+1}]"
items.add(itm)
}
if (nextpage == "null") nextpage = ""
@ -382,7 +382,6 @@ class InfoUtil(private val context: Context) {
if (v == null || v.thumb.isEmpty()) {
continue
}
v.playlistTitle = context.getString(R.string.trendingPlaylist)
items.add(v)
}
return items
@ -398,7 +397,6 @@ class InfoUtil(private val context: Context) {
element.put("uploader", element.getString("uploaderName"))
val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url"))
if (v == null || v.thumb.isEmpty()) continue
v.playlistTitle = context.getString(R.string.trendingPlaylist)
items.add(v)
}
} catch (e: Exception) {
@ -627,6 +625,11 @@ class InfoUtil(private val context: Context) {
var playlistTitle: String? = ""
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
if(playlistTitle.equals(query)) playlistTitle = ""
if (playlistTitle?.isNotBlank() == true){
playlistTitle += "[${jsonObject.getString("playlist_index")}]"
}
val formatsInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null
val formats : ArrayList<Format> = parseYTDLFormats(formatsInJSON)
@ -967,6 +970,19 @@ class InfoUtil(private val context: Context) {
downDir.mkdirs()
}
if (downloadItem.playlistTitle.isNotBlank()){
request.addOption("--parse-metadata","${downloadItem.playlistTitle.split("[")[0]}:%(playlist)s")
runCatching {
request.addOption("--parse-metadata",
downloadItem.playlistTitle
.substring(
downloadItem.playlistTitle.indexOf("[") + 1,
downloadItem.playlistTitle.indexOf("]"),
) + ":%(playlist_index)s"
)
}
}
val aria2 = sharedPreferences.getBoolean("aria2", false)
if (aria2) {
@ -1017,15 +1033,13 @@ class InfoUtil(private val context: Context) {
}
}
if(downloadItem.title.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata","title",".*.",downloadItem.title.take(200)))
request.addOption("--parse-metadata", downloadItem.title.take(200) + ":%(title)s")
}
if (downloadItem.author.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", downloadItem.author.take(25))
request.addOption("--parse-metadata", downloadItem.author.take(25) + ":%(artist)s")
}
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
request.addCommands(listOf("--replace-in-metadata","artist"," - Topic$",""))
if (downloadItem.downloadSections.isNotBlank()){
downloadItem.downloadSections.split(";").forEach {
@ -1319,6 +1333,8 @@ class InfoUtil(private val context: Context) {
)
}
else -> {}
}
return request
@ -1333,7 +1349,15 @@ class InfoUtil(private val context: Context) {
}
}
return java.lang.String.join(" ", arr).replace("\"\"", "\" \"")
var final = java.lang.String.join(" ", arr).replace("\"\"", "\" \"")
val ppas = "--config(-locations)? \"(.*?)\"".toRegex().findAll(final)
ppas.forEach {
val path = "\"(.*?)\"".toRegex().find(it.value)?.value?.replace("\"", "")
final = final.replace(it.value, "")
final += " ${File(path ?: "").readText()}"
}
return final
}
private val pipedURL = sharedPreferences.getString("piped_instance", defaultPipedURL)?.ifEmpty { defaultPipedURL }?.removeSuffix("/")

@ -8,6 +8,7 @@ import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.net.Uri
@ -22,7 +23,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.webkit.MimeTypeMap
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
@ -40,6 +40,7 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -76,6 +77,7 @@ import java.util.Calendar
import java.util.Locale
import java.util.regex.Pattern
object UiUtil {
@SuppressLint("SetTextI18n")
fun populateFormatCard(context: Context, formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<Format>?){
@ -118,6 +120,7 @@ object UiUtil {
fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){
card.findViewById<TextView>(R.id.title).text = item.title
card.findViewById<TextView>(R.id.content).text = item.content
card.alpha = 1f
card.tag = item.id
}
@ -812,7 +815,7 @@ object UiUtil {
val entries = context.resources.getStringArray(R.array.sponsorblock_settings_entries)
val checkedItems : ArrayList<Boolean> = arrayListOf()
values.forEach {
if (items[0].videoPreferences.sponsorBlockFilters.contains(it) && items.size == 1) {
if (items.all{ i -> i.videoPreferences.sponsorBlockFilters.contains(it)}) {
checkedItems.add(true)
}else{
checkedItems.add(false)
@ -1034,7 +1037,7 @@ object UiUtil {
val entries = context.resources.getStringArray(R.array.sponsorblock_settings_entries)
val checkedItems : ArrayList<Boolean> = arrayListOf()
values.forEach {
if (items[0].audioPreferences.sponsorBlockFilters.contains(it) && items.size == 1) {
if (items.all{ i -> i.audioPreferences.sponsorBlockFilters.contains(it)}) {
checkedItems.add(true)
}else{
checkedItems.add(false)

@ -49,17 +49,15 @@ class UpdateUtil(var context: Context) {
}
val useBeta = sharedPreferences.getBoolean("update_beta", false)
val v: GithubRelease? = runCatching {
res.firstOrNull {
if (useBeta) it.tag_name.contains("beta", true)
else !it.tag_name.contains("beta", true)
}
}.getOrNull()
var v: GithubRelease?
if (useBeta){
v = res.firstOrNull { it.tag_name.contains("beta", true) && res.indexOf(it) == 0 }
if (v == null) v = res.first()
}else{
v = res.first { !it.tag_name.contains("beta", true) }
}
if (
(useBeta && v == null) ||
BuildConfig.VERSION_NAME == v!!.tag_name.removePrefix("v").removeSuffix("-beta")
){
if (BuildConfig.VERSION_NAME == v.tag_name.removePrefix("v")){
result(context.getString(R.string.you_are_in_latest_version))
return
}

@ -35,7 +35,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Calendar
import kotlin.random.Random
@ -231,8 +230,6 @@ class DownloadWorker(
if (logDownloads){
logRepo.update(it.out, logItem.id)
}
if (items.size <= 1) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
}
}.onFailure {

@ -86,6 +86,20 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/filesize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingTop="10dp"
android:visibility="gone"
android:text="@string/file_size"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview"
android:layout_width="match_parent"

@ -29,6 +29,7 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/command_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="10dp"

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@+id/textview"
android:textStyle="bold"
android:fontFamily="monospace"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</TextView>

@ -16,7 +16,6 @@
<string name="audio_format">অডিও ফরম্যাট</string>
<string name="title">শিরোনাম</string>
<string name="adjust_audio">অডিও সামঞ্জস্য করুন</string>
<string name="workmanager_updated">এই পছন্দটি কার্যকর করার জন্য আপনাকে অ্যাপটি পুনরায় চালু করতে হবে এবং কোনও সক্রিয় ডাউনলোড নেই!</string>
<string name="dont_ask_again">আবার জিজ্ঞাসা করবেন না</string>
<string name="music_directory">মিউজিক ফোল্ডার</string>
<string name="warning">সতর্কতা</string>

@ -10,7 +10,6 @@
<string name="in_queue">Di Antrean</string>
<string name="split_by_chapters">Kapisah dening Undhang-undhang</string>
<string name="adjust_audio">Nyetel Audio</string>
<string name="workmanager_updated">Sampeyan kudu miwiti maneh app lan ora duwe download aktif supaya pilihan iki ditrapake!</string>
<string name="dont_ask_again">Aja takon maneh</string>
<string name="music_directory">Folder musik</string>
<string name="warning">Pènget</string>

@ -144,12 +144,14 @@
<string-array name="download_types">
<item>@string/auto</item>
<item>@string/audio</item>
<item>@string/video</item>
<item>@string/command</item>
</string-array>
<string-array name="download_types_values">
<item>auto</item>
<item>audio</item>
<item>video</item>
<item>command</item>
@ -800,4 +802,5 @@
<item>h264</item>
</string-array>
<string name="auto">Auto</string>
</resources>

@ -45,7 +45,7 @@
app:title="@string/socks5_proxy" />
<ListPreference
android:defaultValue="video"
android:defaultValue="auto"
android:entries="@array/download_types"
android:entryValues="@array/download_types_values"
android:icon="@drawable/ic_download_type"

Loading…
Cancel
Save