From fdb143310e2a45085e6f13cad73984b22175528f Mon Sep 17 00:00:00 2001 From: zaednasr <75589932+zaednasr@users.noreply.github.com> Date: Sat, 13 Jul 2024 15:37:36 +0200 Subject: [PATCH] other fixes --- app/src/main/java/com/deniscerri/ytdl/App.kt | 2 + .../database/repository/ResultRepository.kt | 1 - .../database/viewmodel/DownloadViewModel.kt | 20 +- .../com/deniscerri/ytdl/ui/HomeFragment.kt | 2 +- .../deniscerri/ytdl/ui/adapter/HomeAdapter.kt | 3 +- .../ConfigureDownloadBottomSheetDialog.kt | 57 +---- .../downloadcard/CutVideoBottomSheetDialog.kt | 29 ++- .../ui/downloadcard/DownloadAudioFragment.kt | 4 - .../downloadcard/DownloadBottomSheetDialog.kt | 191 ++++++----------- .../downloadcard/DownloadFragmentAdapter.kt | 75 ++++++- .../ui/downloadcard/DownloadVideoFragment.kt | 4 - .../FormatSelectionBottomSheetDialog.kt | 3 + .../downloadcard/ResultCardDetailsDialog.kt | 19 +- .../ui/downloads/ActiveDownloadsFragment.kt | 17 +- .../ytdl/ui/downloads/HistoryFragment.kt | 10 +- .../ytdl/ui/more/CookiesFragment.kt | 2 +- .../more/settings/GeneralSettingsFragment.kt | 26 +-- .../ui/more/settings/MainSettingsFragment.kt | 3 +- .../java/com/deniscerri/ytdl/util/FileUtil.kt | 75 ++++++- .../java/com/deniscerri/ytdl/util/InfoUtil.kt | 74 ++++--- .../com/deniscerri/ytdl/util/NavbarUtil.kt | 7 +- .../deniscerri/ytdl/util/NotificationUtil.kt | 2 +- .../com/deniscerri/ytdl/util/ThemeUtil.kt | 54 ++++- .../java/com/deniscerri/ytdl/util/UiUtil.kt | 158 +++++--------- .../com/deniscerri/ytdl/util/UpdateUtil.kt | 2 +- ...> UpdateMultipleDownloadsFormatsWorker.kt} | 16 +- app/src/main/res/layout/cut_video_sheet.xml | 200 +++++++++--------- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/general_preferences.xml | 11 + app/src/main/res/xml/provider_paths.xml | 2 +- 30 files changed, 549 insertions(+), 522 deletions(-) rename app/src/main/java/com/deniscerri/ytdl/work/{UpdatePlaylistFormatsWorker.kt => UpdateMultipleDownloadsFormatsWorker.kt} (85%) diff --git a/app/src/main/java/com/deniscerri/ytdl/App.kt b/app/src/main/java/com/deniscerri/ytdl/App.kt index 9384ad87..3f7b77d1 100644 --- a/app/src/main/java/com/deniscerri/ytdl/App.kt +++ b/app/src/main/java/com/deniscerri/ytdl/App.kt @@ -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() { diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt index c12cdd33..a6146156 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/ResultRepository.kt @@ -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> = resultDao.getResults() var itemCount = MutableStateFlow(-1) diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index eb724677..93b70210 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -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) = viewModelScope.launch(Dispatchers.IO){ + fun turnDownloadItemsToProcessingDownloads(itemIDs: List, 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() + val workRequest = OneTimeWorkRequestBuilder() .setInputData( Data.Builder() .putLongArray("ids", ids.toLongArray()) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt index 02e43a65..da27ba7d 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt @@ -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) } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HomeAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HomeAdapter.kt index 0c95021f..9c99687b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HomeAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/HomeAdapter.kt @@ -202,7 +202,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) companion object { private val DIFF_CALLBACK: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { 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 { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt index 75185027..44996f50 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt @@ -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) } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt index e35aaa52..aa25770b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt @@ -180,38 +180,32 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private lifecycleScope.launch { try { - val data : MutableList = 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>() {}.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() } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt index 91bdfe8f..bb9f41f7 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadAudioFragment.kt @@ -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 { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt index bec8a4cf..9876e6ed 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt @@ -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(R.id.format_loading_progress)?.apply { - isVisible = true - isClickable = true - setOnClickListener { - lifecycleScope.launch { - resultViewModel.cancelUpdateFormatsItemData() + (fragmentAdapter.fragments[0] as DownloadAudioFragment).apply { + view?.findViewById(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(R.id.format_loading_progress)?.apply { - isVisible = true - isClickable = true - setOnClickListener { - lifecycleScope.launch { - resultViewModel.cancelUpdateFormatsItemData() + (fragmentAdapter.fragments[1] as DownloadVideoFragment).apply { + view?.findViewById(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(R.id.format_loading_progress)?.apply { - isVisible = false - isClickable = false + (fragmentAdapter.fragments[0] as DownloadAudioFragment).apply { + view?.findViewById(R.id.format_loading_progress)?.apply { + isVisible = false + isClickable = false + } } } runCatching { - val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment - f1.view?.findViewById(R.id.format_loading_progress)?.apply { - isVisible = false - isClickable = false + (fragmentAdapter.fragments[1] as DownloadVideoFragment).apply { + view?.findViewById(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(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(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()) { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadFragmentAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadFragmentAdapter.kt index 5fd846d9..55f6126b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadFragmentAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadFragmentAdapter.kt @@ -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( + 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 } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt index 4971d61d..b58f5620 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadVideoFragment.kt @@ -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 { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt index a85ca85c..3fea7e27 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt @@ -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) } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt index fc1a4134..4dab5c32 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ResultCardDetailsDialog.kt @@ -283,29 +283,28 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap lifecycleScope.launch { try { - val data : MutableList = 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() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt index 668ba1aa..ecdfdb7d 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/ActiveDownloadsFragment.kt @@ -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) {} } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt index c45176fc..934408af 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt @@ -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 { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt index 1816833f..052f3998 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/CookiesFragment.kt @@ -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() } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt index 5881f28a..0074ea74 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt @@ -89,7 +89,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { findPreference("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 } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/MainSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/MainSettingsFragment.kt index 8139f6e1..d0c7f679 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/MainSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/MainSettingsFragment.kt @@ -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() } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt index bc679e3a..2d56345c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt @@ -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){ + val uris : ArrayList = 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))) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt index 8ccb85f0..044f5684 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt @@ -929,13 +929,22 @@ class InfoUtil(private val context: Context) { - fun getStreamingUrlAndChapters(url: String) : MutableList { + fun getStreamingUrlAndChapters(url: String) : Pair, List?> { 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 = 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() + 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 { - 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(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 diff --git a/app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt index 94306f02..5010f2ac 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt @@ -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) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt index 8906d948..5b21e164 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt @@ -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) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/ThemeUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/ThemeUtil.kt index 1623a777..971891e2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/ThemeUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/ThemeUtil.kt @@ -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() + + 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 diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index 4cd2a4bd..164b3e04 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -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){ - val bottomSheet = BottomSheetDialog(context) - bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) - bottomSheet.setContentView(R.layout.filepathlist) - - val list = bottomSheet.findViewById(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(R.id.file_name).text = file.nameWithoutExtension - - findViewById(R.id.duration).apply { - val duration = file.getMediaDuration(context) - isVisible = duration > 0 - text = duration.toStringDuration(Locale.US) - } - - - findViewById(R.id.filesize).text = FileUtil.convertFileSize(file.length()) - findViewById(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){ - val uris : ArrayList = 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){ + val bottomSheet = BottomSheetDialog(context) + bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) + bottomSheet.setContentView(R.layout.filepathlist) + + val list = bottomSheet.findViewById(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(R.id.file_name).text = file.nameWithoutExtension + + findViewById(R.id.duration).apply { + val duration = file.getMediaDuration(context) + isVisible = duration > 0 + text = duration.toStringDuration(Locale.US) + } + + + findViewById(R.id.filesize).text = FileUtil.convertFileSize(file.length()) + findViewById(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 + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt index 88eb8d22..0698411f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt @@ -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) } diff --git a/app/src/main/java/com/deniscerri/ytdl/work/UpdatePlaylistFormatsWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt similarity index 85% rename from app/src/main/java/com/deniscerri/ytdl/work/UpdatePlaylistFormatsWorker.kt rename to app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt index bf037db1..c6d05a93 100644 --- a/app/src/main/java/com/deniscerri/ytdl/work/UpdatePlaylistFormatsWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdl/work/UpdateMultipleDownloadsFormatsWorker.kt @@ -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() - } } \ No newline at end of file diff --git a/app/src/main/res/layout/cut_video_sheet.xml b/app/src/main/res/layout/cut_video_sheet.xml index 3e9a9c30..b7b25218 100644 --- a/app/src/main/res/layout/cut_video_sheet.xml +++ b/app/src/main/res/layout/cut_video_sheet.xml @@ -37,103 +37,110 @@ app:useDefaultControls="true" app:use_controller="false" /> + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - @@ -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"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17a75447..766f3928 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -410,4 +410,6 @@ Press \'Restore\' to merge the saved data with your current data.\nPress \'Reset\' to erase and only use the saved data in the file. Use AlarmManager instead of WorkManager for Scheduling Enable this if WorkManager is restricted by your device vendor or scheduled jobs are not too precise + Use Item URL instead of Playlist URL + Useful for Libretube offline playlists that are not recognised by yt-dlp. Playlist metadata wont be embedded with this enabled \ No newline at end of file diff --git a/app/src/main/res/xml/general_preferences.xml b/app/src/main/res/xml/general_preferences.xml index 6bec7a3d..64b45fdb 100644 --- a/app/src/main/res/xml/general_preferences.xml +++ b/app/src/main/res/xml/general_preferences.xml @@ -99,6 +99,17 @@ app:defaultValue="" android:summary="@string/piped_instance_summary" app:title="@string/piped_instance" /> + + + + + diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index 20151e82..545210c9 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,7 +1,7 @@ - +