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
This commit is contained in:
deniscerri 2023-09-09 12:15:07 +02:00
parent a1510cdcfa
commit 236f83d161
No known key found for this signature in database
GPG key ID: 95C43D517D830350
29 changed files with 377 additions and 155 deletions

View file

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

View file

@ -18,6 +18,7 @@ import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat 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.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.BaseActivity import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.ui.HomeFragment 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.ui.more.settings.SettingsActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UpdateUtil import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
@ -54,8 +57,10 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.io.Reader import java.io.Reader
import java.net.URI
import java.nio.charset.Charset import java.nio.charset.Charset
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -332,26 +337,38 @@ class MainActivity : BaseActivity() {
}else{ }else{
intent.getParcelableExtra(Intent.EXTRA_STREAM) intent.getParcelableExtra(Intent.EXTRA_STREAM)
} }
val `is` = contentResolver.openInputStream(uri!!)
val textBuilder = StringBuilder() if (preferences.getString("preferred_download_type", "video") == "command"){
val reader: Reader = BufferedReader( val f = File(FileUtil.formatPath(uri?.path ?: ""))
InputStreamReader( if (!f.exists()){
`is`, Charset.forName( Toast.makeText(context, "Couldn't read file", Toast.LENGTH_LONG).show()
StandardCharsets.UTF_8.name() 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
var c: Int while (reader.read().also { c = it } != -1) {
while (reader.read().also { c = it } != -1) { textBuilder.append(c.toChar())
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) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -159,7 +159,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
homeAdapter!!.submitList(it) homeAdapter!!.submitList(it)
resultsList = it resultsList = it
if(resultViewModel.repository.itemCount.value > 1 || resultViewModel.repository.itemCount.value == -1){ 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 downloadAllFabCoordinator!!.visibility = VISIBLE
}else{ }else{
downloadAllFabCoordinator!!.visibility = GONE downloadAllFabCoordinator!!.visibility = GONE
@ -187,7 +187,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
recyclerView?.setPadding(0,0,0,100) recyclerView?.setPadding(0,0,0,100)
shimmerCards!!.stopShimmer() shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE 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 downloadAllFabCoordinator!!.visibility = VISIBLE
}else{ }else{
downloadAllFabCoordinator!!.visibility = GONE downloadAllFabCoordinator!!.visibility = GONE

View file

@ -11,6 +11,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -64,7 +65,6 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
super.setupDialog(dialog, style) super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null) val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null)
dialog.setContentView(view) dialog.setContentView(view)
dialog.setOnShowListener { dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View) behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
@ -80,7 +80,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
overScrollMode = View.OVER_SCROLL_NEVER overScrollMode = View.OVER_SCROLL_NEVER
} }
val fragments = mutableListOf( val fragments = mutableListOf<Fragment>(
DownloadAudioFragment(resultItem, downloadItem), DownloadAudioFragment(resultItem, downloadItem),
DownloadVideoFragment(resultItem, downloadItem) DownloadVideoFragment(resultItem, downloadItem)
) )
@ -161,6 +161,9 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
tabLayout.selectTab(tabLayout.getTabAt(position)) 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) val ok = view.findViewById<Button>(R.id.bottom_sheet_ok)
ok!!.setOnClickListener { ok!!.setOnClickListener {
val item = getDownloadItem()
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())
onDownloadItemUpdateListener.onDownloadItemUpdate(resultItem.id, item) onDownloadItemUpdateListener.onDownloadItemUpdate(resultItem.id, item)
dismiss() 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 { interface OnDownloadItemUpdateListener {
fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem)
@ -216,16 +245,6 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
cleanUp() 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(){ private fun cleanUp(){
kotlin.runCatching { kotlin.runCatching {

View file

@ -37,20 +37,19 @@ import kotlinx.coroutines.withContext
import java.io.File 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 _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var resultViewModel : ResultViewModel private lateinit var resultViewModel : ResultViewModel
private lateinit var title : TextInputLayout
private lateinit var author : TextInputLayout
private lateinit var saveDir : TextInputLayout private lateinit var saveDir : TextInputLayout
private lateinit var freeSpace : TextView private lateinit var freeSpace : TextView
private lateinit var infoUtil: InfoUtil private lateinit var infoUtil: InfoUtil
lateinit var downloadItem : DownloadItem lateinit var downloadItem : DownloadItem
lateinit var title : TextInputLayout
lateinit var author : TextInputLayout
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, 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( private var audioPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->

View file

@ -8,12 +8,14 @@ import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Patterns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController 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.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.DownloadItem 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.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -56,7 +57,6 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
private lateinit var downloadAudioFragment: DownloadAudioFragment private lateinit var downloadAudioFragment: DownloadAudioFragment
private lateinit var downloadVideoFragment: DownloadVideoFragment private lateinit var downloadVideoFragment: DownloadVideoFragment
private lateinit var downloadCommandFragment: DownloadCommandFragment private lateinit var downloadCommandFragment: DownloadCommandFragment
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
@ -91,11 +91,12 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem) downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem)
downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem) downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem)
val fragments = mutableListOf(downloadAudioFragment, downloadVideoFragment) val fragments = mutableListOf<Fragment>(downloadAudioFragment, downloadVideoFragment)
var commandTemplateNr = 0 var commandTemplateNr = 0
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
commandTemplateNr = commandTemplateDao.getTotalNumber() commandTemplateNr = commandTemplateDao.getTotalNumber()
if (!Patterns.WEB_URL.matcher(resultItem.url).matches()) commandTemplateNr++
if(commandTemplateNr > 0){ if(commandTemplateNr > 0){
downloadCommandFragment = DownloadCommandFragment(resultItem, downloadItem) downloadCommandFragment = DownloadCommandFragment(resultItem, downloadItem)
fragments.add(downloadCommandFragment) 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 (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 val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter( fragmentAdapter = DownloadFragmentAdapter(
@ -173,6 +184,9 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
tabLayout.selectTab(tabLayout.getTabAt(position)) tabLayout.selectTab(tabLayout.getTabAt(position))
runCatching {
updateTitleAuthorWhenSwitching()
}
} }
}) })
@ -186,7 +200,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
UiUtil.showDatePicker(fragmentManager) { UiUtil.showDatePicker(fragmentManager) {
scheduleBtn.isEnabled = false scheduleBtn.isEnabled = false
download.isEnabled = false download.isEnabled = false
val item: DownloadItem = getDownloadItem(); val item: DownloadItem = getDownloadItem()
item.downloadStartTime = it.timeInMillis item.downloadStartTime = it.timeInMillis
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) downloadViewModel.queueDownloads(listOf(item))
@ -199,7 +213,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
download!!.setOnClickListener { download!!.setOnClickListener {
scheduleBtn.isEnabled = false scheduleBtn.isEnabled = false
download.isEnabled = false download.isEnabled = false
val item: DownloadItem = getDownloadItem(); val item: DownloadItem = getDownloadItem()
runBlocking { runBlocking {
downloadViewModel.queueDownloads(listOf(item)) 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) 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) val updateItem = view.findViewById<Button>(R.id.update_item)
if (quickDownload) { if (Patterns.WEB_URL.matcher(resultItem.url).matches()){
(updateItem.parent as LinearLayout).visibility = View.VISIBLE link.text = resultItem.url
updateItem.setOnClickListener { link.setOnClickListener{
if (activity is ShareActivity) { UiUtil.openLinkIntent(requireContext(), resultItem.url, null)
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
)
}
} }
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{ }else{
(updateItem.parent as LinearLayout).visibility = View.GONE link.visibility = View.GONE
updateItem.visibility = View.GONE
} }
} }
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
private fun getDownloadItem() : DownloadItem{ return when(selectedTabPosition){
return when(tabLayout.selectedTabPosition){
0 -> { 0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.downloadItem 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) { override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog) super.onCancel(dialog)
cleanUp() cleanUp()

View file

@ -8,6 +8,7 @@ import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log import android.util.Log
import android.util.Patterns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View 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.enableTextHighlight
import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers 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 { try {
val templates : MutableList<CommandTemplate> = withContext(Dispatchers.IO){ val templates : MutableList<CommandTemplate> = withContext(Dispatchers.IO){
commandTemplateViewModel.getAll().toMutableList() 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 beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) { override fun afterTextChanged(p0: Editable?) {
view.findViewById<MaterialCardView>(R.id.command_card).alpha = 0.3f
if (p0!!.isNotEmpty()){ if (p0!!.isNotEmpty()){
chosenCommandView.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_delete_all) chosenCommandView.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_delete_all)
}else{ }else{
@ -119,26 +127,34 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
chosenCommandView.editText!!.enableScrollText() chosenCommandView.editText!!.enableScrollText()
val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card) val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card)
populateCommandCard(commandTemplateCard, templates.first()) runCatching {
populateCommandCard(commandTemplateCard, templates.first())
commandTemplateCard.setOnClickListener { commandTemplateCard.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){
chosenCommandView.editText!!.setText(it.content) chosenCommandView.editText!!.setText(it.content)
populateCommandCard(commandTemplateCard, it) populateCommandCard(commandTemplateCard, it)
downloadItem.format = Format( downloadItem.format = Format(
it.title, it.title,
"", "",
"", "",
"", "",
"", "",
0, 0,
it.content 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) saveDir = view.findViewById(R.id.outputPath)
@ -166,7 +182,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
UiUtil.configureCommand( UiUtil.configureCommand(
view, view,
1, if (downloadItem.url.isEmpty()) 0 else 1,
shortcutCount, shortcutCount,
newTemplateClicked = { newTemplateClicked = {
UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) { UiUtil.showCommandTemplateCreationOrUpdatingSheet(null, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
@ -181,6 +197,9 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
0, 0,
it.content 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) populateCommandCard(commandTemplateCard, it)
} }
}, },

View file

@ -79,6 +79,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var bottomAppBar: BottomAppBar private lateinit var bottomAppBar: BottomAppBar
private lateinit var filesize : TextView
private lateinit var sharedPreferences: SharedPreferences 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 scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
val download = view.findViewById<Button>(R.id.bottomsheet_download_button) val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
filesize = view.findViewById<TextView>(R.id.filesize)
scheduleBtn.setOnClickListener{ scheduleBtn.setOnClickListener{
@ -184,6 +186,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
DownloadViewModel.Type.command -> { DownloadViewModel.Type.command -> {
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24) preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
} }
else -> {}
} }
val formatListener = object : OnFormatClickListener { val formatListener = object : OnFormatClickListener {
@ -204,6 +208,7 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
} }
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() 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() items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.audio).toMutableList()
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24) preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[1].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[3].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() items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.video).toMutableList()
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24) preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[1].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[3].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.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
updateFileSize(items.map { it.format.filesize })
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24) preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[1].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[3].icon?.alpha = 30 bottomAppBar.menu[3].icon?.alpha = 30
@ -477,6 +485,8 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
} }
DownloadViewModel.Type.command -> { 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( private var pathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->

View file

@ -38,15 +38,15 @@ import kotlinx.coroutines.withContext
import java.io.File 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 _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
private lateinit var downloadViewModel : DownloadViewModel private lateinit var downloadViewModel : DownloadViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var title : TextInputLayout lateinit var title : TextInputLayout
private lateinit var author : TextInputLayout lateinit var author : TextInputLayout
private lateinit var saveDir : TextInputLayout private lateinit var saveDir : TextInputLayout
private lateinit var freeSpace : TextView private lateinit var freeSpace : TextView
private lateinit var infoUtil: InfoUtil 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( private var videoPathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { result -> ) { result ->

View file

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

View file

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.FileObserver import android.os.FileObserver
@ -297,7 +298,17 @@ class TerminalActivity : BaseActivity() {
Log.e(TAG, "$action $type") Log.e(TAG, "$action $type")
if (action == Intent.ACTION_SEND && type != null) { if (action == Intent.ACTION_SEND && type != null) {
Log.e(TAG, action) 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) input!!.setText(txt)
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -86,6 +86,20 @@
</androidx.constraintlayout.widget.ConstraintLayout> </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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview" android:id="@+id/downloadMultipleRecyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

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

View file

@ -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>

View file

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

View file

@ -10,7 +10,6 @@
<string name="in_queue">Di Antrean</string> <string name="in_queue">Di Antrean</string>
<string name="split_by_chapters">Kapisah dening Undhang-undhang</string> <string name="split_by_chapters">Kapisah dening Undhang-undhang</string>
<string name="adjust_audio">Nyetel Audio</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="dont_ask_again">Aja takon maneh</string>
<string name="music_directory">Folder musik</string> <string name="music_directory">Folder musik</string>
<string name="warning">Pènget</string> <string name="warning">Pènget</string>

View file

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

View file

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