1.7.9.1 prep

This commit is contained in:
zaednasr 2024-08-11 17:28:02 +02:00
parent c40095ac7f
commit 087ba0781a
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
38 changed files with 642 additions and 369 deletions

View file

@ -201,4 +201,5 @@ dependencies {
implementation 'androidx.compose.material3:material3-android:1.2.1' implementation 'androidx.compose.material3:material3-android:1.2.1'
implementation "io.noties.markwon:core:4.6.2" implementation "io.noties.markwon:core:4.6.2"
implementation("org.greenrobot:eventbus:3.3.1") implementation("org.greenrobot:eventbus:3.3.1")
//implementation("com.github.TeamNewPipe:NewPipeExtractor:0.24.2")
} }

View file

@ -88,5 +88,11 @@
static <1>$Companion Companion; 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 -keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontobfuscate -dontobfuscate

View file

@ -80,24 +80,46 @@
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="youtube.com" />
<data
android:scheme="https"
android:host="youtube.com" />
<data android:host="youtube.com" /> <data android:host="youtube.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.youtube.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.youtu.be" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="youtu.be" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="twitter.com" /> <data android:host="twitter.com" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.twitter.com" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -114,7 +136,7 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="soundcloud.com" /> <data android:host="www.x.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -122,162 +144,177 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="soundcloud.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.soundcloud.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="facebook.com" /> <data android:host="facebook.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="www.facebook.com" /> <data android:host="www.facebook.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="fb.watch" /> <data android:host="fb.watch" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="instagram.com" /> <data android:host="instagram.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="www.instagram.com" /> <data android:host="www.instagram.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="tiktok.com" /> <data android:host="tiktok.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.tiktok.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="vm.tiktok.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="bilibili.tv" /> <data android:host="bilibili.tv" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="youtu.be" /> <data android:host="www.bilibili.tv" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="twitch.com" /> <data android:host="twitch.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="reddit.com" /> <data android:host="www.twitch.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="www.reddit.com" /> <data android:host="www.reddit.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="reddit.com" /> <data android:host="reddit.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="m.reddit.com" /> <data android:host="m.reddit.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="pinterest.com" /> <data android:host="pinterest.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.pinterest.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="music.youtube.com" /> <data android:host="music.youtube.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="m.youtube.com" /> <data android:host="m.youtube.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="vk.com" /> <data android:host="vk.com" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.vk.com" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="rutube.com" /> <data android:host="rutube.com" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="www.rutube.com" />
</intent-filter>
</activity> </activity>
<activity <activity

View file

@ -2,6 +2,7 @@ package com.deniscerri.ytdl
import android.Manifest import android.Manifest
import android.app.ActionBar.LayoutParams import android.app.ActionBar.LayoutParams
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -32,6 +33,7 @@ import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
@ -290,9 +292,6 @@ class MainActivity : BaseActivity() {
} }
} }
} }
override fun onSaveInstanceState(savedInstanceState: Bundle) { override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState) super.onSaveInstanceState(savedInstanceState)
@ -442,6 +441,8 @@ class MainActivity : BaseActivity() {
e.printStackTrace() e.printStackTrace()
} }
}else if (action == Intent.ACTION_VIEW){ }else if (action == Intent.ACTION_VIEW){
navController.popBackStack(navController.graph.startDestinationId, true)
when(intent.getStringExtra("destination")){ when(intent.getStringExtra("destination")){
"Downloads" -> { "Downloads" -> {
navController.navigate(R.id.historyFragment) navController.navigate(R.id.historyFragment)

View file

@ -7,6 +7,7 @@ import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update import androidx.room.Update
import com.deniscerri.ytdl.database.models.LogItem import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@ -43,10 +44,10 @@ interface LogDao {
suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) { suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) {
val l = getByID(id) ?: return val l = getByID(id) ?: return
var log = l.content ?: "" var log = l.content ?: ""
if (resetLog) { log = if (resetLog) {
log = line.lines().joinToString("\n") line.appendLineToLog()
}else{ }else{
log += "\n${line}" log.appendLineToLog(line)
} }
updateLogContent(log, id) updateLogContent(log, id)
} }

View file

@ -5,6 +5,7 @@ import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import com.deniscerri.ytdl.database.models.TerminalItem import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@ -31,10 +32,10 @@ interface TerminalDao {
suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){ suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){
val t = getTerminalById(id) ?: return val t = getTerminalById(id) ?: return
var log = t.log ?: "" var log = t.log ?: ""
if (resetLog) { log = if (resetLog) {
log = line.lines().joinToString("\n") line.lines().joinToString("\n")
}else{ }else{
log += "\n${line}" log.appendLineToLog(line)
} }
updateTerminalLog(log, id) updateTerminalLog(log, id)
} }

View file

@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Parcelable import android.os.Parcelable
@ -148,7 +149,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val confTmp = Configuration(application.resources.configuration) 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() val metrics = DisplayMetrics()
resources = Resources(application.assets, metrics, confTmp) resources = Resources(application.assets, metrics, confTmp)
} }
@ -245,6 +255,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val preferredAudioFormats = getPreferredAudioFormats(resultItem.formats) val preferredAudioFormats = getPreferredAudioFormats(resultItem.formats)
val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
val videoPreferences = VideoPreferences( val videoPreferences = VideoPreferences(
embedSubs, embedSubs,
@ -252,6 +263,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
ArrayList(sponsorblock), ArrayList(sponsorblock),
saveSubs, saveSubs,
saveAutoSubs, saveAutoSubs,
subsLanguages,
audioFormatIDs = preferredAudioFormats, audioFormatIDs = preferredAudioFormats,
recodeVideo = recodeVideo recodeVideo = recodeVideo
) )
@ -354,8 +366,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val format = getFormat(it.allFormats, type) val format = getFormat(it.allFormats, type)
it.format = format it.format = format
var updatedDownloadPath: String = "" var updatedDownloadPath = ""
var container: String = "" var container = ""
when(type){ when(type){
Type.audio -> { Type.audio -> {
@ -363,11 +375,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
container = sharedPreferences.getString("audio_format", "")!! container = sharedPreferences.getString("audio_format", "")!!
} }
Type.video -> { Type.video -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!! updatedDownloadPath = sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!!
container = sharedPreferences.getString("audio_format", "")!! container = sharedPreferences.getString("video_format", "")!!
} }
Type.command -> { Type.command -> {
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!! updatedDownloadPath = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!!
container = "" container = ""
} }
else -> { else -> {
@ -391,6 +403,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false) val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
val customFileNameTemplate = when(historyItem.type) { val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B") 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 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 var path = defaultPath
historyItem.downloadPath.first().apply { historyItem.downloadPath.first().apply {
File(this).parent?.apply { File(this).parent?.apply {
@ -930,10 +943,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}else{ }else{
val queued = repository.updateAll(queuedItems) val queued = repository.updateAll(queuedItems)
if (!sharedPreferences.getBoolean("paused_downloads", false)) { if (sharedPreferences.getBoolean("paused_downloads", true)) {
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" } sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
resetActiveToQueued()
} }
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
if(!useScheduler){ if(!useScheduler){
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
queued.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach { queued.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {

View file

@ -22,11 +22,16 @@ import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
import com.deniscerri.ytdl.util.InfoUtil import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDLException import com.yausername.youtubedl_android.YoutubeDLException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
@ -40,18 +45,13 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
private val dao: ResultDao private val dao: ResultDao
data class ResultsUiState( data class ResultsUiState(
var processing: Boolean, var processing: Boolean,
var errorMessage: Pair<Int, String>?, var errorMessage: String?,
var actions: MutableList<Pair<Int, ResultAction>>? var canContinueOnError: Boolean = false
) )
enum class ResultAction {
COPY_LOG
}
val uiState: MutableStateFlow<ResultsUiState> = MutableStateFlow(ResultsUiState( val uiState: MutableStateFlow<ResultsUiState> = MutableStateFlow(ResultsUiState(
processing = false, processing = false,
errorMessage = null, errorMessage = null,
actions = null
)) ))
@ -63,7 +63,8 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(null) var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(null)
private var updateFormatsResultDataJob: Job? = null private var updateFormatsResultDataJob: Job? = null
var parsingQueries: Job? = null private var parsingQueries: Job? = null
private var parsingQueriesJobList : MutableList<Job> = mutableListOf()
private val sharedPreferences: SharedPreferences private val sharedPreferences: SharedPreferences
@ -101,6 +102,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
fun cancelParsingQueries(){ fun cancelParsingQueries(){
parsingQueries?.cancel(CancellationException()) parsingQueries?.cancel(CancellationException())
parsingQueriesJobList.forEach { it.cancel(CancellationException()) }
uiState.update {it.copy(processing = false)} uiState.update {it.copy(processing = false)}
} }
@ -110,23 +112,29 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
} }
val resetResults = inputQueries.size == 1 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<ResultItem?>() val res = mutableListOf<ResultItem?>()
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){ if (!isForegrounded() && inputQueries.size > 1){
notificationUtil.showQueriesFinished() notificationUtil.showQueriesFinished()
} }
@ -279,17 +287,12 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
uiState.update { uiState.update {
it.copy( it.copy(
processing = false, processing = false,
errorMessage = Pair(R.string.no_results, e.message.toString()), errorMessage = e.message.toString(),
actions = mutableListOf(
Pair(
R.string.copy_log,
ResultAction.COPY_LOG
)
)
) )
} }
Log.e(tag, e.toString()) Log.e(tag, e.toString())
} }
updatingFormats.emit(false)
} }
} }
updateFormatsResultDataJob?.start() updateFormatsResultDataJob?.start()

View file

@ -114,6 +114,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private var appBarLayout: AppBarLayout? = null private var appBarLayout: AppBarLayout? = null
private var materialToolbar: MaterialToolbar? = null private var materialToolbar: MaterialToolbar? = null
private var loadingItems: Boolean = false private var loadingItems: Boolean = false
private var queryList = mutableListOf<String>()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@ -255,8 +256,28 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
launch{ launch{
resultViewModel.uiState.collectLatest { res -> resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){ if (res.errorMessage != null){
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed ={}) } val isSingleQueryAndURL = queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
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 loadingItems = res.processing
@ -270,9 +291,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
shimmerCards!!.stopShimmer() shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE shimmerCards!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){ if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
this@HomeFragment.downloadAllFab!!.visibility = VISIBLE downloadAllFab!!.visibility = VISIBLE
}else{ }else{
this@HomeFragment.downloadAllFab!!.visibility = GONE downloadAllFab!!.visibility = GONE
} }
} }
} }
@ -602,7 +623,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private fun initSearch(searchView: SearchView){ private fun initSearch(searchView: SearchView){
val queryList = mutableListOf<String>() queryList = mutableListOf()
if (queriesChipGroup!!.childCount > 0){ if (queriesChipGroup!!.childCount > 0){
queriesChipGroup!!.children.forEach { queriesChipGroup!!.children.forEach {
val query = (it as Chip).text.toString().trim {it2 -> it2 <= ' '} val query = (it as Chip).text.toString().trim {it2 -> it2 <= ' '}
@ -689,7 +710,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
private fun showSingleDownloadSheet( private fun showSingleDownloadSheet(
resultItem: ResultItem, resultItem: ResultItem,
type: DownloadViewModel.Type type: DownloadViewModel.Type,
disableUpdateData : Boolean = false
){ ){
if(findNavController().currentBackStack.value.firstOrNull {it.destination.id == R.id.downloadBottomSheetDialog} == null && if(findNavController().currentBackStack.value.firstOrNull {it.destination.id == R.id.downloadBottomSheetDialog} == null &&
findNavController().currentDestination?.id == R.id.homeFragment findNavController().currentDestination?.id == R.id.homeFragment
@ -698,6 +720,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
val bundle = Bundle() val bundle = Bundle()
bundle.putParcelable("result", resultItem) bundle.putParcelable("result", resultItem)
bundle.putSerializable("type", downloadViewModel.getDownloadType(type, resultItem.url)) bundle.putSerializable("type", downloadViewModel.getDownloadType(type, resultItem.url))
if (disableUpdateData) {
bundle.putBoolean("disableUpdateData", true)
}
findNavController().navigate(R.id.downloadBottomSheetDialog, bundle) findNavController().navigate(R.id.downloadBottomSheetDialog, bundle)
} }
} }

View file

@ -76,6 +76,10 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
progressBar.progress = 0 progressBar.progress = 0
progressBar.isIndeterminate = true progressBar.isIndeterminate = true
//OUTPUT ------------------------------------------------------------
val output = card.findViewById<TextView>(R.id.output)
output.tag = "${item.id}##output"
// TITLE ---------------------------------- // TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title) val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } } 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) popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true) if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
val pause = popup.menu[0] // val pause = popup.menu[0]
val resume = popup.menu[1] // val resume = popup.menu[1]
// //
// if (paused){ // if (paused){
// pause.isVisible = false // pause.isVisible = false

View file

@ -86,6 +86,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var result: ResultItem private lateinit var result: ResultItem
private lateinit var type: Type private lateinit var type: Type
private var disableUpdateData : Boolean = false
private var currentDownloadItem: DownloadItem? = null private var currentDownloadItem: DownloadItem? = null
private var incognito: Boolean = false private var incognito: Boolean = false
@ -108,6 +109,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
dwl = arguments?.getParcelable<DownloadItem>("downloadItem") dwl = arguments?.getParcelable<DownloadItem>("downloadItem")
} }
type = arguments?.getSerializable("type") as Type type = arguments?.getSerializable("type") as Type
disableUpdateData = arguments?.getBoolean("disableUpdateData") == true
if (res == null){ if (res == null){
dismiss() dismiss()
@ -429,13 +431,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
} }
//if auto-update after the card is open is off //if auto-update after the card is open is off
if (result.title.isEmpty() && currentDownloadItem == null) { if (result.title.isEmpty() && currentDownloadItem == null && sharedPreferences.getBoolean("quick_download", false)) {
if(sharedPreferences.getBoolean("quick_download", false)) { (updateItem.parent as LinearLayout).visibility = View.VISIBLE
(updateItem.parent as LinearLayout).visibility = View.VISIBLE updateItem.setOnClickListener {
updateItem.setOnClickListener { (updateItem.parent as LinearLayout).visibility = View.GONE
(updateItem.parent as LinearLayout).visibility = View.GONE initUpdateData()
initUpdateData()
}
} }
}else{ }else{
(updateItem.parent as LinearLayout).visibility = View.GONE (updateItem.parent as LinearLayout).visibility = View.GONE
@ -463,24 +463,28 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
//update in the background if there is no data //update in the background if there is no data
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){ if (!disableUpdateData) {
initUpdateData() if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
}else { initUpdateData()
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") } }else {
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){ val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
initUpdateFormats(result) if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
initUpdateFormats(result)
}
} }
} }
lifecycleScope.launch { lifecycleScope.launch {
resultViewModel.uiState.collectLatest { res -> resultViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null){ if (res.errorMessage != null){
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed = { kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, true, continued = {}, closed = {
dismiss() dismiss()
}) } }) }
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) } resultViewModel.uiState.update {it.copy(errorMessage = null) }
} }
} }
} }

View file

@ -774,6 +774,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val fileSizes = mutableListOf<Long>() val fileSizes = mutableListOf<Long>()
items.forEach { items.forEach {
if (it.type == DownloadViewModel.Type.video){ if (it.type == DownloadViewModel.Type.video){
println(it.format.filesize)
if (it.format.filesize <= 5L) { if (it.format.filesize <= 5L) {
fileSizes.add(0) fileSizes.add(0)
}else{ }else{
@ -787,6 +788,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
fileSizes.add(it.format.filesize + audioFormatSize) fileSizes.add(it.format.filesize + audioFormatSize)
} }
}else if (it.type == DownloadViewModel.Type.audio){
fileSizes.add(it.format.filesize)
} }
} }

View file

@ -114,7 +114,7 @@ class FormatSelectionBottomSheetDialog(
return return
} }
items = _items.distinctBy { it!!.url }.toMutableList() items = _items.toMutableList()
if (items.size == 1) { if (items.size == 1) {
formats = items.first()!!.allFormats formats = items.first()!!.allFormats
}else{ }else{
@ -192,8 +192,10 @@ class FormatSelectionBottomSheetDialog(
refreshBtn.setOnClickListener { refreshBtn.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
val itemsThatHaveFormats = items.filter { it!!.allFormats.isNotEmpty() } val distinctItems = items.distinctBy { it!!.url }
val itemsWithMissingFormats = items.filter { it!!.allFormats.isEmpty() }.ifEmpty { items }
val itemsThatHaveFormats = distinctItems.filter { it!!.allFormats.isNotEmpty() }
val itemsWithMissingFormats = distinctItems.filter { it!!.allFormats.isEmpty() }.ifEmpty { distinctItems }
if (itemsWithMissingFormats.size > 10){ if (itemsWithMissingFormats.size > 10){
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG) continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
@ -238,11 +240,7 @@ class FormatSelectionBottomSheetDialog(
} }
}.onFailure { err -> }.onFailure { err ->
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState( UiUtil.handleNoResults(requireActivity(), err.message.toString(), false, continued = {}, closed = {})
false,
Pair(R.string.no_results, err.message.toString()),
mutableListOf(Pair(R.string.copy_log, ResultViewModel.ResultAction.COPY_LOG))
), closed = {})
} }
} }
@ -270,9 +268,9 @@ class FormatSelectionBottomSheetDialog(
} }
}else{ }else{
multipleFormatsListener.onFormatUpdated(it.url, it.formats) multipleFormatsListener.onFormatUpdated(it.url, it.formats)
items.firstOrNull { item -> item!!.url == it.url }?.apply { items.filter { item -> item!!.url == it.url }.forEach { d ->
allFormats.clear() d?.allFormats?.clear()
allFormats.addAll(it.formats) d?.allFormats?.addAll(it.formats)
} }
progressInt++ progressInt++
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
@ -290,7 +288,7 @@ class FormatSelectionBottomSheetDialog(
val flatFormatCollection = formatCollection.flatten() val flatFormatCollection = formatCollection.flatten()
val commonFormats = val commonFormats =
flatFormatCollection.groupingBy { it.format_id }.eachCount() 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 } } .mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
.map { it.value } .map { it.value }
formats.addAll(commonFormats) formats.addAll(commonFormats)

View file

@ -53,6 +53,7 @@ import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.VideoPlayerUtil 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.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -72,6 +73,9 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext 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.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
@ -86,8 +90,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
private lateinit var queuedAdapter: GenericDownloadAdapter private lateinit var queuedAdapter: GenericDownloadAdapter
private var activeCount: Int = 0
private lateinit var downloadManager: DownloadManager private lateinit var downloadManager: DownloadManager
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
@ -97,6 +100,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
infoUtil = InfoUtil(requireActivity()) infoUtil = InfoUtil(requireActivity())
notificationUtil = NotificationUtil(requireActivity())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager 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.text = item.url
bottomSheetLink.setOnClickListener{ bottomSheetLink.setOnClickListener{
@ -482,21 +480,42 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
override fun onCancelClick(itemID: Long) { override fun onCancelClick(itemID: Long) {
lifecycleScope.launch { lifecycleScope.launch {
if (activeCount == 1){ YoutubeDL.getInstance().destroyProcessById(itemID.toString())
val queue = withContext(Dispatchers.IO){ notificationUtil.cancelDownloadNotification(itemID.toInt())
val list = downloadViewModel.getQueued().toMutableList()
list.map { it.status = DownloadRepository.Status.Queued.toString() }
list
}
runBlocking { val item = withContext(Dispatchers.IO){
downloadViewModel.queueDownloads(queue) 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() { override fun onCardClick() {
this.dismiss() this.dismiss()
@ -505,22 +524,29 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
) )
} }
private fun cancelActiveDownload(itemID: Long){ //dont remove
lifecycleScope.launch { @Subscribe(threadMode = ThreadMode.MAIN)
val id = itemID.toInt() fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
YoutubeDL.getInstance().destroyProcessById(id.toString()) val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
notificationUtil.cancelDownloadNotification(id) val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
withContext(Dispatchers.IO){ requireActivity().runOnUiThread {
downloadViewModel.getItemByID(itemID) try {
}.let { progressBar?.setProgressCompat(event.progress, true)
it.status = DownloadRepository.Status.Cancelled.toString() outputText?.text = event.output
lifecycleScope.launch(Dispatchers.IO){ }catch (ignored: Exception) {}
downloadViewModel.updateDownload(it)
}
}
} }
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
} }
} }

View file

@ -48,6 +48,9 @@ class DownloadLogFragment : Fragment() {
private lateinit var logViewModel: LogViewModel private lateinit var logViewModel: LogViewModel
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
private var autoScroll : Boolean = true
private var scrollDownBtn : MenuItem? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -113,6 +116,7 @@ class DownloadLogFragment : Fragment() {
contentScrollView.enableFastScroll() contentScrollView.enableFastScroll()
scrollDownBtn = bottomAppBar?.menu?.children?.first { it.itemId == R.id.scroll_down }
val slider = view.findViewById<Slider>(R.id.textsize_seekbar) val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem -> bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
@ -124,16 +128,17 @@ class DownloadLogFragment : Fragment() {
scrollView.removeAllViews() scrollView.removeAllViews()
parent.removeView(scrollView) parent.removeView(scrollView)
parent.addView(content, 0) parent.addView(content, 0)
contentScrollView.setPadding(0,0,0, // contentScrollView.setPadding(0,0,0,
(requireContext().resources.displayMetrics.density * 150).toInt() // (requireContext().resources.displayMetrics.density * 150).toInt()
) // )
updateAutoScrollState()
}else{ }else{
val parent = content.parent as ViewGroup val parent = content.parent as ViewGroup
parent.removeView(content) parent.removeView(content)
scrollView = HorizontalScrollView(requireContext()) scrollView = HorizontalScrollView(requireContext())
scrollView.setPadding(0,0,0, // scrollView.setPadding(0,0,0,
(requireContext().resources.displayMetrics.density * 150).toInt() // (requireContext().resources.displayMetrics.density * 150).toInt()
) // )
contentScrollView.setPadding(0,0,0,0) contentScrollView.setPadding(0,0,0,0)
scrollView.layoutParams = LinearLayout.LayoutParams( scrollView.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
@ -142,11 +147,13 @@ class DownloadLogFragment : Fragment() {
scrollView.addView(content) scrollView.addView(content)
scrollView.id = R.id.horizontalscroll_output scrollView.id = R.id.horizontalscroll_output
parent.addView(scrollView, 0) parent.addView(scrollView, 0)
updateAutoScrollState()
} }
} }
R.id.scroll_down -> { R.id.scroll_down -> {
m.isVisible = false m.isVisible = false
autoScroll = true
contentScrollView.fullScroll(View.FOCUS_DOWN) contentScrollView.fullScroll(View.FOCUS_DOWN)
} }
@ -171,9 +178,12 @@ class DownloadLogFragment : Fragment() {
} }
contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy -> contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy ->
if (sy < osy){ updateAutoScrollState()
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1) }
}
contentScrollView.setOnTouchListener { view, motionEvent ->
autoScroll = false
false
} }
@ -185,9 +195,11 @@ class DownloadLogFragment : Fragment() {
content.setText(logItem.content, TextView.BufferType.SPANNABLE) content.setText(logItem.content, TextView.BufferType.SPANNABLE)
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1) bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
} }
if (!bottomAppBar.menu.children.first { it.itemId == R.id.scroll_down }.isVisible){ if (autoScroll){
content.scrollTo(0, content.height) //content.scrollTo(0, content.height)
contentScrollView.fullScroll(View.FOCUS_DOWN) 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 { companion object {
private const val TAG = "DownloadLogActivity" private const val TAG = "DownloadLogActivity"
} }

View file

@ -81,8 +81,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
editor.putString("command_path", FileUtil.getDefaultCommandPath()) editor.putString("command_path", FileUtil.getDefaultCommandPath())
} }
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) || if (FileUtil.hasAllFilesAccess()) {
VERSION.SDK_INT < 30) {
accessAllFiles!!.isVisible = false accessAllFiles!!.isVisible = false
cacheDownloads!!.isEnabled = true cacheDownloads!!.isEnabled = true
}else{ }else{
@ -90,8 +89,6 @@ class FolderSettingsFragment : BaseSettingsFragment() {
cacheDownloads!!.isEnabled = false cacheDownloads!!.isEnabled = false
} }
editor.apply()
musicPath!!.summary = FileUtil.formatPath(preferences.getString("music_path", "")!!) musicPath!!.summary = FileUtil.formatPath(preferences.getString("music_path", "")!!)
musicPath!!.onPreferenceClickListener = musicPath!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {

View file

@ -261,20 +261,27 @@ object Extensions {
} }
} }
// fun String.appendLineToLog(line: String): String { fun String.appendLineToLog(line: String = ""): String {
// val lines = this.split("\n") val lines = this.lines().toMutableList()
// if (!lines.takeLast(3).contains(line)){ val finishingProgressLinesRegex = Pattern.compile("\\[download]\\h+(100%|[a-zA-Z])")
// //clean dublicate progress + add newline
// var newLine = line if (line.isNotBlank()) {
// if (newLine.contains("[download")) { var newLine = ""
// newLine = "[download]" + line.split("[download]").last() val newLines = line.lines()
// } if (!lines.takeLast(3).any { newLines.contains(it) }) {
// lines.addAll(newLines.dropLast(1))
// return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}" newLine = "\n${newLines.last()}"
// } }
//
// return this 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){ fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
if(!hideThumb){ if(!hideThumb){

View file

@ -400,4 +400,9 @@ object FileUtil {
} }
} }
fun hasAllFilesAccess() : Boolean {
if (Build.VERSION.SDK_INT < 30) return true
return Environment.isExternalStorageManager()
}
} }

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.Html import android.text.Html
@ -43,7 +42,6 @@ import java.lang.reflect.Type
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.regex.Pattern
class InfoUtil(private val context: Context) { class InfoUtil(private val context: Context) {
@ -168,6 +166,13 @@ class InfoUtil(private val context: Context) {
fun getYoutubeVideo(url: String): List<ResultItem>? { fun getYoutubeVideo(url: String): List<ResultItem>? {
val theURL = url.replace("\\?list.*".toRegex(), "") val theURL = url.replace("\\?list.*".toRegex(), "")
try{ 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 id = getIDFromYoutubeURL(theURL)
val res = genericRequest("$pipedURL/streams/$id") val res = genericRequest("$pipedURL/streams/$id")
if (res.length() == 0) { if (res.length() == 0) {
@ -678,7 +683,7 @@ class InfoUtil(private val context: Context) {
} }
val lang = sharedPreferences.getString("app_language", "en") val lang = sharedPreferences.getString("app_language", "en")
if (searchEngine == "ytsearch" || query.isYoutubeURL()) { 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)) { if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang" extractorArgs += ";lang=$lang"
} }
@ -1034,6 +1039,12 @@ class InfoUtil(private val context: Context) {
} }
} }
private fun MutableList<String>.addOption(vararg options: String) {
options.forEach {
this.add(it)
}
}
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest { fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
@ -1052,14 +1063,16 @@ class InfoUtil(private val context: Context) {
} }
} }
val metadataCommands = mutableListOf<String>()
if (downloadItem.playlistIndex != null && useItemURL) { 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 type = downloadItem.type
val downDir : File 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){ if (!sharedPreferences.getBoolean("cache_downloads", true) && canWrite){
downDir = File(FileUtil.formatPath(downloadItem.downloadPath)) downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
request.addOption("--no-quiet") request.addOption("--no-quiet")
@ -1162,18 +1175,14 @@ class InfoUtil(private val context: Context) {
} }
if(downloadItem.title.isNotBlank()){ 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()){ if (downloadItem.author.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30))) metadataCommands.addOption("--replace-in-metadata", "uploader", ".+", downloadItem.author.take(30))
} metadataCommands.addOption("--parse-metadata", "%(uploader)s:%(artist)s")
request.addOption("--parse-metadata", "uploader:^(?P<uploader>.*?)(?:(?= - Topic)|$)")
if (embedMetadata){
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
} }
if (downloadItem.downloadSections.isNotBlank()){ if (downloadItem.downloadSections.isNotBlank()){
@ -1196,7 +1205,7 @@ class InfoUtil(private val context: Context) {
} }
if (sharedPreferences.getBoolean("use_audio_quality", false)){ 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)){ if (sharedPreferences.getBoolean("write_description", false)){
@ -1204,7 +1213,7 @@ class InfoUtil(private val context: Context) {
} }
if (downloadItem.url.isYoutubeURL()) { if (downloadItem.url.isYoutubeURL()) {
var extractorArgs = "player_client=default,mediaconnect,android" var extractorArgs = "player_client=default,mediaconnect"
val lang = sharedPreferences.getString("app_language", "en") val lang = sharedPreferences.getString("app_language", "en")
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) { if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
extractorArgs += ";lang=$lang" extractorArgs += ";lang=$lang"
@ -1261,7 +1270,7 @@ class InfoUtil(private val context: Context) {
} }
request.addOption("-x") request.addOption("-x")
val formatSorting = StringBuilder("hasaud") val formatSorting = StringBuilder("hasaud,size")
if (aCodecPref.isNotBlank()){ if (aCodecPref.isNotBlank()){
formatSorting.append(",acodec:$aCodecPref") formatSorting.append(",acodec:$aCodecPref")
@ -1285,31 +1294,46 @@ class InfoUtil(private val context: Context) {
request.addOption("-P", downDir.absolutePath) request.addOption("-P", downDir.absolutePath)
request.addOption("-S", formatSorting.toString()) request.addOption("-S", formatSorting.toString())
metadataCommands.addOption("--parse-metadata", """%(uploader,artist,channel,creator|null)s:^(?P<uploader>.*?)(?:(?= - Topic)|$)""")
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters") request.addOption("--split-chapters")
request.addOption("-o", "chapter:%(section_title)s.%(ext)s") request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
}else{ }else{
if (embedMetadata){ if (embedMetadata){
request.addOption("--embed-metadata") metadataCommands.addOption("--embed-metadata")
request.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)")
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})") val emptyAuthor = downloadItem.author.isEmpty()
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\\d+)") val usePlaylistMetadata = sharedPreferences.getBoolean("playlist_as_album", true)
if (downloadItem.playlistTitle.isNotEmpty()) { if (emptyAuthor) {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") if (usePlaylistMetadata) {
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)") metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist,uploader|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
} else { }else{
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") metadataCommands.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}
}else{
if (usePlaylistMetadata) {
metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}else{
metadataCommands.addOption("--parse-metadata", "%(artist|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
}
} }
metadataCommands.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
metadataCommands.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\\d+)")
metadataCommands.addOption("--parse-metadata", "%(album_artist,first_artist|)s:%(album_artist)s")
metadataCommands.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
} }
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true) val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
if (downloadItem.audioPreferences.embedThumb){ if (downloadItem.audioPreferences.embedThumb){
request.addOption("--embed-thumbnail") metadataCommands.addOption("--embed-thumbnail")
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!) if (!request.hasOption("--convert-thumbnails")) metadataCommands.addOption("--convert-thumbnails", thumbnailFormat!!)
val thumbnailConfig = StringBuilder("") val thumbnailConfig = StringBuilder("")
val cropConfig = """-vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\""""" 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 { runCatching {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt") val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
config.writeText(thumbnailConfig.toString()) 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 -> { DownloadViewModel.Type.video -> {
metadataCommands.addOption("--parse-metadata", """%(uploader,channel,creator,artist|null)s:^(?P<uploader>.*?)(?:(?= - Topic)|$)""")
val supportedContainers = context.resources.getStringArray(R.array.video_containers) val supportedContainers = context.resources.getStringArray(R.array.video_containers)
if (downloadItem.videoPreferences.addChapters) { if (downloadItem.videoPreferences.addChapters) {
@ -1359,7 +1385,8 @@ class InfoUtil(private val context: Context) {
){ ){
cont = outputContainer cont = outputContainer
if (downloadItem.videoPreferences.recodeVideo) { val cantRecode = listOf("avi")
if (downloadItem.videoPreferences.recodeVideo && !cantRecode.contains(cont)) {
request.addOption("--recode-video", outputContainer.lowercase()) request.addOption("--recode-video", outputContainer.lowercase())
}else{ }else{
request.addOption("--merge-output-format", outputContainer.lowercase()) 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())) { if (!listOf("webm", "avi", "flv").contains(outputContainer.lowercase())) {
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
if (embedThumb) { if (embedThumb) {
request.addOption("--embed-thumbnail") metadataCommands.addOption("--embed-thumbnail")
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!) 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 -> var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
val format = downloadItem.allFormats.find { it.format_id == f } val format = downloadItem.allFormats.find { it.format_id == f }
format?.run { format?.run {
println(format_id)
if (this.format_id.matches(".*-[0-9]+".toRegex())) { if (this.format_id.matches(".*-[0-9]+".toRegex())) {
if (!this.lang.isNullOrBlank() && this.lang != "None") { if (!this.lang.isNullOrBlank() && this.lang != "None") {
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]" "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 return request
} }

View file

@ -35,6 +35,7 @@ object NavbarUtil {
val pref = settings.getString("start_destination", "")!! val pref = settings.getString("start_destination", "")!!
val items = getDefaultNavBarItems(context) val items = getDefaultNavBarItems(context)
val navBarHasPref = getNavBarPrefs().contains(items.indexOfFirst { it.itemId == navItems[pref] }.toString()) val navBarHasPref = getNavBarPrefs().contains(items.indexOfFirst { it.itemId == navItems[pref] }.toString())
println("START FRAGMENT: $pref")
return if (pref == "") { return if (pref == "") {
R.id.homeFragment R.id.homeFragment
}else { }else {

View file

@ -20,6 +20,7 @@ import androidx.core.content.FileProvider
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.navigation.NavDeepLinkBuilder import androidx.navigation.NavDeepLinkBuilder
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
@ -148,11 +149,9 @@ class NotificationUtil(var context: Context) {
fun createDownloadServiceNotification( fun createDownloadServiceNotification(
pendingIntent: PendingIntent?, pendingIntent: PendingIntent?,
title: String?, title: String?,
itemID: Int,
): Notification { ): Notification {
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID) val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
return notificationBuilder return notificationBuilder
.setContentTitle(title) .setContentTitle(title)
.setOngoing(true) .setOngoing(true)

View file

@ -103,6 +103,7 @@ import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Locale import java.util.Locale
import java.util.Queue import java.util.Queue
import java.util.function.Predicate
object UiUtil { object UiUtil {
@ -862,14 +863,16 @@ object UiUtil {
true 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 { else {
formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id
formatIdParent?.setOnClickListener(clicker) formatIdParent?.setOnClickListener(clicker)
formatIdParent?.setOnLongClickListener(longClicker) formatIdParent?.setOnLongClickListener(longClicker)
} }
if (format.url.isNullOrBlank()) formatURLParent?.visibility = View.GONE if (format.url.doesntExist()) formatURLParent?.visibility = View.GONE
else { else {
formatURLParent?.findViewById<TextView>(R.id.format_url_value)?.text = format.url formatURLParent?.findViewById<TextView>(R.id.format_url_value)?.text = format.url
formatURLParent?.setOnClickListener(clicker) 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 { else {
containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container
containerParent?.setOnClickListener(clicker) containerParent?.setOnClickListener(clicker)
@ -893,7 +896,7 @@ object UiUtil {
format.acodec.uppercase() format.acodec.uppercase()
} }
if (codecField.isBlank()) codecParent?.visibility = View.GONE if (codecField.doesntExist()) codecParent?.visibility = View.GONE
else { else {
codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField
codecParent?.setOnClickListener(clicker) codecParent?.setOnClickListener(clicker)
@ -907,28 +910,28 @@ object UiUtil {
filesizeParent?.setOnLongClickListener(longClicker) filesizeParent?.setOnLongClickListener(longClicker)
} }
if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE if (format.format_note.doesntExist()) formatnoteParent?.visibility = View.GONE
else { else {
formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note
formatnoteParent?.setOnClickListener(clicker) formatnoteParent?.setOnClickListener(clicker)
formatnoteParent?.setOnLongClickListener(longClicker) 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 { else {
fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps
fpsParent?.setOnClickListener(clicker) fpsParent?.setOnClickListener(clicker)
fpsParent?.setOnLongClickListener(longClicker) fpsParent?.setOnLongClickListener(longClicker)
} }
if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE if (format.asr.doesntExist()) asrParent?.visibility = View.GONE
else { else {
asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr
asrParent?.setOnClickListener(clicker) asrParent?.setOnClickListener(clicker)
asrParent?.setOnLongClickListener(longClicker) asrParent?.setOnLongClickListener(longClicker)
} }
if (format.tbr.isNullOrBlank()) bitrateParent?.visibility = View.GONE if (format.tbr.doesntExist()) bitrateParent?.visibility = View.GONE
else{ else{
bitrateParent?.findViewById<TextView>(R.id.bitrate_value)?.text = format.tbr bitrateParent?.findViewById<TextView>(R.id.bitrate_value)?.text = format.tbr
bitrateParent?.setOnClickListener(clicker) bitrateParent?.setOnClickListener(clicker)
@ -990,7 +993,21 @@ object UiUtil {
val chips = mutableListOf<Chip>() val chips = mutableListOf<Chip>()
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s -> context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip 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.tag = s
tmp.id = index tmp.id = index
@ -1564,24 +1581,20 @@ object UiUtil {
} }
fun handleNoResults(context: Activity, message: String, continueAnyway: Boolean = false, continued: () -> Unit, closed: () -> Unit) {
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState, closed: () -> Unit){
val title = context.getString(it.errorMessage!!.first)
val message = it.errorMessage!!.second
val errDialog = MaterialAlertDialogBuilder(context) val errDialog = MaterialAlertDialogBuilder(context)
.setTitle(title) .setTitle(R.string.no_results)
.setMessage(message) .setMessage(message)
for (a in it.actions!!){ errDialog.setPositiveButton(R.string.copy_log) { d:DialogInterface?, _:Int ->
when(a.second){ val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
ResultViewModel.ResultAction.COPY_LOG -> { clipboard.setText(message)
errDialog.setPositiveButton(a.first) { d:DialogInterface?, _:Int -> d?.dismiss()
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()
} }
} }

View file

@ -1,9 +1,12 @@
package com.deniscerri.ytdl.work package com.deniscerri.ytdl.work
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -20,6 +23,7 @@ import androidx.work.await
import androidx.work.workDataOf import androidx.work.workDataOf
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.App import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.DBManager import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.models.HistoryItem import com.deniscerri.ytdl.database.models.HistoryItem
@ -73,15 +77,28 @@ class DownloadWorker(
//val createResultItem = inputData.getBoolean("createResultItem", true) //val createResultItem = inputData.getBoolean("createResultItem", true)
val confTmp = Configuration(context.resources.configuration) val confTmp = Configuration(context.resources.configuration)
val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-") val locale = if (Build.VERSION.SDK_INT < 33) {
confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1])) 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 metrics = DisplayMetrics()
val resources = Resources(context.assets, metrics, confTmp) val resources = Resources(context.assets, metrics, confTmp)
val pendingIntent = NavDeepLinkBuilder(context) val openQueueIntent = Intent(context, MainActivity::class.java)
.setGraph(R.navigation.nav_graph) openQueueIntent.setAction(Intent.ACTION_VIEW)
.setDestination(R.id.downloadQueueMainFragment) openQueueIntent.putExtra("destination", "Queue")
.createPendingIntent() val openDownloadQueue = PendingIntent.getActivity(
context,
1000000000,
openQueueIntent,
PendingIntent.FLAG_IMMUTABLE
)
val workNotif = notificationUtil.createDefaultWorkerNotification() val workNotif = notificationUtil.createDefaultWorkerNotification()
val foregroundInfo = ForegroundInfo(1000000000, workNotif) 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 } val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
eligibleDownloads.forEach{downloadItem -> 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) notificationUtil.notify(downloadItem.id.toInt(), notification)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
@ -189,7 +206,7 @@ class DownloadWorker(
} }
//val wasQuickDownloaded = resultDao.getCountInt() == 0 //val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking { runBlocking {
var finalPaths : MutableList<String>? var finalPaths = mutableListOf<String>()
if (noCache){ if (noCache){
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id)) eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id))
@ -212,9 +229,6 @@ class DownloadWorker(
finalPaths.sortBy { File(it).lastModified() } finalPaths.sortBy { File(it).lastModified() }
finalPaths = finalPaths.distinct().toMutableList() finalPaths = finalPaths.distinct().toMutableList()
FileUtil.scanMedia(finalPaths, context) FileUtil.scanMedia(finalPaths, context)
if (finalPaths.isEmpty()){
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
}
}else{ }else{
//move file from internal to set download directory //move file from internal to set download directory
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id)) eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
@ -227,11 +241,8 @@ class DownloadWorker(
if (finalPaths.isNotEmpty()){ if (finalPaths.isNotEmpty()){
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id)) eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
}else{
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
} }
}catch (e: Exception){ }catch (e: Exception){
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
e.printStackTrace() e.printStackTrace()
handler.postDelayed({ handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
@ -246,51 +257,52 @@ class DownloadWorker(
add("description") add("description")
add("txt") 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) FileUtil.deleteConfigFiles(request)
//put download in history //put download in history
if (!downloadItem.incognito) { 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({ handler.postDelayed({
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}, 100) }, 100)
}else{ }else{
val unixTime = System.currentTimeMillis() / 1000 val unixTime = System.currentTimeMillis() / 1000
finalPaths?.apply { finalPaths.first().apply {
this.first().apply { val file = File(this)
val file = File(this) var duration = downloadItem.duration
var duration = downloadItem.duration val d = file.getMediaDuration(context)
val d = file.getMediaDuration(context) if (d > 0) duration = d.toStringDuration(Locale.US)
if (d > 0) duration = d.toStringDuration(Locale.US)
downloadItem.format.filesize = file.length() downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension downloadItem.format.container = file.extension
downloadItem.duration = duration 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)
} }
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.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished( 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){ // if (wasQuickDownloaded && createResultItem){

View file

@ -49,7 +49,7 @@ class TerminalDownloadWorker(
val intent = Intent(context, TerminalActivity::class.java) val intent = Intent(context, TerminalActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) 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) val foregroundInfo = ForegroundInfo(itemId, notification)
setForegroundAsync(foregroundInfo) setForegroundAsync(foregroundInfo)

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:colorAccent" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,16.5c-2.49,0 -4.5,-2.01 -4.5,-4.5S9.51,7.5 12,7.5s4.5,2.01 4.5,4.5 -2.01,4.5 -4.5,4.5zM12,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1 1,-0.45 1,-1 -0.45,-1 -1,-1z"/>
</vector>

View file

@ -8,5 +8,5 @@
android:bottom="1dp" android:bottom="1dp"
android:top="1dp" /> android:top="1dp" />
<corners android:radius="3dp" /> <corners android:radius="5dp" />
</shape> </shape>

View file

@ -34,7 +34,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:layout_height="80dp"> android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/downloads_image_view" android:id="@+id/downloads_image_view"
@ -49,13 +49,12 @@
app:layout_constraintEnd_toStartOf="@+id/download_item_data" app:layout_constraintEnd_toStartOf="@+id/download_item_data"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" /> app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/download_item_data" android:id="@+id/download_item_data"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/options" app:layout_constraintEnd_toStartOf="@+id/options"
app:layout_constraintStart_toEndOf="@+id/downloads_image_view" app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -66,7 +65,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2" android:maxLines="1"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:scrollbars="none" android:scrollbars="none"
android:textSize="15sp" android:textSize="15sp"
@ -76,11 +75,11 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<HorizontalScrollView <HorizontalScrollView
android:id="@+id/horizontalScrollView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="bottom" android:gravity="bottom"
android:scrollbars="none" android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -90,27 +89,30 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/download_type" android:id="@+id/download_type"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="5dp" android:layout_marginHorizontal="5dp"
android:background="@drawable/rounded_corner" android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorPrimaryContainer" android:backgroundTint="?attr/colorSecondary"
android:clickable="false" android:clickable="false"
android:ellipsize="end"
android:gravity="center" android:gravity="center"
android:maxLength="17"
android:minWidth="30dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold" android:textStyle="bold"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:drawableStartCompat="@drawable/ic_music_formatcard" app:drawableStartCompat="@drawable/ic_music_formatcard"
app:drawableTint="@color/white" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" /> app:layout_constraintVertical_bias="0.0" />
<TextView <TextView
android:id="@+id/file_size" android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary" style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
@ -123,7 +125,6 @@
android:gravity="center" android:gravity="center"
android:minWidth="30dp" android:minWidth="30dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold" android:textStyle="bold"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -142,10 +143,8 @@
android:ellipsize="end" android:ellipsize="end"
android:gravity="center" android:gravity="center"
android:maxLength="17" android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp" android:minWidth="30dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold" android:textStyle="bold"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -161,10 +160,11 @@
android:background="@drawable/rounded_corner" android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorSecondary" android:backgroundTint="?attr/colorSecondary"
android:clickable="false" android:clickable="false"
android:ellipsize="end"
android:gravity="center" android:gravity="center"
android:maxLength="17"
android:minWidth="30dp" android:minWidth="30dp"
android:paddingHorizontal="5dp" android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold" android:textStyle="bold"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -172,12 +172,24 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> </LinearLayout>
</HorizontalScrollView> </HorizontalScrollView>
<TextView
android:id="@+id/output"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:maxLines="2"
android:ellipsize="end"
android:layout_margin="5dp"
android:textSize="13sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/horizontalScrollView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -38,7 +38,7 @@
app:layout_constraintHorizontal_weight="0.3" app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" /> app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/incognitoLabel" android:id="@+id/incognitoLabel"

View file

@ -120,7 +120,6 @@
android:textStyle="bold" android:textStyle="bold"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:drawableStartCompat="@drawable/ic_music_formatcard" app:drawableStartCompat="@drawable/ic_music_formatcard"
app:drawableTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -173,6 +172,7 @@
<TextView <TextView
android:id="@+id/format_id" android:id="@+id/format_id"
android:layout_width="0dp" android:layout_width="0dp"
android:minWidth="70dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"

View file

@ -1,31 +1,33 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
tools:context="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:id="@+id/appbarlayout"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:liftOnScroll="false"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/title" android:id="@+id/title"
android:elevation="0dp"
android:layout_width="match_parent" android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back" android:layout_height="match_parent"
android:elevation="0dp"
app:navigationContentDescription="@string/back" app:navigationContentDescription="@string/back"
android:layout_height="match_parent"/> app:navigationIcon="@drawable/ic_back" />
<com.google.android.material.slider.Slider <com.google.android.material.slider.Slider
android:visibility="gone"
android:id="@+id/textsize_seekbar" android:id="@+id/textsize_seekbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbar" /> app:layout_constraintTop_toBottomOf="@+id/appbar" />
@ -34,34 +36,45 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<FrameLayout <FrameLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/frameLayout"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent"> 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">
<me.zhanghai.android.fastscroll.FastScrollScrollView <me.zhanghai.android.fastscroll.FastScrollScrollView
android:id="@+id/content_scrollview" android:id="@+id/content_scrollview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false">
>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="150dp"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:orientation="vertical"> android:orientation="vertical">
<TextView <HorizontalScrollView
android:id="@+id/content"
android:padding="10dp"
android:fontFamily="monospace"
android:textSize="15sp"
android:gravity="bottom"
android:scrollbars="none"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:textIsSelectable="true" android:id="@+id/horizontalscroll_output"
android:layout_height="wrap_content" /> android:layout_height="wrap_content">
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:gravity="bottom"
android:padding="10dp"
android:scrollbars="none"
android:textIsSelectable="true"
android:textSize="15sp" />
</HorizontalScrollView>
</LinearLayout> </LinearLayout>
@ -69,27 +82,41 @@
</FrameLayout> </FrameLayout>
<include layout="@layout/no_results" <androidx.coordinatorlayout.widget.CoordinatorLayout
android:visibility="gone" /> android:id="@+id/coordinatorLayout3"
android:layout_width="0dp"
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.Material3.BottomAppBar" app:layout_constraintEnd_toEndOf="parent"
app:menu="@menu/log_detail_menu" app:layout_constraintStart_toStartOf="parent">
/>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/copy_log" android:id="@+id/bottomAppBar"
android:layout_width="wrap_content" style="@style/Widget.Material3.BottomAppBar"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:contentDescription="@string/copy_log" android:layout_height="wrap_content"
android:text="@string/copy_log" android:layout_gravity="bottom"
app:layout_anchor="@id/bottomAppBar" app:layout_constraintBottom_toBottomOf="@+id/frameLayout"
app:icon="@drawable/ic_copy"/> app:layout_constraintEnd_toEndOf="parent"
app:menu="@menu/log_detail_menu" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/copy_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/copy_log"
android:text="@string/copy_log"
app:icon="@drawable/ic_copy"
app:layout_anchor="@id/bottomAppBar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> <include
layout="@layout/no_results"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/appbarlayout"
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout3"
app:layout_constraintEnd_toEndOf="@+id/frameLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -67,7 +67,7 @@
app:layout_constraintHorizontal_weight="0.3" app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" /> app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<TextView <TextView

View file

@ -101,9 +101,12 @@
android:id="@+id/download_music" android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp" android:layout_width="55dp"
app:elevation="0dp"
android:elevation="0dp"
app:borderWidth="0dp"
android:contentDescription="@string/audio" android:contentDescription="@string/audio"
android:layout_height="55dp" android:layout_height="55dp"
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="5dp"
app:icon="@drawable/ic_music" app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video" app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -113,8 +116,9 @@
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp" android:layout_width="55dp"
android:layout_height="55dp" android:layout_height="55dp"
app:elevation="0dp"
android:contentDescription="@string/video" android:contentDescription="@string/video"
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="5dp"
app:icon="@drawable/ic_video" app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View file

@ -3,19 +3,19 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" > tools:context=".MainActivity" >
<group> <!-- <group>-->
<item <!-- <item-->
android:id="@+id/pause" <!-- android:id="@+id/pause"-->
android:title="@string/pause" <!-- android:title="@string/pause"-->
android:icon="@drawable/exomedia_ic_pause_white" <!-- android:icon="@drawable/exomedia_ic_pause_white"-->
app:showAsAction="ifRoom" /> <!-- app:showAsAction="ifRoom" />-->
<item <!-- <item-->
android:id="@+id/resume" <!-- android:id="@+id/resume"-->
android:title="@string/resume" <!-- android:title="@string/resume"-->
android:icon="@drawable/exomedia_ic_play_arrow_white" <!-- android:icon="@drawable/exomedia_ic_play_arrow_white"-->
app:showAsAction="ifRoom" /> <!-- app:showAsAction="ifRoom" />-->
</group> <!-- </group>-->
<item <item
android:id="@+id/cancel" android:id="@+id/cancel"

View file

@ -12,7 +12,6 @@
<item <item
android:id="@+id/scroll_down" android:id="@+id/scroll_down"
android:title="@string/scroll_down" android:title="@string/scroll_down"
android:visible="false"
android:icon="@drawable/ic_down" android:icon="@drawable/ic_down"
app:showAsAction="always" /> app:showAsAction="always" />

View file

@ -1029,13 +1029,16 @@
<item>en-IN</item>, <item>en-IN</item>,
<item>en-GB</item>, <item>en-GB</item>,
<item>en</item>, <item>en</item>,
<item>en.*</item>,
<item>es</item>, <item>es</item>,
<item>es-419</item>, <item>es-419</item>,
<item>es-US</item>, <item>es-US</item>,
<item>es.*</item>,
<item>eu</item>, <item>eu</item>,
<item>fil</item>, <item>fil</item>,
<item>fr</item>, <item>fr</item>,
<item>fr-CA</item>, <item>fr-CA</item>,
<item>fr.*</item>,
<item>gl</item>, <item>gl</item>,
<item>hr</item>, <item>hr</item>,
<item>zu</item>, <item>zu</item>,
@ -1051,6 +1054,7 @@
<item>pl</item>, <item>pl</item>,
<item>pt-PT</item>, <item>pt-PT</item>,
<item>pt</item>, <item>pt</item>,
<item>pt.*</item>,
<item>ro</item>, <item>ro</item>,
<item>sq</item>, <item>sq</item>,
<item>sk</item>, <item>sk</item>,
@ -1097,6 +1101,7 @@
<item>zh-CN</item>, <item>zh-CN</item>,
<item>zh-TW</item>, <item>zh-TW</item>,
<item>zh-HK</item>, <item>zh-HK</item>,
<item>zh.*</item>,
<item>ja</item>, <item>ja</item>,
<item>ko</item> <item>ko</item>
</string-array> </string-array>

View file

@ -418,4 +418,7 @@
<string name="weekly">Weekly</string> <string name="weekly">Weekly</string>
<string name="monthly">Monthly</string> <string name="monthly">Monthly</string>
<string name="cleanup_leftover_downloads">Clean-up leftover downloads (cancelled, errored)</string> <string name="cleanup_leftover_downloads">Clean-up leftover downloads (cancelled, errored)</string>
<string name="continue_anyway">Continue Anyway</string>
<string name="playlist_as_album">Use Playlist Name as Album Metadata</string>
<string name="playlist_as_album_summary">If Album Metadata is not present, use playlist name instead</string>
</resources> </resources>

View file

@ -32,6 +32,11 @@
<item name="cornerSize">5dp</item> <item name="cornerSize">5dp</item>
</style> </style>
<style name="ShapeAppearanceOverlay.Avatar2" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">7dp</item>
</style>
<style name="ShapeAppearanceOverlay.Chip.Rounded" parent="ShapeAppearance.MaterialComponents.SmallComponent"> <style name="ShapeAppearanceOverlay.Chip.Rounded" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
<item name="cornerSizeTopLeft">12dp</item> <item name="cornerSizeTopLeft">12dp</item>

View file

@ -90,6 +90,14 @@
app:summary="@string/crop_thumb_summary" app:summary="@string/crop_thumb_summary"
app:title="@string/crop_thumb" /> app:title="@string/crop_thumb" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/baseline_album_24"
app:key="playlist_as_album"
app:summary="@string/playlist_as_album_summary"
app:title="@string/playlist_as_album" />
</PreferenceCategory> </PreferenceCategory>