From 087ba0781a6262b73af9286e08461f307fe517dc Mon Sep 17 00:00:00 2001
From: zaednasr <75589932+zaednasr@users.noreply.github.com>
Date: Sun, 11 Aug 2024 17:28:02 +0200
Subject: [PATCH] 1.7.9.1 prep
---
app/build.gradle | 1 +
app/proguard-rules.pro | 6 +
app/src/main/AndroidManifest.xml | 125 ++++++++++++------
.../java/com/deniscerri/ytdl/MainActivity.kt | 7 +-
.../deniscerri/ytdl/database/dao/LogDao.kt | 7 +-
.../ytdl/database/dao/TerminalDao.kt | 7 +-
.../database/viewmodel/DownloadViewModel.kt | 34 +++--
.../database/viewmodel/ResultViewModel.kt | 61 +++++----
.../com/deniscerri/ytdl/ui/HomeFragment.kt | 37 +++++-
.../adapter/ActiveDownloadMinifiedAdapter.kt | 8 +-
.../downloadcard/DownloadBottomSheetDialog.kt | 34 ++---
.../DownloadMultipleBottomSheetDialog.kt | 3 +
.../FormatSelectionBottomSheetDialog.kt | 22 ++-
.../downloadcard/ResultCardDetailsDialog.kt | 88 +++++++-----
.../more/downloadLogs/DownloadLogFragment.kt | 41 ++++--
.../more/settings/FolderSettingsFragment.kt | 5 +-
.../com/deniscerri/ytdl/util/Extensions.kt | 35 +++--
.../java/com/deniscerri/ytdl/util/FileUtil.kt | 5 +
.../java/com/deniscerri/ytdl/util/InfoUtil.kt | 94 ++++++++-----
.../com/deniscerri/ytdl/util/NavbarUtil.kt | 1 +
.../deniscerri/ytdl/util/NotificationUtil.kt | 3 +-
.../java/com/deniscerri/ytdl/util/UiUtil.kt | 61 +++++----
.../deniscerri/ytdl/work/DownloadWorker.kt | 96 ++++++++------
.../ytdl/work/TerminalDownloadWorker.kt | 2 +-
.../main/res/drawable/baseline_album_24.xml | 5 +
app/src/main/res/drawable/rounded_corner.xml | 2 +-
.../layout/active_download_card_minified.xml | 44 +++---
app/src/main/res/layout/download_card.xml | 2 +-
app/src/main/res/layout/format_item.xml | 2 +-
.../main/res/layout/fragment_download_log.xml | 117 +++++++++-------
.../main/res/layout/queued_download_card.xml | 2 +-
.../main/res/layout/result_card_details.xml | 8 +-
.../res/menu/active_downloads_minified.xml | 24 ++--
app/src/main/res/menu/log_detail_menu.xml | 1 -
app/src/main/res/values/arrays.xml | 5 +
app/src/main/res/values/strings.xml | 3 +
app/src/main/res/values/styles.xml | 5 +
.../main/res/xml/processing_preferences.xml | 8 ++
38 files changed, 642 insertions(+), 369 deletions(-)
create mode 100644 app/src/main/res/drawable/baseline_album_24.xml
diff --git a/app/build.gradle b/app/build.gradle
index 2ee78c91..e0aec3d9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -201,4 +201,5 @@ dependencies {
implementation 'androidx.compose.material3:material3-android:1.2.1'
implementation "io.noties.markwon:core:4.6.2"
implementation("org.greenrobot:eventbus:3.3.1")
+ //implementation("com.github.TeamNewPipe:NewPipeExtractor:0.24.2")
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index cf608782..e4c07011 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -88,5 +88,11 @@
static <1>$Companion Companion;
}
+### Rules for NewPipeExtractor
+#-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
+#-keep class org.mozilla.javascript.** { *; }
+#-keep class org.mozilla.classfile.ClassFileWriter
+#-dontwarn org.mozilla.javascript.tools.**
+
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontobfuscate
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 017d5f45..93060c51 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -80,24 +80,46 @@
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -114,7 +136,7 @@
-
+
@@ -122,162 +144,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
navController.navigate(R.id.historyFragment)
diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/LogDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/LogDao.kt
index 3a99eebe..8abcfb76 100644
--- a/app/src/main/java/com/deniscerri/ytdl/database/dao/LogDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/LogDao.kt
@@ -7,6 +7,7 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import com.deniscerri.ytdl.database.models.LogItem
+import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@@ -43,10 +44,10 @@ interface LogDao {
suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) {
val l = getByID(id) ?: return
var log = l.content ?: ""
- if (resetLog) {
- log = line.lines().joinToString("\n")
+ log = if (resetLog) {
+ line.appendLineToLog()
}else{
- log += "\n${line}"
+ log.appendLineToLog(line)
}
updateLogContent(log, id)
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/TerminalDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/TerminalDao.kt
index a3ee24c7..d10d0da3 100644
--- a/app/src/main/java/com/deniscerri/ytdl/database/dao/TerminalDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/TerminalDao.kt
@@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import com.deniscerri.ytdl.database.models.TerminalItem
+import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@@ -31,10 +32,10 @@ interface TerminalDao {
suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){
val t = getTerminalById(id) ?: return
var log = t.log ?: ""
- if (resetLog) {
- log = line.lines().joinToString("\n")
+ log = if (resetLog) {
+ line.lines().joinToString("\n")
}else{
- log += "\n${line}"
+ log.appendLineToLog(line)
}
updateTerminalLog(log, id)
}
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 d756e7be..56a846ff 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
@@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import android.content.res.Configuration
import android.content.res.Resources
+import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Parcelable
@@ -148,7 +149,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val confTmp = Configuration(application.resources.configuration)
- confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
+ val locale = if (Build.VERSION.SDK_INT < 33) {
+ sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
+ }else{
+ Locale.getDefault().language
+ }.run {
+ split("-")
+ }.run {
+ if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
+ }
+ confTmp.setLocale(locale)
val metrics = DisplayMetrics()
resources = Resources(application.assets, metrics, confTmp)
}
@@ -245,6 +255,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val preferredAudioFormats = getPreferredAudioFormats(resultItem.formats)
+ val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
val videoPreferences = VideoPreferences(
embedSubs,
@@ -252,6 +263,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
ArrayList(sponsorblock),
saveSubs,
saveAutoSubs,
+ subsLanguages,
audioFormatIDs = preferredAudioFormats,
recodeVideo = recodeVideo
)
@@ -354,8 +366,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val format = getFormat(it.allFormats, type)
it.format = format
- var updatedDownloadPath: String = ""
- var container: String = ""
+ var updatedDownloadPath = ""
+ var container = ""
when(type){
Type.audio -> {
@@ -363,11 +375,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
container = sharedPreferences.getString("audio_format", "")!!
}
Type.video -> {
- updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
- container = sharedPreferences.getString("audio_format", "")!!
+ updatedDownloadPath = sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!!
+ container = sharedPreferences.getString("video_format", "")!!
}
Type.command -> {
- updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
+ updatedDownloadPath = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!!
container = ""
}
else -> {
@@ -391,6 +403,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
+ val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
@@ -420,7 +433,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
- val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, recodeVideo = recodeVideo)
+ val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, subsLanguages, recodeVideo = recodeVideo)
var path = defaultPath
historyItem.downloadPath.first().apply {
File(this).parent?.apply {
@@ -930,10 +943,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}else{
val queued = repository.updateAll(queuedItems)
- if (!sharedPreferences.getBoolean("paused_downloads", false)) {
- result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
+ if (sharedPreferences.getBoolean("paused_downloads", true)) {
+ sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
+ resetActiveToQueued()
}
+ result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
+
if(!useScheduler){
CoroutineScope(Dispatchers.IO).launch {
queued.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt
index 3fbc2b07..c0b074a1 100644
--- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/ResultViewModel.kt
@@ -22,11 +22,16 @@ import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDLException
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Semaphore
+import kotlinx.coroutines.sync.withPermit
import java.util.concurrent.CancellationException
@@ -40,18 +45,13 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
private val dao: ResultDao
data class ResultsUiState(
var processing: Boolean,
- var errorMessage: Pair?,
- var actions: MutableList>?
+ var errorMessage: String?,
+ var canContinueOnError: Boolean = false
)
- enum class ResultAction {
- COPY_LOG
- }
-
val uiState: MutableStateFlow = MutableStateFlow(ResultsUiState(
processing = false,
errorMessage = null,
- actions = null
))
@@ -63,7 +63,8 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
var updateFormatsResultData: MutableStateFlow?> = MutableStateFlow(null)
private var updateFormatsResultDataJob: Job? = null
- var parsingQueries: Job? = null
+ private var parsingQueries: Job? = null
+ private var parsingQueriesJobList : MutableList = mutableListOf()
private val sharedPreferences: SharedPreferences
@@ -101,6 +102,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
fun cancelParsingQueries(){
parsingQueries?.cancel(CancellationException())
+ parsingQueriesJobList.forEach { it.cancel(CancellationException()) }
uiState.update {it.copy(processing = false)}
}
@@ -110,23 +112,29 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
}
val resetResults = inputQueries.size == 1
- uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
+ uiState.update {it.copy(processing = true, errorMessage = null)}
val res = mutableListOf()
- inputQueries.forEach { inputQuery ->
- try {
- res.addAll(repository.getResultsFromSource(inputQuery, resetResults))
- } catch (e: Exception) {
- if (updateResultDataJob?.isCancelled == false || e is YoutubeDLException){
- uiState.update {it.copy(
- processing = false,
- errorMessage = Pair(R.string.no_results, e.message.toString()),
- actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
- )}
- Log.e(tag, e.toString())
- }
- }
+ val requestSemaphore = Semaphore(10)
+ inputQueries.forEach { inputQuery ->
+ parsingQueriesJobList.add(viewModelScope.launch(Dispatchers.IO){
+ requestSemaphore.withPermit {
+ try {
+ res.addAll(repository.getResultsFromSource(inputQuery, resetResults))
+ } catch (e: Exception) {
+ if (updateResultDataJob?.isCancelled == false || e is YoutubeDLException){
+ uiState.update {it.copy(
+ processing = false,
+ errorMessage = e.message.toString(),
+ )}
+ Log.e(tag, e.toString())
+ }
+
+ }
+ }
+ })
}
+ parsingQueriesJobList.joinAll()
if (!isForegrounded() && inputQueries.size > 1){
notificationUtil.showQueriesFinished()
}
@@ -279,17 +287,12 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
uiState.update {
it.copy(
processing = false,
- errorMessage = Pair(R.string.no_results, e.message.toString()),
- actions = mutableListOf(
- Pair(
- R.string.copy_log,
- ResultAction.COPY_LOG
- )
- )
+ errorMessage = e.message.toString(),
)
}
Log.e(tag, e.toString())
}
+ updatingFormats.emit(false)
}
}
updateFormatsResultDataJob?.start()
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 64c96b24..e296ceaa 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/HomeFragment.kt
@@ -114,6 +114,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private var appBarLayout: AppBarLayout? = null
private var materialToolbar: MaterialToolbar? = null
private var loadingItems: Boolean = false
+ private var queryList = mutableListOf()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -255,8 +256,28 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
launch{
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
- kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed ={}) }
- resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
+ val isSingleQueryAndURL = queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()
+
+ kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, continueAnyway = isSingleQueryAndURL, continued = {
+ lifecycleScope.launch {
+ if (sharedPreferences!!.getBoolean("download_card", true)) {
+ withContext(Dispatchers.Main){
+ showSingleDownloadSheet(
+ resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),
+ type = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!),
+ disableUpdateData = true
+ )
+ }
+ } else {
+ val downloadItem = downloadViewModel.createDownloadItemFromResult(
+ result = downloadViewModel.createEmptyResultItem(queryList.first()),
+ givenType = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
+ )
+ downloadViewModel.queueDownloads(listOf(downloadItem))
+ }
+ }
+ }, closed = {}) }
+ resultViewModel.uiState.update {it.copy(errorMessage = null) }
}
loadingItems = res.processing
@@ -270,9 +291,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
- this@HomeFragment.downloadAllFab!!.visibility = VISIBLE
+ downloadAllFab!!.visibility = VISIBLE
}else{
- this@HomeFragment.downloadAllFab!!.visibility = GONE
+ downloadAllFab!!.visibility = GONE
}
}
}
@@ -602,7 +623,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private fun initSearch(searchView: SearchView){
- val queryList = mutableListOf()
+ queryList = mutableListOf()
if (queriesChipGroup!!.childCount > 0){
queriesChipGroup!!.children.forEach {
val query = (it as Chip).text.toString().trim {it2 -> it2 <= ' '}
@@ -689,7 +710,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
@SuppressLint("RestrictedApi")
private fun showSingleDownloadSheet(
resultItem: ResultItem,
- type: DownloadViewModel.Type
+ type: DownloadViewModel.Type,
+ disableUpdateData : Boolean = false
){
if(findNavController().currentBackStack.value.firstOrNull {it.destination.id == R.id.downloadBottomSheetDialog} == null &&
findNavController().currentDestination?.id == R.id.homeFragment
@@ -698,6 +720,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
val bundle = Bundle()
bundle.putParcelable("result", resultItem)
bundle.putSerializable("type", downloadViewModel.getDownloadType(type, resultItem.url))
+ if (disableUpdateData) {
+ bundle.putBoolean("disableUpdateData", true)
+ }
findNavController().navigate(R.id.downloadBottomSheetDialog, bundle)
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/ActiveDownloadMinifiedAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/ActiveDownloadMinifiedAdapter.kt
index 540a7fc9..1a4ba055 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/ActiveDownloadMinifiedAdapter.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/ActiveDownloadMinifiedAdapter.kt
@@ -76,6 +76,10 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
progressBar.progress = 0
progressBar.isIndeterminate = true
+ //OUTPUT ------------------------------------------------------------
+ val output = card.findViewById(R.id.output)
+ output.tag = "${item.id}##output"
+
// TITLE ----------------------------------
val itemTitle = card.findViewById(R.id.title)
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
@@ -131,8 +135,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
- val pause = popup.menu[0]
- val resume = popup.menu[1]
+// val pause = popup.menu[0]
+// val resume = popup.menu[1]
//
// if (paused){
// pause.isVisible = false
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 477c4cdc..65dd89f9 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
@@ -86,6 +86,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var result: ResultItem
private lateinit var type: Type
+ private var disableUpdateData : Boolean = false
private var currentDownloadItem: DownloadItem? = null
private var incognito: Boolean = false
@@ -108,6 +109,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
dwl = arguments?.getParcelable("downloadItem")
}
type = arguments?.getSerializable("type") as Type
+ disableUpdateData = arguments?.getBoolean("disableUpdateData") == true
if (res == null){
dismiss()
@@ -429,13 +431,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
//if auto-update after the card is open is off
- if (result.title.isEmpty() && currentDownloadItem == null) {
- if(sharedPreferences.getBoolean("quick_download", false)) {
- (updateItem.parent as LinearLayout).visibility = View.VISIBLE
- updateItem.setOnClickListener {
- (updateItem.parent as LinearLayout).visibility = View.GONE
- initUpdateData()
- }
+ if (result.title.isEmpty() && currentDownloadItem == null && sharedPreferences.getBoolean("quick_download", false)) {
+ (updateItem.parent as LinearLayout).visibility = View.VISIBLE
+ updateItem.setOnClickListener {
+ (updateItem.parent as LinearLayout).visibility = View.GONE
+ initUpdateData()
}
}else{
(updateItem.parent as LinearLayout).visibility = View.GONE
@@ -463,24 +463,28 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
//update in the background if there is no data
- if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
- initUpdateData()
- }else {
- val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
- if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
- initUpdateFormats(result)
+ if (!disableUpdateData) {
+ if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
+ initUpdateData()
+ }else {
+ val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
+ if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
+ initUpdateFormats(result)
+ }
}
}
+
+
lifecycleScope.launch {
resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){
- kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed = {
+ kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, true, continued = {}, closed = {
dismiss()
}) }
- resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
+ resultViewModel.uiState.update {it.copy(errorMessage = null) }
}
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt
index 5474307e..a0a9c1ef 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt
@@ -774,6 +774,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val fileSizes = mutableListOf()
items.forEach {
if (it.type == DownloadViewModel.Type.video){
+ println(it.format.filesize)
if (it.format.filesize <= 5L) {
fileSizes.add(0)
}else{
@@ -787,6 +788,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
}
fileSizes.add(it.format.filesize + audioFormatSize)
}
+ }else if (it.type == DownloadViewModel.Type.audio){
+ fileSizes.add(it.format.filesize)
}
}
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 0a6e3d7a..53a60945 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
@@ -114,7 +114,7 @@ class FormatSelectionBottomSheetDialog(
return
}
- items = _items.distinctBy { it!!.url }.toMutableList()
+ items = _items.toMutableList()
if (items.size == 1) {
formats = items.first()!!.allFormats
}else{
@@ -192,8 +192,10 @@ class FormatSelectionBottomSheetDialog(
refreshBtn.setOnClickListener {
lifecycleScope.launch {
- val itemsThatHaveFormats = items.filter { it!!.allFormats.isNotEmpty() }
- val itemsWithMissingFormats = items.filter { it!!.allFormats.isEmpty() }.ifEmpty { items }
+ val distinctItems = items.distinctBy { it!!.url }
+
+ val itemsThatHaveFormats = distinctItems.filter { it!!.allFormats.isNotEmpty() }
+ val itemsWithMissingFormats = distinctItems.filter { it!!.allFormats.isEmpty() }.ifEmpty { distinctItems }
if (itemsWithMissingFormats.size > 10){
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
@@ -238,11 +240,7 @@ class FormatSelectionBottomSheetDialog(
}
}.onFailure { err ->
withContext(Dispatchers.Main){
- UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState(
- false,
- Pair(R.string.no_results, err.message.toString()),
- mutableListOf(Pair(R.string.copy_log, ResultViewModel.ResultAction.COPY_LOG))
- ), closed = {})
+ UiUtil.handleNoResults(requireActivity(), err.message.toString(), false, continued = {}, closed = {})
}
}
@@ -270,9 +268,9 @@ class FormatSelectionBottomSheetDialog(
}
}else{
multipleFormatsListener.onFormatUpdated(it.url, it.formats)
- items.firstOrNull { item -> item!!.url == it.url }?.apply {
- allFormats.clear()
- allFormats.addAll(it.formats)
+ items.filter { item -> item!!.url == it.url }.forEach { d ->
+ d?.allFormats?.clear()
+ d?.allFormats?.addAll(it.formats)
}
progressInt++
lifecycleScope.launch(Dispatchers.Main) {
@@ -290,7 +288,7 @@ class FormatSelectionBottomSheetDialog(
val flatFormatCollection = formatCollection.flatten()
val commonFormats =
flatFormatCollection.groupingBy { it.format_id }.eachCount()
- .filter { it.value == items.size }
+ .filter { it.value == distinctItems.size }
.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
.map { it.value }
formats.addAll(commonFormats)
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 4dab5c32..904508a5 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
@@ -53,6 +53,7 @@ import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.VideoPlayerUtil
+import com.deniscerri.ytdl.work.DownloadWorker
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@@ -72,6 +73,9 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
@@ -86,8 +90,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
private lateinit var queuedAdapter: GenericDownloadAdapter
- private var activeCount: Int = 0
-
+
private lateinit var downloadManager: DownloadManager
private lateinit var sharedPreferences: SharedPreferences
@@ -97,6 +100,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
infoUtil = InfoUtil(requireActivity())
+ notificationUtil = NotificationUtil(requireActivity())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
@@ -237,12 +241,6 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
}
}
- lifecycleScope.launch {
- downloadViewModel.activeDownloadsCount.collectLatest {
- activeCount = it
- }
- }
-
bottomSheetLink.text = item.url
bottomSheetLink.setOnClickListener{
@@ -482,21 +480,42 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
override fun onCancelClick(itemID: Long) {
lifecycleScope.launch {
- if (activeCount == 1){
- val queue = withContext(Dispatchers.IO){
- val list = downloadViewModel.getQueued().toMutableList()
- list.map { it.status = DownloadRepository.Status.Queued.toString() }
- list
- }
+ YoutubeDL.getInstance().destroyProcessById(itemID.toString())
+ notificationUtil.cancelDownloadNotification(itemID.toInt())
- runBlocking {
- downloadViewModel.queueDownloads(queue)
+ val item = withContext(Dispatchers.IO){
+ downloadViewModel.getItemByID(itemID)
+ }
+ item.status = DownloadRepository.Status.Cancelled.toString()
+ withContext(Dispatchers.IO){
+ downloadViewModel.updateDownload(item)
+ }
+
+ val activeCount = withContext(Dispatchers.IO){
+ downloadViewModel.getActiveDownloadsCount()
+ }
+
+ if (activeCount == 0){
+ val queuedCount = withContext(Dispatchers.IO){
+ downloadViewModel.getQueuedDownloadsCount()
+ }
+ if (queuedCount == 0) {
+ sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
}
}
- }
+ if (activeCount == 1){
+ val queue = withContext(Dispatchers.IO){
+ downloadViewModel.getQueued()
+ }
- cancelActiveDownload(itemID)
+ if (!sharedPreferences.getBoolean("paused_downloads", false)){
+ runBlocking {
+ downloadViewModel.queueDownloads(queue)
+ }
+ }
+ }
+ }
}
override fun onCardClick() {
this.dismiss()
@@ -505,22 +524,29 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
)
}
- private fun cancelActiveDownload(itemID: Long){
- lifecycleScope.launch {
- val id = itemID.toInt()
- YoutubeDL.getInstance().destroyProcessById(id.toString())
- notificationUtil.cancelDownloadNotification(id)
+ //dont remove
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
+ val progressBar = requireView().findViewWithTag("${event.downloadItemID}##progress")
+ val outputText = requireView().findViewWithTag("${event.downloadItemID}##output")
- withContext(Dispatchers.IO){
- downloadViewModel.getItemByID(itemID)
- }.let {
- it.status = DownloadRepository.Status.Cancelled.toString()
- lifecycleScope.launch(Dispatchers.IO){
- downloadViewModel.updateDownload(it)
- }
- }
+ requireActivity().runOnUiThread {
+ try {
+ progressBar?.setProgressCompat(event.progress, true)
+ outputText?.text = event.output
+ }catch (ignored: Exception) {}
}
+ }
+
+ override fun onStart() {
+ super.onStart()
+ EventBus.getDefault().register(this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ EventBus.getDefault().unregister(this)
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt
index 229be4d9..daa0cd48 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/downloadLogs/DownloadLogFragment.kt
@@ -48,6 +48,9 @@ class DownloadLogFragment : Fragment() {
private lateinit var logViewModel: LogViewModel
private lateinit var sharedPreferences: SharedPreferences
+ private var autoScroll : Boolean = true
+ private var scrollDownBtn : MenuItem? = null
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -113,6 +116,7 @@ class DownloadLogFragment : Fragment() {
contentScrollView.enableFastScroll()
+ scrollDownBtn = bottomAppBar?.menu?.children?.first { it.itemId == R.id.scroll_down }
val slider = view.findViewById(R.id.textsize_seekbar)
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
@@ -124,16 +128,17 @@ class DownloadLogFragment : Fragment() {
scrollView.removeAllViews()
parent.removeView(scrollView)
parent.addView(content, 0)
- contentScrollView.setPadding(0,0,0,
- (requireContext().resources.displayMetrics.density * 150).toInt()
- )
+// contentScrollView.setPadding(0,0,0,
+// (requireContext().resources.displayMetrics.density * 150).toInt()
+// )
+ updateAutoScrollState()
}else{
val parent = content.parent as ViewGroup
parent.removeView(content)
scrollView = HorizontalScrollView(requireContext())
- scrollView.setPadding(0,0,0,
- (requireContext().resources.displayMetrics.density * 150).toInt()
- )
+// scrollView.setPadding(0,0,0,
+// (requireContext().resources.displayMetrics.density * 150).toInt()
+// )
contentScrollView.setPadding(0,0,0,0)
scrollView.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -142,11 +147,13 @@ class DownloadLogFragment : Fragment() {
scrollView.addView(content)
scrollView.id = R.id.horizontalscroll_output
parent.addView(scrollView, 0)
+ updateAutoScrollState()
}
}
R.id.scroll_down -> {
m.isVisible = false
+ autoScroll = true
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
@@ -171,9 +178,12 @@ class DownloadLogFragment : Fragment() {
}
contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy ->
- if (sy < osy){
- bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
- }
+ updateAutoScrollState()
+ }
+
+ contentScrollView.setOnTouchListener { view, motionEvent ->
+ autoScroll = false
+ false
}
@@ -185,9 +195,11 @@ class DownloadLogFragment : Fragment() {
content.setText(logItem.content, TextView.BufferType.SPANNABLE)
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
}
- if (!bottomAppBar.menu.children.first { it.itemId == R.id.scroll_down }.isVisible){
- content.scrollTo(0, content.height)
- contentScrollView.fullScroll(View.FOCUS_DOWN)
+ if (autoScroll){
+ //content.scrollTo(0, content.height)
+ content.post {
+ contentScrollView.fullScroll(View.FOCUS_DOWN)
+ }
}
}
}
@@ -195,6 +207,11 @@ class DownloadLogFragment : Fragment() {
}
}
+ private fun updateAutoScrollState() {
+ val canVerticallyScroll = contentScrollView.canScrollVertically(1)
+ scrollDownBtn?.isVisible = canVerticallyScroll
+ }
+
companion object {
private const val TAG = "DownloadLogActivity"
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt
index 30dd34bd..ce7b172c 100644
--- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt
@@ -81,8 +81,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
editor.putString("command_path", FileUtil.getDefaultCommandPath())
}
- if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
- VERSION.SDK_INT < 30) {
+ if (FileUtil.hasAllFilesAccess()) {
accessAllFiles!!.isVisible = false
cacheDownloads!!.isEnabled = true
}else{
@@ -90,8 +89,6 @@ class FolderSettingsFragment : BaseSettingsFragment() {
cacheDownloads!!.isEnabled = false
}
- editor.apply()
-
musicPath!!.summary = FileUtil.formatPath(preferences.getString("music_path", "")!!)
musicPath!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
diff --git a/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt b/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt
index 2d655873..2ed10798 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt
@@ -261,20 +261,27 @@ object Extensions {
}
}
-// fun String.appendLineToLog(line: String): String {
-// val lines = this.split("\n")
-// if (!lines.takeLast(3).contains(line)){
-// //clean dublicate progress + add newline
-// var newLine = line
-// if (newLine.contains("[download")) {
-// newLine = "[download]" + line.split("[download]").last()
-// }
-//
-// return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
-// }
-//
-// return this
-// }
+ fun String.appendLineToLog(line: String = ""): String {
+ val lines = this.lines().toMutableList()
+ val finishingProgressLinesRegex = Pattern.compile("\\[download]\\h+(100%|[a-zA-Z])")
+
+ if (line.isNotBlank()) {
+ var newLine = ""
+ val newLines = line.lines()
+ if (!lines.takeLast(3).any { newLines.contains(it) }) {
+ lines.addAll(newLines.dropLast(1))
+ newLine = "\n${newLines.last()}"
+ }
+
+ return lines.dropLastWhile {
+ it.contains("[download") && !finishingProgressLinesRegex.matcher(it).find()
+ }.joinToString("\n") + newLine
+ }
+
+ return lines.filterNot {
+ it.contains("[download") && !finishingProgressLinesRegex.matcher(it).find()
+ }.joinToString("\n")
+ }
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
if(!hideThumb){
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 2d56345c..14fe4710 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/FileUtil.kt
@@ -400,4 +400,9 @@ object FileUtil {
}
}
+ fun hasAllFilesAccess() : Boolean {
+ if (Build.VERSION.SDK_INT < 30) return true
+ return Environment.isExternalStorageManager()
+ }
+
}
\ 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 780cdd58..97969898 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
-import android.content.res.Resources.NotFoundException
import android.os.Handler
import android.os.Looper
import android.text.Html
@@ -43,7 +42,6 @@ import java.lang.reflect.Type
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
-import java.util.regex.Pattern
class InfoUtil(private val context: Context) {
@@ -168,6 +166,13 @@ class InfoUtil(private val context: Context) {
fun getYoutubeVideo(url: String): List? {
val theURL = url.replace("\\?list.*".toRegex(), "")
try{
+ //TODO NEWPIPE TEST
+// val extractor = ServiceList.YouTube.getStreamExtractor(url)
+// extractor.fetchPage()
+// val extractor2 = ServiceList.YouTube.getPlaylistExtractor(url)
+// extractor2.fetchPage()
+// var info = extractor2.getPage(extractor2.initialPage.nextPage)
+
val id = getIDFromYoutubeURL(theURL)
val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) {
@@ -678,7 +683,7 @@ class InfoUtil(private val context: Context) {
}
val lang = sharedPreferences.getString("app_language", "en")
if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
- var extractorArgs = "player_client=default,mediaconnect,android"
+ var extractorArgs = "player_client=default,mediaconnect"
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang"
}
@@ -1034,6 +1039,12 @@ class InfoUtil(private val context: Context) {
}
}
+ private fun MutableList.addOption(vararg options: String) {
+ options.forEach {
+ this.add(it)
+ }
+ }
+
@SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
@@ -1052,14 +1063,16 @@ class InfoUtil(private val context: Context) {
}
}
+ val metadataCommands = mutableListOf()
+
if (downloadItem.playlistIndex != null && useItemURL) {
- request.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
+ metadataCommands.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
}
val type = downloadItem.type
val downDir : File
- val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite() || sharedPreferences.getBoolean("access_all_files", false)
+ val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
if (!sharedPreferences.getBoolean("cache_downloads", true) && canWrite){
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
request.addOption("--no-quiet")
@@ -1162,18 +1175,14 @@ class InfoUtil(private val context: Context) {
}
if(downloadItem.title.isNotBlank()){
- request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(180)))
+ metadataCommands.addOption("--replace-in-metadata", "title", ".+", downloadItem.title.take(180))
+ metadataCommands.addOption("--parse-metadata", "%(title)s:%(meta_title)s")
}
if (downloadItem.author.isNotBlank()){
- request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
- }
-
- request.addOption("--parse-metadata", "uploader:^(?P.*?)(?:(?= - Topic)|$)")
-
- if (embedMetadata){
- request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
+ metadataCommands.addOption("--replace-in-metadata", "uploader", ".+", downloadItem.author.take(30))
+ metadataCommands.addOption("--parse-metadata", "%(uploader)s:%(artist)s")
}
if (downloadItem.downloadSections.isNotBlank()){
@@ -1196,7 +1205,7 @@ class InfoUtil(private val context: Context) {
}
if (sharedPreferences.getBoolean("use_audio_quality", false)){
- request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0))
+ request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0).toString())
}
if (sharedPreferences.getBoolean("write_description", false)){
@@ -1204,7 +1213,7 @@ class InfoUtil(private val context: Context) {
}
if (downloadItem.url.isYoutubeURL()) {
- var extractorArgs = "player_client=default,mediaconnect,android"
+ var extractorArgs = "player_client=default,mediaconnect"
val lang = sharedPreferences.getString("app_language", "en")
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang"
@@ -1261,7 +1270,7 @@ class InfoUtil(private val context: Context) {
}
request.addOption("-x")
- val formatSorting = StringBuilder("hasaud")
+ val formatSorting = StringBuilder("hasaud,size")
if (aCodecPref.isNotBlank()){
formatSorting.append(",acodec:$aCodecPref")
@@ -1285,31 +1294,46 @@ class InfoUtil(private val context: Context) {
request.addOption("-P", downDir.absolutePath)
request.addOption("-S", formatSorting.toString())
+ metadataCommands.addOption("--parse-metadata", """%(uploader,artist,channel,creator|null)s:^(?P.*?)(?:(?= - Topic)|$)""")
+
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters")
request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
}else{
if (embedMetadata){
- request.addOption("--embed-metadata")
- request.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P[^,]*)")
- request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
+ metadataCommands.addOption("--embed-metadata")
- request.addOption("--parse-metadata", "description:(?:Released on: )(?P\\d{4})")
- request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P\\d+)")
+ val emptyAuthor = downloadItem.author.isEmpty()
+ val usePlaylistMetadata = sharedPreferences.getBoolean("playlist_as_album", true)
- if (downloadItem.playlistTitle.isNotEmpty()) {
- request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
- request.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P\\d+)")
- } else {
- request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
+ if (emptyAuthor) {
+ if (usePlaylistMetadata) {
+ metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist,uploader|)s:^(?P.*?)(?:(?=,\\s+)|$)")
+ }else{
+ metadataCommands.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P.*?)(?:(?=,\\s+)|$)")
+ }
+ }else{
+ if (usePlaylistMetadata) {
+ metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist|)s:^(?P.*?)(?:(?=,\\s+)|$)")
+ }else{
+ metadataCommands.addOption("--parse-metadata", "%(artist|)s:^(?P.*?)(?:(?=,\\s+)|$)")
+ }
}
+
+
+ metadataCommands.addOption("--parse-metadata", "description:(?:Released on: )(?P\\d{4})")
+ metadataCommands.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P\\d+)")
+ metadataCommands.addOption("--parse-metadata", "%(album_artist,first_artist|)s:%(album_artist)s")
+ metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P\\d+)")
+
+
}
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
if (downloadItem.audioPreferences.embedThumb){
- request.addOption("--embed-thumbnail")
- if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
+ metadataCommands.addOption("--embed-thumbnail")
+ if (!request.hasOption("--convert-thumbnails")) metadataCommands.addOption("--convert-thumbnails", thumbnailFormat!!)
val thumbnailConfig = StringBuilder("")
val cropConfig = """-vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"""""
@@ -1326,7 +1350,7 @@ class InfoUtil(private val context: Context) {
runCatching {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
config.writeText(thumbnailConfig.toString())
- request.addOption("--config", config.absolutePath)
+ metadataCommands.addOption("--config", config.absolutePath)
}
}
@@ -1339,6 +1363,8 @@ class InfoUtil(private val context: Context) {
}
DownloadViewModel.Type.video -> {
+ metadataCommands.addOption("--parse-metadata", """%(uploader,channel,creator,artist|null)s:^(?P.*?)(?:(?= - Topic)|$)""")
+
val supportedContainers = context.resources.getStringArray(R.array.video_containers)
if (downloadItem.videoPreferences.addChapters) {
@@ -1359,7 +1385,8 @@ class InfoUtil(private val context: Context) {
){
cont = outputContainer
- if (downloadItem.videoPreferences.recodeVideo) {
+ val cantRecode = listOf("avi")
+ if (downloadItem.videoPreferences.recodeVideo && !cantRecode.contains(cont)) {
request.addOption("--recode-video", outputContainer.lowercase())
}else{
request.addOption("--merge-output-format", outputContainer.lowercase())
@@ -1368,7 +1395,7 @@ class InfoUtil(private val context: Context) {
if (!listOf("webm", "avi", "flv").contains(outputContainer.lowercase())) {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) {
- request.addOption("--embed-thumbnail")
+ metadataCommands.addOption("--embed-thumbnail")
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
}
}
@@ -1382,7 +1409,6 @@ class InfoUtil(private val context: Context) {
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
val format = downloadItem.allFormats.find { it.format_id == f }
format?.run {
- println(format_id)
if (this.format_id.matches(".*-[0-9]+".toRegex())) {
if (!this.lang.isNullOrBlank() && this.lang != "None") {
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
@@ -1591,7 +1617,9 @@ class InfoUtil(private val context: Context) {
)
}
-
+ if (metadataCommands.isNotEmpty()){
+ request.addCommands(metadataCommands)
+ }
return request
}
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 5010f2ac..8e2d1088 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt
@@ -35,6 +35,7 @@ object NavbarUtil {
val pref = settings.getString("start_destination", "")!!
val items = getDefaultNavBarItems(context)
val navBarHasPref = getNavBarPrefs().contains(items.indexOfFirst { it.itemId == navItems[pref] }.toString())
+ println("START FRAGMENT: $pref")
return if (pref == "") {
R.id.homeFragment
}else {
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 ada4893b..232adddc 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt
@@ -20,6 +20,7 @@ import androidx.core.content.FileProvider
import androidx.core.os.bundleOf
import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavDeepLinkBuilder
+import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
@@ -148,11 +149,9 @@ class NotificationUtil(var context: Context) {
fun createDownloadServiceNotification(
pendingIntent: PendingIntent?,
title: String?,
- itemID: Int,
): Notification {
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
-
return notificationBuilder
.setContentTitle(title)
.setOngoing(true)
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 05ab1c08..61d90220 100644
--- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt
@@ -103,6 +103,7 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.Queue
+import java.util.function.Predicate
object UiUtil {
@@ -862,14 +863,16 @@ object UiUtil {
true
}
- if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE
+ fun String?.doesntExist() : Boolean { return this.isNullOrBlank() || this == "None"}
+
+ if (format.format_id.doesntExist()) formatIdParent?.visibility = View.GONE
else {
formatIdParent?.findViewById(R.id.format_id_value)?.text = format.format_id
formatIdParent?.setOnClickListener(clicker)
formatIdParent?.setOnLongClickListener(longClicker)
}
- if (format.url.isNullOrBlank()) formatURLParent?.visibility = View.GONE
+ if (format.url.doesntExist()) formatURLParent?.visibility = View.GONE
else {
formatURLParent?.findViewById(R.id.format_url_value)?.text = format.url
formatURLParent?.setOnClickListener(clicker)
@@ -877,7 +880,7 @@ object UiUtil {
}
- if (format.container.isBlank()) containerParent?.visibility = View.GONE
+ if (format.container.doesntExist()) containerParent?.visibility = View.GONE
else {
containerParent?.findViewById(R.id.container_value)?.text = format.container
containerParent?.setOnClickListener(clicker)
@@ -893,7 +896,7 @@ object UiUtil {
format.acodec.uppercase()
}
- if (codecField.isBlank()) codecParent?.visibility = View.GONE
+ if (codecField.doesntExist()) codecParent?.visibility = View.GONE
else {
codecParent?.findViewById(R.id.codec_value)?.text = codecField
codecParent?.setOnClickListener(clicker)
@@ -907,28 +910,28 @@ object UiUtil {
filesizeParent?.setOnLongClickListener(longClicker)
}
- if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE
+ if (format.format_note.doesntExist()) formatnoteParent?.visibility = View.GONE
else {
formatnoteParent?.findViewById(R.id.format_note_value)?.text = format.format_note
formatnoteParent?.setOnClickListener(clicker)
formatnoteParent?.setOnLongClickListener(longClicker)
}
- if (format.fps.isNullOrBlank() || format.fps == "0") fpsParent?.visibility = View.GONE
+ if (format.fps.doesntExist() || format.fps == "0") fpsParent?.visibility = View.GONE
else {
fpsParent?.findViewById(R.id.fps_value)?.text = format.fps
fpsParent?.setOnClickListener(clicker)
fpsParent?.setOnLongClickListener(longClicker)
}
- if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE
+ if (format.asr.doesntExist()) asrParent?.visibility = View.GONE
else {
asrParent?.findViewById(R.id.asr_value)?.text = format.asr
asrParent?.setOnClickListener(clicker)
asrParent?.setOnLongClickListener(longClicker)
}
- if (format.tbr.isNullOrBlank()) bitrateParent?.visibility = View.GONE
+ if (format.tbr.doesntExist()) bitrateParent?.visibility = View.GONE
else{
bitrateParent?.findViewById(R.id.bitrate_value)?.text = format.tbr
bitrateParent?.setOnClickListener(clicker)
@@ -990,7 +993,21 @@ object UiUtil {
val chips = mutableListOf()
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
- tmp.text = Locale(s).displayLanguage
+ s.split("-", ".*").run {
+ if (s.endsWith(".*")) {
+ val name = "${Locale(this[0]).displayName} (${context.getString(R.string.all)})"
+ tmp.text = name
+ }else{
+ if (this.size == 1) {
+ Locale(this[0])
+ } else {
+ Locale(this[0], this[1])
+ }.apply {
+ tmp.text = this.displayName
+ }
+ }
+ }
+
tmp.tag = s
tmp.id = index
@@ -1564,24 +1581,20 @@ object UiUtil {
}
-
- fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState, closed: () -> Unit){
- val title = context.getString(it.errorMessage!!.first)
- val message = it.errorMessage!!.second
-
+ fun handleNoResults(context: Activity, message: String, continueAnyway: Boolean = false, continued: () -> Unit, closed: () -> Unit) {
val errDialog = MaterialAlertDialogBuilder(context)
- .setTitle(title)
+ .setTitle(R.string.no_results)
.setMessage(message)
- for (a in it.actions!!){
- when(a.second){
- ResultViewModel.ResultAction.COPY_LOG -> {
- errDialog.setPositiveButton(a.first) { d:DialogInterface?, _:Int ->
- val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- clipboard.setText(message)
- d?.dismiss()
- }
- }
+ errDialog.setPositiveButton(R.string.copy_log) { d:DialogInterface?, _:Int ->
+ val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ clipboard.setText(message)
+ d?.dismiss()
+ }
+
+ if (continueAnyway) {
+ errDialog.setNeutralButton(R.string.continue_anyway) {d: DialogInterface?, _:Int ->
+ continued()
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt
index d285036d..2c18f12b 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/DownloadWorker.kt
@@ -1,9 +1,12 @@
package com.deniscerri.ytdl.work
import android.annotation.SuppressLint
+import android.app.PendingIntent
import android.content.Context
+import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
+import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.DisplayMetrics
@@ -20,6 +23,7 @@ import androidx.work.await
import androidx.work.workDataOf
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.App
+import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.models.HistoryItem
@@ -73,15 +77,28 @@ class DownloadWorker(
//val createResultItem = inputData.getBoolean("createResultItem", true)
val confTmp = Configuration(context.resources.configuration)
- val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-")
- confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1]))
+ val locale = if (Build.VERSION.SDK_INT < 33) {
+ sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
+ }else{
+ Locale.getDefault().language
+ }.run {
+ split("-")
+ }.run {
+ if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
+ }
+ confTmp.setLocale(locale)
val metrics = DisplayMetrics()
val resources = Resources(context.assets, metrics, confTmp)
- val pendingIntent = NavDeepLinkBuilder(context)
- .setGraph(R.navigation.nav_graph)
- .setDestination(R.id.downloadQueueMainFragment)
- .createPendingIntent()
+ val openQueueIntent = Intent(context, MainActivity::class.java)
+ openQueueIntent.setAction(Intent.ACTION_VIEW)
+ openQueueIntent.putExtra("destination", "Queue")
+ val openDownloadQueue = PendingIntent.getActivity(
+ context,
+ 1000000000,
+ openQueueIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
val workNotif = notificationUtil.createDefaultWorkerNotification()
val foregroundInfo = ForegroundInfo(1000000000, workNotif)
@@ -112,7 +129,7 @@ class DownloadWorker(
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
eligibleDownloads.forEach{downloadItem ->
- val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title.ifEmpty { downloadItem.url }, downloadItem.id.toInt())
+ val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
notificationUtil.notify(downloadItem.id.toInt(), notification)
CoroutineScope(Dispatchers.IO).launch {
@@ -189,7 +206,7 @@ class DownloadWorker(
}
//val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
- var finalPaths : MutableList?
+ var finalPaths = mutableListOf()
if (noCache){
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id))
@@ -212,9 +229,6 @@ class DownloadWorker(
finalPaths.sortBy { File(it).lastModified() }
finalPaths = finalPaths.distinct().toMutableList()
FileUtil.scanMedia(finalPaths, context)
- if (finalPaths.isEmpty()){
- finalPaths = mutableListOf(context.getString(R.string.unfound_file))
- }
}else{
//move file from internal to set download directory
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
@@ -227,11 +241,8 @@ class DownloadWorker(
if (finalPaths.isNotEmpty()){
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
- }else{
- finalPaths = mutableListOf(context.getString(R.string.unfound_file))
}
}catch (e: Exception){
- finalPaths = mutableListOf(context.getString(R.string.unfound_file))
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
@@ -246,51 +257,52 @@ class DownloadWorker(
add("description")
add("txt")
}
- finalPaths = finalPaths?.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }?.toMutableList()
+ finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList()
+ if (finalPaths.isEmpty()){
+ finalPaths = mutableListOf(context.getString(R.string.unfound_file))
+ }
FileUtil.deleteConfigFiles(request)
//put download in history
if (!downloadItem.incognito) {
- if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) {
+ if (request.hasOption("--download-archive") && finalPaths.isEmpty()) {
handler.postDelayed({
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}, 100)
}else{
val unixTime = System.currentTimeMillis() / 1000
- finalPaths?.apply {
- this.first().apply {
- val file = File(this)
- var duration = downloadItem.duration
- val d = file.getMediaDuration(context)
- if (d > 0) duration = d.toStringDuration(Locale.US)
+ finalPaths.first().apply {
+ val file = File(this)
+ var duration = downloadItem.duration
+ val d = file.getMediaDuration(context)
+ if (d > 0) duration = d.toStringDuration(Locale.US)
- downloadItem.format.filesize = file.length()
- downloadItem.format.container = file.extension
- downloadItem.duration = duration
- }
-
- val historyItem = HistoryItem(0,
- downloadItem.url,
- downloadItem.title,
- downloadItem.author,
- downloadItem.duration,
- downloadItem.thumb,
- downloadItem.type,
- unixTime,
- this,
- downloadItem.website,
- downloadItem.format,
- downloadItem.id,
- commandString)
- historyDao.insert(historyItem)
+ downloadItem.format.filesize = file.length()
+ downloadItem.format.container = file.extension
+ downloadItem.duration = duration
}
+ val historyItem = HistoryItem(0,
+ downloadItem.url,
+ downloadItem.title,
+ downloadItem.author,
+ downloadItem.duration,
+ downloadItem.thumb,
+ downloadItem.type,
+ unixTime,
+ finalPaths,
+ downloadItem.website,
+ downloadItem.format,
+ downloadItem.id,
+ commandString)
+ historyDao.insert(historyItem)
+
}
}
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished(
- downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
+ downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
)
// if (wasQuickDownloaded && createResultItem){
diff --git a/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt
index d90a273b..31d9ed83 100644
--- a/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdl/work/TerminalDownloadWorker.kt
@@ -49,7 +49,7 @@ class TerminalDownloadWorker(
val intent = Intent(context, TerminalActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
- val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), itemId)
+ val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65))
val foregroundInfo = ForegroundInfo(itemId, notification)
setForegroundAsync(foregroundInfo)
diff --git a/app/src/main/res/drawable/baseline_album_24.xml b/app/src/main/res/drawable/baseline_album_24.xml
new file mode 100644
index 00000000..30fded47
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_album_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/rounded_corner.xml b/app/src/main/res/drawable/rounded_corner.xml
index c9f2a687..01227d24 100644
--- a/app/src/main/res/drawable/rounded_corner.xml
+++ b/app/src/main/res/drawable/rounded_corner.xml
@@ -8,5 +8,5 @@
android:bottom="1dp"
android:top="1dp" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/active_download_card_minified.xml b/app/src/main/res/layout/active_download_card_minified.xml
index 361d5da0..9215d993 100644
--- a/app/src/main/res/layout/active_download_card_minified.xml
+++ b/app/src/main/res/layout/active_download_card_minified.xml
@@ -34,7 +34,7 @@
+ android:layout_height="wrap_content">
+ app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
+
-
-
+
+
diff --git a/app/src/main/res/layout/download_card.xml b/app/src/main/res/layout/download_card.xml
index 6fbe9802..9c3d8f43 100644
--- a/app/src/main/res/layout/download_card.xml
+++ b/app/src/main/res/layout/download_card.xml
@@ -38,7 +38,7 @@
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
+ app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
@@ -173,6 +172,7 @@
-
+ android:layout_height="match_parent">
+ android:fitsSystemWindows="true"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:liftOnScroll="false">
+ app:navigationIcon="@drawable/ic_back" />
@@ -34,34 +36,45 @@
+ android:id="@+id/frameLayout"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout3"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/appbarlayout">
+ android:clipToPadding="false">
-
+ android:id="@+id/horizontalscroll_output"
+ android:layout_height="wrap_content">
+
+
+
+
+
+
@@ -69,27 +82,41 @@
-
-
-
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
-
+
+
+
+
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/queued_download_card.xml b/app/src/main/res/layout/queued_download_card.xml
index 0e9cc689..93b0ce35 100644
--- a/app/src/main/res/layout/queued_download_card.xml
+++ b/app/src/main/res/layout/queued_download_card.xml
@@ -67,7 +67,7 @@
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
+ app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
@@ -113,8 +116,9 @@
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
+ app:elevation="0dp"
android:contentDescription="@string/video"
- android:layout_marginHorizontal="10dp"
+ android:layout_marginHorizontal="5dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/menu/active_downloads_minified.xml b/app/src/main/res/menu/active_downloads_minified.xml
index bf150d99..427dedcb 100644
--- a/app/src/main/res/menu/active_downloads_minified.xml
+++ b/app/src/main/res/menu/active_downloads_minified.xml
@@ -3,19 +3,19 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 815c602c..532f1b07 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1029,13 +1029,16 @@
- en-IN
,
- en-GB
,
- en
,
+ - en.*
,
- es
,
- es-419
,
- es-US
,
+ - es.*
,
- eu
,
- fil
,
- fr
,
- fr-CA
,
+ - fr.*
,
- gl
,
- hr
,
- zu
,
@@ -1051,6 +1054,7 @@
- pl
,
- pt-PT
,
- pt
,
+ - pt.*
,
- ro
,
- sq
,
- sk
,
@@ -1097,6 +1101,7 @@
- zh-CN
,
- zh-TW
,
- zh-HK
,
+ - zh.*
,
- ja
,
- ko
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9c6d70a7..92c7cc34 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -418,4 +418,7 @@
Weekly
Monthly
Clean-up leftover downloads (cancelled, errored)
+ Continue Anyway
+ Use Playlist Name as Album Metadata
+ If Album Metadata is not present, use playlist name instead
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b5b1f7b2..b26dcd49 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -32,6 +32,11 @@
- 5dp
+
+