other fixes

This commit is contained in:
zaednasr 2024-07-13 15:37:36 +02:00
parent 8e63219d73
commit fdb143310e
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
30 changed files with 549 additions and 522 deletions

View file

@ -6,6 +6,7 @@ import android.widget.Toast
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.ThemeUtil
import com.yausername.aria2c.Aria2c
import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL
@ -44,6 +45,7 @@ class App : Application() {
e.printStackTrace()
}
}
ThemeUtil.init(this)
}
@Throws(YoutubeDLException::class)
private fun initLibraries() {

View file

@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import java.util.regex.Pattern
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
private val tag: String = "ResultRepository"
val YTDLNIS_SEARCH = "YTDLNIS_SEARCH"
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
var itemCount = MutableStateFlow(-1)

View file

@ -1,6 +1,5 @@
package com.deniscerri.ytdl.database.viewmodel
import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.content.res.Configuration
@ -9,12 +8,9 @@ import android.os.Handler
import android.os.Looper
import android.os.Parcelable
import android.util.DisplayMetrics
import android.util.Log
import android.widget.Toast
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
@ -39,27 +35,24 @@ import com.deniscerri.ytdl.database.models.VideoPreferences
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.HistoryRepository
import com.deniscerri.ytdl.database.repository.ResultRepository
import com.deniscerri.ytdl.ui.downloadcard.FormatTuple
import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple
import com.deniscerri.ytdl.util.Extensions.toListString
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.FormatSorter
import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.work.AlarmScheduler
import com.deniscerri.ytdl.work.UpdatePlaylistFormatsWorker
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
import com.google.gson.Gson
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import okhttp3.internal.format
import java.io.File
import java.util.Locale
@ -539,18 +532,17 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>, deleteExisting : Boolean = false) = viewModelScope.launch(Dispatchers.IO){
val job = viewModelScope.launch(Dispatchers.IO) {
repository.deleteProcessing()
processingItems.emit(true)
try {
itemIDs.forEachIndexed { idx, it ->
itemIDs.forEach {
val item = repository.getItemByID(it)
if (processingItemsJob?.isCancelled == true) throw CancellationException()
item.id = 0
if (!deleteExisting) item.id = 0
item.status = DownloadRepository.Status.Processing.toString()
repository.insert(item)
repository.update(item)
}
processingItems.emit(false)
} catch (e: Exception) {
@ -1059,7 +1051,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
dao.updateProcessingtoSavedStatus()
val id = System.currentTimeMillis().toInt()
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()
val workRequest = OneTimeWorkRequestBuilder<UpdateMultipleDownloadsFormatsWorker>()
.setInputData(
Data.Builder()
.putLongArray("ids", ids.toLongArray())

View file

@ -308,7 +308,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
arguments?.remove("showDownloadsWithUpdatedFormats")
CoroutineScope(Dispatchers.IO).launch {
val ids = arguments?.getLongArray("downloadIds") ?: return@launch
downloadViewModel.turnDownloadItemsToProcessingDownloads(ids.toList())
downloadViewModel.turnDownloadItemsToProcessingDownloads(ids.toList(), deleteExisting = true)
withContext(Dispatchers.Main){
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
}

View file

@ -202,7 +202,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<ResultItem> = object : DiffUtil.ItemCallback<ResultItem>() {
override fun areItemsTheSame(oldItem: ResultItem, newItem: ResultItem): Boolean {
return oldItem.id === newItem.id
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: ResultItem, newItem: ResultItem): Boolean {

View file

@ -108,8 +108,11 @@ class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: Downlo
fragmentManager,
lifecycle,
null,
currentDownloadItem
currentDownloadItem,
isIncognito = incognito
)
viewPager2.adapter = fragmentAdapter
viewPager2.isSaveFromParentEnabled = false
@ -182,7 +185,7 @@ class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: Downlo
putString("last_used_download_type",
listOf(Type.audio, Type.video, Type.command)[position].toString())
}
updateWhenSwitching()
fragmentAdapter.updateWhenSwitching(viewPager2.currentItem)
}
}
})
@ -216,6 +219,7 @@ class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: Downlo
}
incognito = !incognito
fragmentAdapter.isIncognito = incognito
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: $onOff", Toast.LENGTH_SHORT).show()
}
@ -224,54 +228,7 @@ class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: Downlo
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
val item = when(selectedTabPosition){
0 -> {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.downloadItem.apply { id = currentDownloadItem.id }
}
1 -> {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.downloadItem.apply { id = currentDownloadItem.id }
}
else -> {
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
f.downloadItem.apply { id = currentDownloadItem.id }
}
}
item.incognito = incognito
return item
}
private fun updateWhenSwitching(){
val prevDownloadItem = getDownloadItem(
if (viewPager2.currentItem == 1) 0 else 1
)
fragmentAdapter.setTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
when(viewPager2.currentItem){
0 -> {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
}
}
1 -> {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
f.updateSelectedAudioFormat(getDownloadItem(0).format)
}
}
2 -> {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
}
else -> {}
}
return fragmentAdapter.getDownloadItem(selectedTabPosition)
}

View file

@ -180,38 +180,32 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
lifecycleScope.launch {
try {
val data : MutableList<String?> = withContext(Dispatchers.IO){
val data = withContext(Dispatchers.IO) {
if (urls.isNullOrEmpty()) {
infoUtil.getStreamingUrlAndChapters(item.url)
}else {
urls.split("\n").toMutableList()
}else{
Pair(urls.split("\n"), chapters)
}
}
if (data.isEmpty()) throw Exception("No Data found!")
if (data.first.isEmpty()) throw Exception("No Data found!")
if (chapters!!.isEmpty() && urls!!.isBlank()){
try{
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
chapters = Gson().fromJson(data.first().toString(), listType)
data.removeFirst()
}catch (ignored: Exception) {
data.removeFirst()
}
chapters = data.second
}
if (data.isEmpty()) throw Exception("No Streaming URL found!")
if (data.size == 2){
val urls = data.first
if (urls.size == 2){
val audioSource : MediaSource =
DefaultMediaSourceFactory(requireContext())
.createMediaSource(fromUri(Uri.parse(data[0])))
.createMediaSource(fromUri(Uri.parse(urls[0])))
val videoSource: MediaSource =
DefaultMediaSourceFactory(requireContext())
.createMediaSource(fromUri(Uri.parse(data[1])))
.createMediaSource(fromUri(Uri.parse(urls[1])))
player.setMediaSource(MergingMediaSource(videoSource, audioSource))
}else{
player.addMediaItem(fromUri(Uri.parse(data[0])))
player.addMediaItem(fromUri(Uri.parse(urls[0])))
}
player.addMediaItem(fromUri(Uri.parse(data[0])))
progress.visibility = View.GONE
populateSuggestedChapters()
@ -221,6 +215,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
}catch (e: Exception){
progress.visibility = View.GONE
frame.visibility = View.GONE
videoView.visibility = View.GONE
e.printStackTrace()
}
}

View file

@ -239,10 +239,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
this.formats.removeAll(formats.toSet())
this.formats.addAll(allFormats.filter { !genericAudioFormats.contains(it) })
resultViewModel.update(this)
kotlin.runCatching {
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f1.updateUI(this)
}
}
currentDownloadItem?.apply {

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
@ -18,9 +17,7 @@ import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.content.res.ResourcesCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
@ -190,18 +187,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
(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(result.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
(updateItem.parent as LinearLayout).visibility = View.GONE
}
//remove outdated player url of 1hr so it can refetch it in the cut player
if (result.creationTime > System.currentTimeMillis() - 3600000) result.urls = ""
@ -210,7 +195,8 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
fragmentManager,
lifecycle,
result,
currentDownloadItem
currentDownloadItem,
isIncognito = incognito
)
viewPager2.adapter = fragmentAdapter
@ -239,6 +225,18 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}, 200)
}
}
//check if the item is coming from a text file
val isCommandOnly = (type == Type.command && !Patterns.WEB_URL.matcher(result.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
(updateItem.parent as LinearLayout).visibility = View.GONE
}
}
sharedPreferences.edit(commit = true) {
@ -294,7 +292,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
putString("last_used_download_type",
listOf(Type.audio, Type.video, Type.command)[position].toString())
}
updateWhenSwitching()
fragmentAdapter.updateWhenSwitching(viewPager2.currentItem)
}
}
})
@ -441,6 +439,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
incognito = !incognito
fragmentAdapter.isIncognito = incognito
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: $onOff", Toast.LENGTH_SHORT).show()
}
@ -498,42 +497,46 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
if (it){
delay(500)
runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = true
isClickable = true
setOnClickListener {
lifecycleScope.launch {
resultViewModel.cancelUpdateFormatsItemData()
(fragmentAdapter.fragments[0] as DownloadAudioFragment).apply {
view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = true
isClickable = true
setOnClickListener {
lifecycleScope.launch {
resultViewModel.cancelUpdateFormatsItemData()
}
}
}
}
}
runCatching {
val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = true
isClickable = true
setOnClickListener {
lifecycleScope.launch {
resultViewModel.cancelUpdateFormatsItemData()
(fragmentAdapter.fragments[1] as DownloadVideoFragment).apply {
view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = true
isClickable = true
setOnClickListener {
lifecycleScope.launch {
resultViewModel.cancelUpdateFormatsItemData()
}
}
}
}
}
}else{
runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = false
isClickable = false
(fragmentAdapter.fragments[0] as DownloadAudioFragment).apply {
view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = false
isClickable = false
}
}
}
runCatching {
val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = false
isClickable = false
(fragmentAdapter.fragments[1] as DownloadVideoFragment).apply {
view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.apply {
isVisible = false
isClickable = false
}
}
}
}
@ -546,43 +549,34 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
if (result == null) return@collectLatest
kotlin.runCatching {
lifecycleScope.launch(Dispatchers.Main) {
if (result.isNotEmpty()){
if (result.size == 1 && result[0] != null){
fragmentAdapter.setResultItem(result[0]!!)
runCatching {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateUI(result[0])
}
if (result.size == 1 && result[0] != null) {
val res = result[0]!!
fragmentAdapter.setResultItem(res)
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f1.updateUI(result[0])
}
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
shimmerLoading.visibility = View.GONE
shimmerLoadingSubtitle.visibility = View.GONE
shimmerLoading.stopShimmer()
shimmerLoadingSubtitle.stopShimmer()
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
shimmerLoading.visibility = View.GONE
shimmerLoadingSubtitle.visibility = View.GONE
shimmerLoading.stopShimmer()
shimmerLoadingSubtitle.stopShimmer()
val usingGenericFormatsOrEmpty = res.formats.isEmpty() || res.formats.any { it.format_note.contains("ytdlnisgeneric") }
arguments?.putParcelable("result", res)
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){
initUpdateFormats(res)
}
val usingGenericFormatsOrEmpty = result[0]!!.formats.isEmpty() || result[0]!!.formats.any { it.format_note.contains("ytdlnisgeneric") }
fragmentAdapter.setResultItem(result[0]!!)
arguments?.putParcelable("result", result[0]!!)
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){
initUpdateFormats(result[0]!!)
}
}else if (result.size > 1) {
//open multi download card instead
if (activity is ShareActivity){
findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_selectPlaylistItemsDialog, bundleOf(
Pair("resultIDs", result.map { it!!.id }.toLongArray()),
))
}else{
//open multi download card instead
if (activity is ShareActivity){
findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_selectPlaylistItemsDialog, bundleOf(
Pair("resultIDs", result.map { it!!.id }.toLongArray()),
))
}else{
dismiss()
}
dismiss()
}
}
resultViewModel.updateResultData.emit(null)
}
@ -606,19 +600,17 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
lifecycleScope.launch {
withContext(Dispatchers.Main){
runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
val f1 = fragmentAdapter.fragments[0] as DownloadAudioFragment
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
resultItem.formats = formats
fragmentAdapter.setResultItem(resultItem)
f1.updateUI(resultItem)
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
}
runCatching {
val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
val f1 = fragmentAdapter.fragments[1] as DownloadVideoFragment
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
resultItem.formats = formats
fragmentAdapter.setResultItem(resultItem)
f1.updateUI(resultItem)
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
}
}
@ -634,28 +626,12 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem {
val item = 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
}
}
item.incognito = incognito
return item
return fragmentAdapter.getDownloadItem(selectedTabPosition)
}
private fun getAlsoAudioDownloadItem(finished: (it: DownloadItem) -> Unit) {
try {
val ff = (fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment)
val ff = fragmentAdapter.fragments[0] as DownloadAudioFragment
ff.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
finished(ff.downloadItem)
}catch (e: Exception){
@ -680,37 +656,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
}
private fun updateWhenSwitching(){
val prevDownloadItem = getDownloadItem(
if (viewPager2.currentItem == 1) 0 else 1
)
fragmentAdapter.setTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
when(viewPager2.currentItem){
0 -> {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
}
}
1 -> {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
f.updateSelectedAudioFormat(getDownloadItem(0).format)
}
}
2 -> {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
}
else -> {}
}
}
private fun initUpdateData() {
kotlin.runCatching {
if (result.url.isBlank()) {

View file

@ -12,33 +12,86 @@ class DownloadFragmentAdapter (
lifecycle : Lifecycle,
private var result: ResultItem?,
private var downloadItem: DownloadItem?,
private var nonSpecific: Boolean = false
nonSpecific: Boolean = false,
var isIncognito: Boolean = false
) : FragmentStateAdapter(fragmentManager, lifecycle) {
fun setResultItem(res: ResultItem){
result = res
fragments.forEachIndexed { idx, it ->
when(idx) {
0 -> kotlin.runCatching { (it as DownloadAudioFragment).updateUI(res) }
1 -> kotlin.runCatching { (it as DownloadVideoFragment).updateUI(res) }
2 -> kotlin.runCatching { (it as DownloadCommandFragment).updateUI(res) }
}
}
}
fun setDownloadItem(dd: DownloadItem){
downloadItem = dd
}
fun setTitleAuthor(t: String, a: String){
private fun setTitleAuthor(t: String, a: String){
result?.title = t
result?.author = a
downloadItem?.title = t
downloadItem?.author = a
}
val fragments = listOf<Fragment>(
DownloadAudioFragment(result, downloadItem,"", nonSpecific),
DownloadVideoFragment(result, downloadItem,"", nonSpecific),
DownloadCommandFragment(result, downloadItem)
)
override fun getItemCount(): Int {
return 3
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return when(position){
0 -> DownloadAudioFragment(result, downloadItem,"", nonSpecific)
1 -> DownloadVideoFragment(result, downloadItem,"", nonSpecific)
else -> DownloadCommandFragment(result, downloadItem)
return fragments[position]
}
fun updateWhenSwitching(currentViewPagerItem : Int) {
val prevDownloadItem = getDownloadItem(
if (currentViewPagerItem == 1) 0 else 1
)
setTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
when(currentViewPagerItem){
0 -> {
kotlin.runCatching {
(fragments[0] as DownloadAudioFragment).apply {
updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
}
}
}
1 -> {
kotlin.runCatching {
(fragments[1] as DownloadVideoFragment).apply {
updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
updateSelectedAudioFormat(getDownloadItem(0).format)
}
}
}
2 -> {
kotlin.runCatching {
(fragments[2] as DownloadCommandFragment).apply {
updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
}
}
}
else -> {}
}
}
fun getDownloadItem(position: Int) : DownloadItem {
return when(position) {
0 -> (fragments[0] as DownloadAudioFragment).downloadItem
1 -> (fragments[1] as DownloadVideoFragment).downloadItem
else -> (fragments[2] as DownloadCommandFragment).downloadItem
}.apply {
incognito = isIncognito
}
}

View file

@ -257,10 +257,6 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
this.formats.removeAll(formats)
this.formats.addAll(allFormats.filter { !genericVideoFormats.contains(it) })
resultViewModel.update(this)
kotlin.runCatching {
val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f1.updateUI(this)
}
}
currentDownloadItem?.apply {

View file

@ -220,6 +220,7 @@ class FormatSelectionBottomSheetDialog(
if (items.size == 1) {
kotlin.runCatching {
val res = infoUtil.getFormats(items.first()!!.url, currentFormatSource)
if (!isActive) return@launch
res.filter { it.format_note != "storyboard" }
chosenFormats = if (items.first()?.type == Type.audio) {
res.filter { it.format_note.contains("audio", ignoreCase = true) }
@ -230,6 +231,8 @@ class FormatSelectionBottomSheetDialog(
formats.clear()
formats.addAll(res)
withContext(Dispatchers.Main){
listener.onFormatsUpdated(res)
}

View file

@ -283,29 +283,28 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
lifecycleScope.launch {
try {
val data : MutableList<String?> = withContext(Dispatchers.IO){
val data = withContext(Dispatchers.IO) {
if (item.urls.isEmpty()) {
infoUtil.getStreamingUrlAndChapters(item.url)
}else {
item.urls.split("\n").toMutableList()
}else{
Pair(item.urls.split("\n"), null)
}
}
if (data.size > 1) data.removeFirst()
if (data.first.isEmpty()) throw Exception("No Data found!")
if (data.isEmpty()) throw Exception("No Streaming URL found!")
if (data.size == 2){
val urls = data.first
if (urls.size == 2){
val audioSource : MediaSource =
DefaultMediaSourceFactory(requireContext())
.createMediaSource(MediaItem.fromUri(Uri.parse(data[0])))
.createMediaSource(MediaItem.fromUri(Uri.parse(urls[0])))
val videoSource: MediaSource =
DefaultMediaSourceFactory(requireContext())
.createMediaSource(MediaItem.fromUri(Uri.parse(data[1])))
.createMediaSource(MediaItem.fromUri(Uri.parse(urls[1])))
player.setMediaSource(MergingMediaSource(videoSource, audioSource))
}else{
player.addMediaItem(MediaItem.fromUri(Uri.parse(data[0])))
player.addMediaItem(MediaItem.fromUri(Uri.parse(urls[0])))
}
player.addMediaItem(MediaItem.fromUri(Uri.parse(data[0])))
player.prepare()
player.play()

View file

@ -126,6 +126,8 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
pause.setOnClickListener {
lifecycleScope.launch {
pause.isEnabled = false
delay(1000)
workManager.cancelAllWorkByTag("download")
val activeDownloadsList = withContext(Dispatchers.IO){
downloadViewModel.getActiveDownloads()
@ -140,16 +142,15 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
resume.isEnabled = false
resume.isVisible = true
activeDownloads.notifyDataSetChanged()
withContext(Dispatchers.Main){
delay(1000)
resume.isEnabled = true
}
resume.isEnabled = true
}
}
resume.setOnClickListener {
lifecycleScope.launch {
resume.isEnabled = false
delay(1000)
preferences.edit().putBoolean("paused_downloads", false).apply()
resume.isVisible = false
pause.isEnabled = false
@ -159,12 +160,9 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
downloadViewModel.startDownloadWorker(listOf())
withContext(Dispatchers.Main){
activeDownloads.notifyDataSetChanged()
pause.isEnabled = true
}
}
withContext(Dispatchers.Main){
delay(1000)
pause.isEnabled = true
}
}
}
@ -204,9 +202,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
requireActivity().runOnUiThread {
try {
progressBar?.setProgressCompat(event.progress, true)
if (event.progress > 95) {
pause.isEnabled = false
}
outputText?.text = event.output
}catch (ignored: Exception) {}
}

View file

@ -48,6 +48,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdl.ui.adapter.HistoryAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.appbar.AppBarLayout
@ -134,7 +135,6 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
requireActivity()
)
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
recyclerView.adapter = historyAdapter
recyclerView.enableFastScroll()
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -165,6 +165,10 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
historyAdapter!!.submitList(it)
historyList = it
scrollToTop()
if (recyclerView.adapter == null){
recyclerView.adapter = historyAdapter
}
}
historyViewModel.sortOrder.observe(viewLifecycleOwner){
@ -552,7 +556,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
true
}
R.id.share -> {
UiUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten())
FileUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten())
clearCheckedItems()
actionMode?.finish()
true
@ -677,7 +681,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onButtonClick(itemID: Long, isPresent: Boolean) {
if (isPresent){
UiUtil.shareFileIntent(requireContext(), historyList!!.first { it!!.id == itemID }!!.downloadPath)
FileUtil.shareFileIntent(requireContext(), historyList!!.first { it!!.id == itemID }!!.downloadPath)
}
}
companion object {

View file

@ -161,7 +161,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
}else{
val snack = Snackbar.make(recyclerView, getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG)
snack.setAction(R.string.share) {
UiUtil.shareFileIntent(requireContext(), listOf(f.absolutePath))
FileUtil.shareFileIntent(requireContext(), listOf(f.absolutePath))
}
snack.show()
}

View file

@ -89,7 +89,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
findPreference<Preference>("label_visibility")?.apply {
isVisible = !resources.getBoolean(R.bool.uses_side_nav)
setOnPreferenceChangeListener { preference, newValue ->
restartApp()
ThemeUtil.recreateMain()
true
}
}
@ -162,7 +162,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
.setPositiveButton(R.string.ok) { _, _ ->
NavbarUtil.setNavBarItems(adapter.items, requireContext())
NavbarUtil.setStartFragment(adapter.selectedHomeTabId)
restartApp()
ThemeUtil.recreateMain()
}
.setNegativeButton(R.string.cancel, null)
.show()
@ -185,34 +185,18 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
theme!!.summary = getString(R.string.light)
}
}
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
requireActivity().finishAffinity()
ThemeUtil.updateThemes()
true
}
accent!!.summary = accent!!.entry
accent!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
requireActivity().finishAffinity()
ThemeUtil.updateThemes()
true
}
highContrast!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, _: Any ->
ThemeUtil.updateTheme(requireActivity() as AppCompatActivity)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this.startActivity(intent)
requireActivity().finishAffinity()
ThemeUtil.updateThemes()
true
}

View file

@ -49,6 +49,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdl.database.viewmodel.ObserveSourcesViewModel
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.UpdateUtil
import com.google.android.material.chip.Chip
@ -220,7 +221,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
saveFile.writeText(GsonBuilder().setPrettyPrinting().create().toJson(json))
val s = Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG)
s.setAction(R.string.Open_File){
UiUtil.openFileIntent(requireActivity(), saveFile.absolutePath)
FileUtil.openFileIntent(requireActivity(), saveFile.absolutePath)
}
s.show()
}

View file

@ -1,31 +1,50 @@
package com.deniscerri.ytdl.util
import android.R.attr.mimeType
import android.app.Activity
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.util.DisplayMetrics
import android.util.Log
import android.view.ViewGroup
import android.view.Window
import android.webkit.MimeTypeMap
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import androidx.work.impl.utils.PreferenceUtils
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.copyFolderTo
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo
import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
import com.deniscerri.ytdl.util.Extensions.toStringDuration
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.yausername.youtubedl_android.YoutubeDLRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly
import okhttp3.internal.lowercase
import java.io.File
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
@ -327,4 +346,58 @@ object FileUtil {
return "${DecimalFormat("#,##0.#", symbols).format(s / 1024.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}"
}
fun openFileIntent(context: Context, downloadPath: String) {
val uri = FileProvider.getUriForFile(context, context.packageName + ".fileprovider", File(downloadPath))
println(uri)
if (uri == null){
Toast.makeText(context, "Error opening file!", Toast.LENGTH_SHORT).show()
}else{
val ext = downloadPath.substring(downloadPath.lastIndexOf(".") + 1).lowercase()
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
val intent = Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, mime)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
context.startActivity(intent)
}
}
fun shareFileIntent(context: Context, paths: List<String>){
val uris : ArrayList<Uri> = arrayListOf()
paths.runCatching {
this.forEach {path ->
val uri = DocumentFile.fromSingleUri(context, Uri.parse(path)).run{
if (this?.exists() == true){
this.uri
}else if (File(path).exists()){
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
File(path))
}else null
}
if (uri != null) uris.add(uri)
}
}
if (uris.isEmpty()){
Toast.makeText(context, "Error sharing files!", Toast.LENGTH_SHORT).show()
}else{
Intent().apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
type = if (uris.size == 1) uris[0].let { context.contentResolver.getType(it) } ?: "media/*" else "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}.run{
context.startActivity(Intent.createChooser(this, context.getString(R.string.share)))
}
}
}
}

View file

@ -929,13 +929,22 @@ class InfoUtil(private val context: Context) {
fun getStreamingUrlAndChapters(url: String) : MutableList<String?> {
fun getStreamingUrlAndChapters(url: String) : Pair<List<String>, List<ChapterItem>?> {
try {
val p = Pattern.compile("(^(https?)://(www.)?(music.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
val m = p.matcher(url)
if (url.isYoutubeURL()) {
val id = getIDFromYoutubeURL(url)
val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) {
throw Exception()
}else{
val item = createVideoFromPipedJSON(res, url)
if (item!!.urls.isBlank()) throw Exception()
val urls = item.urls.split(",")
val chapters = item.chapters
return Pair(urls, chapters)
}
if (m.find()){
return getStreamingUrlAndChaptersFromPIPED(url)
}else{
throw Exception()
}
@ -943,7 +952,7 @@ class InfoUtil(private val context: Context) {
try {
val request = YoutubeDLRequest(url)
request.addOption("--get-url")
request.addOption("--print", "%(chapters)s")
request.addOption("--print", "%(.{urls,chapters})s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
@ -964,8 +973,6 @@ class InfoUtil(private val context: Context) {
}
}
val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()){
request.addOption("--proxy", proxy)
@ -975,36 +982,41 @@ class InfoUtil(private val context: Context) {
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
youtubeDLResponse.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(youtubeDLResponse.out)
val json = JSONObject(youtubeDLResponse.out)
val urls = if (json.has("urls")) {
json.getString("urls").split("\n")
}else{
listOf()
}
return results.filter { it!!.isNotEmpty() }.toMutableList()
val chapters = if (json.has("chapters")) {
val arr = json.getJSONArray("chapters")
val list = mutableListOf<ChapterItem>()
for (i in 0 until arr.length()) {
list.add(
Gson().fromJson(arr.getJSONObject(i).toString(), ChapterItem::class.java)
)
}
list
}else{
listOf()
}
return Pair(urls, chapters)
} catch (e: Exception) {
return mutableListOf()
return Pair(listOf(), listOf())
}
}
}
private fun getStreamingUrlAndChaptersFromPIPED(url: String) : MutableList<String?> {
val id = getIDFromYoutubeURL(url)
val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) {
throw Exception()
}else{
val item = createVideoFromPipedJSON(res, url)
if (item!!.urls.isBlank()) throw Exception()
val list = mutableListOf<String?>(Gson().toJson(item.chapters))
list.addAll(item.urls.split(","))
return list
}
}
@SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank()){
val useItemURL = sharedPreferences.getBoolean("use_itemurl_instead_playlisturl", false)
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || useItemURL){
YoutubeDLRequest(downloadItem.url)
}else{
YoutubeDLRequest(downloadItem.playlistURL!!).apply {
@ -1017,6 +1029,10 @@ class InfoUtil(private val context: Context) {
}
}
if (downloadItem.playlistIndex != null && useItemURL) {
request.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
}
val type = downloadItem.type
val downDir : File

View file

@ -11,6 +11,7 @@ import androidx.core.view.forEach
import androidx.core.view.get
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.util.NavbarUtil.applyNavBarStyle
import com.google.android.material.navigation.NavigationBarView
@ -95,7 +96,7 @@ object NavbarUtil {
return navBarItems
}
fun NavigationBarView.applyNavBarStyle(): Int {
fun NavigationBarView.setLabelVisibility() {
val labelVisibilityMode = when (
settings.getString("label_visibility", "always")
) {
@ -105,6 +106,10 @@ object NavbarUtil {
else -> NavigationBarView.LABEL_VISIBILITY_AUTO
}
this.labelVisibilityMode = labelVisibilityMode
}
fun NavigationBarView.applyNavBarStyle(): Int {
setLabelVisibility()
val navBarItems = getNavBarItems(this.context)

View file

@ -546,7 +546,7 @@ class NotificationUtil(var context: Context) {
)
)
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)

View file

@ -1,8 +1,11 @@
package com.deniscerri.ytdl.util
import android.app.Activity
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.Spanned
import android.util.TypedValue
import androidx.annotation.DrawableRes
@ -11,12 +14,50 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.text.HtmlCompat
import androidx.core.text.parseAsHtml
import androidx.preference.PreferenceManager
import androidx.test.runner.lifecycle.ActivityLifecycleCallback
import androidx.test.runner.lifecycle.Stage
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.google.android.material.color.DynamicColors
object ThemeUtil {
private val activities = mutableListOf<Activity>()
fun init(app: Application) {
app.registerActivityLifecycleCallbacks(object: Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(p0: Activity, p1: Bundle?) {
activities.add(p0)
}
override fun onActivityStarted(p0: Activity) {
}
override fun onActivityResumed(p0: Activity) {
}
override fun onActivityPaused(p0: Activity) {
}
override fun onActivityStopped(p0: Activity) {
}
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
}
override fun onActivityDestroyed(p0: Activity) {
activities.remove(p0)
}
})
}
sealed class AppIcon(
@DrawableRes val iconResource: Int,
val activityAlias: String
@ -32,7 +73,18 @@ object ThemeUtil {
AppIcon.Dark
)
fun updateTheme(activity: AppCompatActivity) {
fun recreateMain() {
activities.firstOrNull { it.javaClass == MainActivity::class.java }?.recreate()
}
fun updateThemes() {
activities.forEach {
updateTheme(it)
it.recreate()
}
}
fun updateTheme(activity: Activity) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
//update accent

View file

@ -389,112 +389,6 @@ object UiUtil {
}
fun openFileIntent(context: Context, downloadPath: String) {
val uri = downloadPath.runCatching {
DocumentFile.fromSingleUri(context, Uri.parse(downloadPath)).run{
if (this?.exists() == true){
this.uri
}else if (File(this@runCatching).exists()){
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
File(this@runCatching))
}else null
}
}.getOrNull()
if (uri == null){
Toast.makeText(context, "Error opening file!", Toast.LENGTH_SHORT).show()
}else{
Intent().apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_VIEW
data = uri
}.run{
context.startActivity(this)
}
}
}
private fun openMultipleFilesIntent(context: Activity, path: List<String>){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.filepathlist)
val list = bottomSheet.findViewById<LinearLayout>(R.id.filepath_list)
list?.apply {
path.forEach {path ->
val file = File(path)
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
item.apply {
findViewById<TextView>(R.id.file_name).text = file.nameWithoutExtension
findViewById<TextView>(R.id.duration).apply {
val duration = file.getMediaDuration(context)
isVisible = duration > 0
text = duration.toStringDuration(Locale.US)
}
findViewById<TextView>(R.id.filesize).text = FileUtil.convertFileSize(file.length())
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
if (!file.exists()){
isEnabled = false
alpha = 0.7f
}
isEnabled = file.exists()
setOnClickListener {
openFileIntent(context, path)
bottomSheet.dismiss()
}
}
list.addView(item)
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun shareFileIntent(context: Context, paths: List<String>){
val uris : ArrayList<Uri> = arrayListOf()
paths.runCatching {
this.forEach {path ->
val uri = DocumentFile.fromSingleUri(context, Uri.parse(path)).run{
if (this?.exists() == true){
this.uri
}else if (File(path).exists()){
FileProvider.getUriForFile(context, context.packageName + ".fileprovider",
File(path))
}else null
}
if (uri != null) uris.add(uri)
}
}
if (uris.isEmpty()){
Toast.makeText(context, "Error sharing files!", Toast.LENGTH_SHORT).show()
}else{
Intent().apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = Intent.ACTION_SEND_MULTIPLE
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
type = if (uris.size == 1) uris[0].let { context.contentResolver.getType(it) } ?: "media/*" else "*/*"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}.run{
context.startActivity(Intent.createChooser(this, context.getString(R.string.share)))
}
}
}
fun showDatePickerOnly(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
val date = Calendar.getInstance()
@ -816,7 +710,7 @@ object UiUtil {
}
setOnClickListener {
shareFileIntent(context, item.downloadPath)
FileUtil.shareFileIntent(context, item.downloadPath)
}
}
}
@ -903,7 +797,7 @@ object UiUtil {
openFile!!.tag = item.id
openFile.setOnClickListener{
if (item.downloadPath.size == 1) {
openFileIntent(context, item.downloadPath.first())
FileUtil.openFileIntent(context, item.downloadPath.first())
}else{
openMultipleFilesIntent(context, item.downloadPath)
}
@ -2046,4 +1940,52 @@ object UiUtil {
}
}
}
fun openMultipleFilesIntent(context: Activity, path: List<String>){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.filepathlist)
val list = bottomSheet.findViewById<LinearLayout>(R.id.filepath_list)
list?.apply {
path.forEach {path ->
val file = File(path)
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
item.apply {
findViewById<TextView>(R.id.file_name).text = file.nameWithoutExtension
findViewById<TextView>(R.id.duration).apply {
val duration = file.getMediaDuration(context)
isVisible = duration > 0
text = duration.toStringDuration(Locale.US)
}
findViewById<TextView>(R.id.filesize).text = FileUtil.convertFileSize(file.length())
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
if (!file.exists()){
isEnabled = false
alpha = 0.7f
}
isEnabled = file.exists()
setOnClickListener {
FileUtil.openFileIntent(context, path)
bottomSheet.dismiss()
}
}
list.addView(item)
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
}

View file

@ -137,7 +137,7 @@ class UpdateUtil(var context: Context) {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
context?.unregisterReceiver(this)
UiUtil.openFileIntent(context!!,
FileUtil.openFileIntent(context!!,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath +
File.separator + releaseVersion.name)
}

View file

@ -13,7 +13,7 @@ import com.deniscerri.ytdl.util.NotificationUtil
import kotlinx.coroutines.runBlocking
class UpdatePlaylistFormatsWorker(
class UpdateMultipleDownloadsFormatsWorker(
private val context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
@ -38,7 +38,7 @@ class UpdatePlaylistFormatsWorker(
ids.forEach {
if (!isStopped){
val d = dao.getDownloadById(it)
val r = resDao.getResultByURL(d.url)!!
val r = resDao.getResultByURL(d.url)
if (d.allFormats.isNotEmpty()){
count++
@ -50,18 +50,18 @@ class UpdatePlaylistFormatsWorker(
d.allFormats.addAll(infoUtil.getFormats(d.url))
d.format = vm.getFormat(d.allFormats,d.type)
r.formats.clear()
r.formats.addAll(d.allFormats)
r?.formats?.clear()
r?.formats?.addAll(d.allFormats)
runBlocking {
resDao.update(r)
r?.apply { resDao.update(this) }
dao.update(d)
}
}
count++
notificationUtil.updateFormatUpdateNotification(workID, UpdatePlaylistFormatsWorker::class.java.name, count, ids.size)
notificationUtil.updateFormatUpdateNotification(workID, UpdateMultipleDownloadsFormatsWorker::class.java.name, count, ids.size)
}else{
throw Exception()
}
@ -79,8 +79,4 @@ class UpdatePlaylistFormatsWorker(
}
}
override fun onStopped() {
Log.e("asd", "Asd")
super.onStopped()
}
}

View file

@ -37,103 +37,110 @@
app:useDefaultControls="true"
app:use_controller="false" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:indeterminate="true"
android:translationZ="10dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/pause"
android:visibility="gone"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_play_arrow_white"
android:layout_width="wrap_content"
android:indeterminate="true"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/rewind"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_rewind_white"
android:layout_marginHorizontal="20dp"
android:layout_width="wrap_content"
android:contentDescription="@string/restart"
android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mute"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:contentDescription="@string/remove_audio"
android:layout_height="wrap_content"
app:icon="@drawable/ic_music"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/forward"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_fast_forward_white"
android:layout_marginHorizontal="20dp"
android:layout_width="wrap_content"
android:contentDescription="@string/end"
android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/durationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#80000000"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:indeterminate="true"
android:translationZ="10dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/pause"
android:visibility="gone"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_play_arrow_white"
android:layout_width="wrap_content"
android:indeterminate="true"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/rewind"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_rewind_white"
android:layout_marginHorizontal="20dp"
android:layout_width="wrap_content"
android:contentDescription="@string/restart"
android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mute"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:contentDescription="@string/remove_audio"
android:layout_height="wrap_content"
app:icon="@drawable/ic_music"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<com.google.android.material.button.MaterialButton
android:id="@+id/forward"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_fast_forward_white"
android:layout_marginHorizontal="20dp"
android:layout_width="wrap_content"
android:contentDescription="@string/end"
android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/durationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#80000000"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cut_section"
@ -167,7 +174,6 @@
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@id/suggested_cuts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rangeSlider"
app:layout_constraintVertical_bias="0.0">
@ -177,6 +183,7 @@
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="70dp"
android:hint="@string/start">
<com.google.android.material.textfield.TextInputEditText
@ -203,6 +210,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/end"
android:minWidth="70dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon">

View file

@ -410,4 +410,6 @@
<string name="restore_info">Press \'Restore\' to merge the saved data with your current data.\nPress \'Reset\' to erase and only use the saved data in the file.</string>
<string name="use_alarm_manager">Use AlarmManager instead of WorkManager for Scheduling</string>
<string name="use_alarm_manager_summary">Enable this if WorkManager is restricted by your device vendor or scheduled jobs are not too precise</string>
<string name="use_item_url_not_playlist">Use Item URL instead of Playlist URL</string>
<string name="use_item_url_not_playlist_summary">Useful for Libretube offline playlists that are not recognised by yt-dlp. Playlist metadata wont be embedded with this enabled</string>
</resources>

View file

@ -99,6 +99,17 @@
app:defaultValue=""
android:summary="@string/piped_instance_summary"
app:title="@string/piped_instance" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
app:icon="@drawable/baseline_format_quote_24"
app:key="use_itemurl_instead_playlisturl"
app:summary="@string/use_item_url_not_playlist_summary"
app:title="@string/use_item_url_not_playlist" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/misc">

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
<root-path name="external_files" path="/storage/" />
<root-path name="external_files" path="." />
<cache-path
name="cache"
path="." />