1.7.9.1 prep
This commit is contained in:
parent
c40095ac7f
commit
087ba0781a
38 changed files with 642 additions and 369 deletions
|
|
@ -201,4 +201,5 @@ dependencies {
|
|||
implementation 'androidx.compose.material3:material3-android:1.2.1'
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
implementation("org.greenrobot:eventbus:3.3.1")
|
||||
//implementation("com.github.TeamNewPipe:NewPipeExtractor:0.24.2")
|
||||
}
|
||||
|
|
|
|||
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
|
|
@ -88,5 +88,11 @@
|
|||
static <1>$Companion Companion;
|
||||
}
|
||||
|
||||
### Rules for NewPipeExtractor
|
||||
#-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||
#-keep class org.mozilla.javascript.** { *; }
|
||||
#-keep class org.mozilla.classfile.ClassFileWriter
|
||||
#-dontwarn org.mozilla.javascript.tools.**
|
||||
|
||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
||||
-dontobfuscate
|
||||
|
|
|
|||
|
|
@ -80,24 +80,46 @@
|
|||
</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"
|
||||
android:host="youtube.com" />
|
||||
<data android:scheme="https" android:host="youtube.com" />
|
||||
<data android:host="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.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:host="twitter.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.twitter.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
@ -114,7 +136,7 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="soundcloud.com" />
|
||||
<data android:host="www.x.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
|
@ -122,162 +144,177 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<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:host="facebook.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.facebook.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="fb.watch" />
|
||||
</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="instagram.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.instagram.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="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="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:host="bilibili.tv" />
|
||||
</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" />
|
||||
<data android:host="www.bilibili.tv" />
|
||||
</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="twitch.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="reddit.com" />
|
||||
<data android:host="www.twitch.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.reddit.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="reddit.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="m.reddit.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="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: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:host="music.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="m.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="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: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:host="rutube.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.rutube.com" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdl
|
|||
|
||||
import android.Manifest
|
||||
import android.app.ActionBar.LayoutParams
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
|
|
@ -32,6 +33,7 @@ import androidx.fragment.app.FragmentContainerView
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
|
|
@ -290,9 +292,6 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||
super.onSaveInstanceState(savedInstanceState)
|
||||
|
|
@ -442,6 +441,8 @@ class MainActivity : BaseActivity() {
|
|||
e.printStackTrace()
|
||||
}
|
||||
}else if (action == Intent.ACTION_VIEW){
|
||||
navController.popBackStack(navController.graph.startDestinationId, true)
|
||||
|
||||
when(intent.getStringExtra("destination")){
|
||||
"Downloads" -> {
|
||||
navController.navigate(R.id.historyFragment)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.room.Query
|
|||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import com.deniscerri.ytdl.database.models.LogItem
|
||||
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
|
|
@ -43,10 +44,10 @@ interface LogDao {
|
|||
suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) {
|
||||
val l = getByID(id) ?: return
|
||||
var log = l.content ?: ""
|
||||
if (resetLog) {
|
||||
log = line.lines().joinToString("\n")
|
||||
log = if (resetLog) {
|
||||
line.appendLineToLog()
|
||||
}else{
|
||||
log += "\n${line}"
|
||||
log.appendLineToLog(line)
|
||||
}
|
||||
updateLogContent(log, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.room.Insert
|
|||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import com.deniscerri.ytdl.database.models.TerminalItem
|
||||
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
|
|
@ -31,10 +32,10 @@ interface TerminalDao {
|
|||
suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){
|
||||
val t = getTerminalById(id) ?: return
|
||||
var log = t.log ?: ""
|
||||
if (resetLog) {
|
||||
log = line.lines().joinToString("\n")
|
||||
log = if (resetLog) {
|
||||
line.lines().joinToString("\n")
|
||||
}else{
|
||||
log += "\n${line}"
|
||||
log.appendLineToLog(line)
|
||||
}
|
||||
updateTerminalLog(log, id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
|||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Parcelable
|
||||
|
|
@ -148,7 +149,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
|
||||
val locale = if (Build.VERSION.SDK_INT < 33) {
|
||||
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
|
||||
}else{
|
||||
Locale.getDefault().language
|
||||
}.run {
|
||||
split("-")
|
||||
}.run {
|
||||
if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
|
||||
}
|
||||
confTmp.setLocale(locale)
|
||||
val metrics = DisplayMetrics()
|
||||
resources = Resources(application.assets, metrics, confTmp)
|
||||
}
|
||||
|
|
@ -245,6 +255,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
|
||||
val preferredAudioFormats = getPreferredAudioFormats(resultItem.formats)
|
||||
val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
|
||||
|
||||
val videoPreferences = VideoPreferences(
|
||||
embedSubs,
|
||||
|
|
@ -252,6 +263,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
ArrayList(sponsorblock),
|
||||
saveSubs,
|
||||
saveAutoSubs,
|
||||
subsLanguages,
|
||||
audioFormatIDs = preferredAudioFormats,
|
||||
recodeVideo = recodeVideo
|
||||
)
|
||||
|
|
@ -354,8 +366,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val format = getFormat(it.allFormats, type)
|
||||
it.format = format
|
||||
|
||||
var updatedDownloadPath: String = ""
|
||||
var container: String = ""
|
||||
var updatedDownloadPath = ""
|
||||
var container = ""
|
||||
|
||||
when(type){
|
||||
Type.audio -> {
|
||||
|
|
@ -363,11 +375,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
container = sharedPreferences.getString("audio_format", "")!!
|
||||
}
|
||||
Type.video -> {
|
||||
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
|
||||
container = sharedPreferences.getString("audio_format", "")!!
|
||||
updatedDownloadPath = sharedPreferences.getString("video_path", FileUtil.getDefaultVideoPath())!!
|
||||
container = sharedPreferences.getString("video_format", "")!!
|
||||
}
|
||||
Type.command -> {
|
||||
updatedDownloadPath = sharedPreferences.getString("music_path", FileUtil.getDefaultAudioPath())!!
|
||||
updatedDownloadPath = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())!!
|
||||
container = ""
|
||||
}
|
||||
else -> {
|
||||
|
|
@ -391,6 +403,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
|
||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
|
||||
val subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!!
|
||||
|
||||
val customFileNameTemplate = when(historyItem.type) {
|
||||
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
|
||||
|
|
@ -420,7 +433,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
|
||||
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, recodeVideo = recodeVideo)
|
||||
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs, subsLanguages, recodeVideo = recodeVideo)
|
||||
var path = defaultPath
|
||||
historyItem.downloadPath.first().apply {
|
||||
File(this).parent?.apply {
|
||||
|
|
@ -930,10 +943,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}else{
|
||||
val queued = repository.updateAll(queuedItems)
|
||||
|
||||
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
|
||||
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
|
||||
if (sharedPreferences.getBoolean("paused_downloads", true)) {
|
||||
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
resetActiveToQueued()
|
||||
}
|
||||
|
||||
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
|
||||
|
||||
if(!useScheduler){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
queued.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||
|
|
|
|||
|
|
@ -22,11 +22,16 @@ import com.deniscerri.ytdl.database.repository.SearchHistoryRepository
|
|||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDLException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
|
||||
|
|
@ -40,18 +45,13 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
private val dao: ResultDao
|
||||
data class ResultsUiState(
|
||||
var processing: Boolean,
|
||||
var errorMessage: Pair<Int, String>?,
|
||||
var actions: MutableList<Pair<Int, ResultAction>>?
|
||||
var errorMessage: String?,
|
||||
var canContinueOnError: Boolean = false
|
||||
)
|
||||
|
||||
enum class ResultAction {
|
||||
COPY_LOG
|
||||
}
|
||||
|
||||
val uiState: MutableStateFlow<ResultsUiState> = MutableStateFlow(ResultsUiState(
|
||||
processing = false,
|
||||
errorMessage = null,
|
||||
actions = null
|
||||
))
|
||||
|
||||
|
||||
|
|
@ -63,7 +63,8 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(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
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
|
||||
fun cancelParsingQueries(){
|
||||
parsingQueries?.cancel(CancellationException())
|
||||
parsingQueriesJobList.forEach { it.cancel(CancellationException()) }
|
||||
uiState.update {it.copy(processing = false)}
|
||||
}
|
||||
|
||||
|
|
@ -110,23 +112,29 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
}
|
||||
val resetResults = inputQueries.size == 1
|
||||
|
||||
uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
|
||||
uiState.update {it.copy(processing = true, errorMessage = null)}
|
||||
val res = mutableListOf<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){
|
||||
notificationUtil.showQueriesFinished()
|
||||
}
|
||||
|
|
@ -279,17 +287,12 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
uiState.update {
|
||||
it.copy(
|
||||
processing = false,
|
||||
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
||||
actions = mutableListOf(
|
||||
Pair(
|
||||
R.string.copy_log,
|
||||
ResultAction.COPY_LOG
|
||||
)
|
||||
)
|
||||
errorMessage = e.message.toString(),
|
||||
)
|
||||
}
|
||||
Log.e(tag, e.toString())
|
||||
}
|
||||
updatingFormats.emit(false)
|
||||
}
|
||||
}
|
||||
updateFormatsResultDataJob?.start()
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
private var appBarLayout: AppBarLayout? = null
|
||||
private var materialToolbar: MaterialToolbar? = null
|
||||
private var loadingItems: Boolean = false
|
||||
private var queryList = mutableListOf<String>()
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
|
|
@ -255,8 +256,28 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
launch{
|
||||
resultViewModel.uiState.collectLatest { res ->
|
||||
if (res.errorMessage != null){
|
||||
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed ={}) }
|
||||
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
|
||||
val isSingleQueryAndURL = queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()
|
||||
|
||||
kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, continueAnyway = isSingleQueryAndURL, continued = {
|
||||
lifecycleScope.launch {
|
||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||
withContext(Dispatchers.Main){
|
||||
showSingleDownloadSheet(
|
||||
resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),
|
||||
type = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!),
|
||||
disableUpdateData = true
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val downloadItem = downloadViewModel.createDownloadItemFromResult(
|
||||
result = downloadViewModel.createEmptyResultItem(queryList.first()),
|
||||
givenType = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
|
||||
)
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
}
|
||||
}, closed = {}) }
|
||||
resultViewModel.uiState.update {it.copy(errorMessage = null) }
|
||||
}
|
||||
|
||||
loadingItems = res.processing
|
||||
|
|
@ -270,9 +291,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
shimmerCards!!.stopShimmer()
|
||||
shimmerCards!!.visibility = GONE
|
||||
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
|
||||
this@HomeFragment.downloadAllFab!!.visibility = VISIBLE
|
||||
downloadAllFab!!.visibility = VISIBLE
|
||||
}else{
|
||||
this@HomeFragment.downloadAllFab!!.visibility = GONE
|
||||
downloadAllFab!!.visibility = GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -602,7 +623,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
|
||||
private fun initSearch(searchView: SearchView){
|
||||
|
||||
val queryList = mutableListOf<String>()
|
||||
queryList = mutableListOf()
|
||||
if (queriesChipGroup!!.childCount > 0){
|
||||
queriesChipGroup!!.children.forEach {
|
||||
val query = (it as Chip).text.toString().trim {it2 -> it2 <= ' '}
|
||||
|
|
@ -689,7 +710,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
@SuppressLint("RestrictedApi")
|
||||
private fun showSingleDownloadSheet(
|
||||
resultItem: ResultItem,
|
||||
type: DownloadViewModel.Type
|
||||
type: DownloadViewModel.Type,
|
||||
disableUpdateData : Boolean = false
|
||||
){
|
||||
if(findNavController().currentBackStack.value.firstOrNull {it.destination.id == R.id.downloadBottomSheetDialog} == null &&
|
||||
findNavController().currentDestination?.id == R.id.homeFragment
|
||||
|
|
@ -698,6 +720,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
val bundle = Bundle()
|
||||
bundle.putParcelable("result", resultItem)
|
||||
bundle.putSerializable("type", downloadViewModel.getDownloadType(type, resultItem.url))
|
||||
if (disableUpdateData) {
|
||||
bundle.putBoolean("disableUpdateData", true)
|
||||
}
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundle)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
progressBar.progress = 0
|
||||
progressBar.isIndeterminate = true
|
||||
|
||||
//OUTPUT ------------------------------------------------------------
|
||||
val output = card.findViewById<TextView>(R.id.output)
|
||||
output.tag = "${item.id}##output"
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
|
||||
|
|
@ -131,8 +135,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
|
||||
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
|
||||
|
||||
val pause = popup.menu[0]
|
||||
val resume = popup.menu[1]
|
||||
// val pause = popup.menu[0]
|
||||
// val resume = popup.menu[1]
|
||||
//
|
||||
// if (paused){
|
||||
// pause.isVisible = false
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
|
||||
private lateinit var result: ResultItem
|
||||
private lateinit var type: Type
|
||||
private var disableUpdateData : Boolean = false
|
||||
private var currentDownloadItem: DownloadItem? = null
|
||||
private var incognito: Boolean = false
|
||||
|
||||
|
|
@ -108,6 +109,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
dwl = arguments?.getParcelable<DownloadItem>("downloadItem")
|
||||
}
|
||||
type = arguments?.getSerializable("type") as Type
|
||||
disableUpdateData = arguments?.getBoolean("disableUpdateData") == true
|
||||
|
||||
if (res == null){
|
||||
dismiss()
|
||||
|
|
@ -429,13 +431,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
//if auto-update after the card is open is off
|
||||
if (result.title.isEmpty() && currentDownloadItem == null) {
|
||||
if(sharedPreferences.getBoolean("quick_download", false)) {
|
||||
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
|
||||
updateItem.setOnClickListener {
|
||||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
initUpdateData()
|
||||
}
|
||||
if (result.title.isEmpty() && currentDownloadItem == null && sharedPreferences.getBoolean("quick_download", false)) {
|
||||
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
|
||||
updateItem.setOnClickListener {
|
||||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
initUpdateData()
|
||||
}
|
||||
}else{
|
||||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
|
|
@ -463,24 +463,28 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
|
||||
|
||||
//update in the background if there is no data
|
||||
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
|
||||
initUpdateData()
|
||||
}else {
|
||||
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
|
||||
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
|
||||
initUpdateFormats(result)
|
||||
if (!disableUpdateData) {
|
||||
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
|
||||
initUpdateData()
|
||||
}else {
|
||||
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
|
||||
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
|
||||
initUpdateFormats(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
resultViewModel.uiState.collectLatest { res ->
|
||||
if (res.errorMessage != null){
|
||||
kotlin.runCatching { UiUtil.handleResultResponse(requireActivity(), res, closed = {
|
||||
kotlin.runCatching { UiUtil.handleNoResults(requireActivity(), res.errorMessage!!, true, continued = {}, closed = {
|
||||
dismiss()
|
||||
}) }
|
||||
resultViewModel.uiState.update {it.copy(errorMessage = null, actions = null) }
|
||||
resultViewModel.uiState.update {it.copy(errorMessage = null) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -774,6 +774,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
val fileSizes = mutableListOf<Long>()
|
||||
items.forEach {
|
||||
if (it.type == DownloadViewModel.Type.video){
|
||||
println(it.format.filesize)
|
||||
if (it.format.filesize <= 5L) {
|
||||
fileSizes.add(0)
|
||||
}else{
|
||||
|
|
@ -787,6 +788,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
fileSizes.add(it.format.filesize + audioFormatSize)
|
||||
}
|
||||
}else if (it.type == DownloadViewModel.Type.audio){
|
||||
fileSizes.add(it.format.filesize)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class FormatSelectionBottomSheetDialog(
|
|||
return
|
||||
}
|
||||
|
||||
items = _items.distinctBy { it!!.url }.toMutableList()
|
||||
items = _items.toMutableList()
|
||||
if (items.size == 1) {
|
||||
formats = items.first()!!.allFormats
|
||||
}else{
|
||||
|
|
@ -192,8 +192,10 @@ class FormatSelectionBottomSheetDialog(
|
|||
|
||||
refreshBtn.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
val itemsThatHaveFormats = items.filter { it!!.allFormats.isNotEmpty() }
|
||||
val itemsWithMissingFormats = items.filter { it!!.allFormats.isEmpty() }.ifEmpty { items }
|
||||
val distinctItems = items.distinctBy { it!!.url }
|
||||
|
||||
val itemsThatHaveFormats = distinctItems.filter { it!!.allFormats.isNotEmpty() }
|
||||
val itemsWithMissingFormats = distinctItems.filter { it!!.allFormats.isEmpty() }.ifEmpty { distinctItems }
|
||||
|
||||
if (itemsWithMissingFormats.size > 10){
|
||||
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
||||
|
|
@ -238,11 +240,7 @@ class FormatSelectionBottomSheetDialog(
|
|||
}
|
||||
}.onFailure { err ->
|
||||
withContext(Dispatchers.Main){
|
||||
UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState(
|
||||
false,
|
||||
Pair(R.string.no_results, err.message.toString()),
|
||||
mutableListOf(Pair(R.string.copy_log, ResultViewModel.ResultAction.COPY_LOG))
|
||||
), closed = {})
|
||||
UiUtil.handleNoResults(requireActivity(), err.message.toString(), false, continued = {}, closed = {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,9 +268,9 @@ class FormatSelectionBottomSheetDialog(
|
|||
}
|
||||
}else{
|
||||
multipleFormatsListener.onFormatUpdated(it.url, it.formats)
|
||||
items.firstOrNull { item -> item!!.url == it.url }?.apply {
|
||||
allFormats.clear()
|
||||
allFormats.addAll(it.formats)
|
||||
items.filter { item -> item!!.url == it.url }.forEach { d ->
|
||||
d?.allFormats?.clear()
|
||||
d?.allFormats?.addAll(it.formats)
|
||||
}
|
||||
progressInt++
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
|
|
@ -290,7 +288,7 @@ class FormatSelectionBottomSheetDialog(
|
|||
val flatFormatCollection = formatCollection.flatten()
|
||||
val commonFormats =
|
||||
flatFormatCollection.groupingBy { it.format_id }.eachCount()
|
||||
.filter { it.value == items.size }
|
||||
.filter { it.value == distinctItems.size }
|
||||
.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
|
||||
.map { it.value }
|
||||
formats.addAll(commonFormats)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import com.deniscerri.ytdl.util.InfoUtil
|
|||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.VideoPlayerUtil
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
|
@ -72,6 +73,9 @@ import kotlinx.coroutines.flow.map
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
|
@ -86,8 +90,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
|
||||
private lateinit var activeAdapter: ActiveDownloadMinifiedAdapter
|
||||
private lateinit var queuedAdapter: GenericDownloadAdapter
|
||||
private var activeCount: Int = 0
|
||||
|
||||
|
||||
private lateinit var downloadManager: DownloadManager
|
||||
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
|
@ -97,6 +100,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
infoUtil = InfoUtil(requireActivity())
|
||||
notificationUtil = NotificationUtil(requireActivity())
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
|
@ -237,12 +241,6 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||
activeCount = it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bottomSheetLink.text = item.url
|
||||
bottomSheetLink.setOnClickListener{
|
||||
|
|
@ -482,21 +480,42 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
|
||||
override fun onCancelClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
if (activeCount == 1){
|
||||
val queue = withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list
|
||||
}
|
||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
||||
val activeCount = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
}
|
||||
|
||||
if (activeCount == 0){
|
||||
val queuedCount = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueuedDownloadsCount()
|
||||
}
|
||||
if (queuedCount == 0) {
|
||||
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (activeCount == 1){
|
||||
val queue = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
|
||||
cancelActiveDownload(itemID)
|
||||
if (!sharedPreferences.getBoolean("paused_downloads", false)){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onCardClick() {
|
||||
this.dismiss()
|
||||
|
|
@ -505,22 +524,29 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
)
|
||||
}
|
||||
|
||||
private fun cancelActiveDownload(itemID: Long){
|
||||
lifecycleScope.launch {
|
||||
val id = itemID.toInt()
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
//dont remove
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(event.progress, true)
|
||||
outputText?.text = event.output
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ class DownloadLogFragment : Fragment() {
|
|||
private lateinit var logViewModel: LogViewModel
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private var autoScroll : Boolean = true
|
||||
private var scrollDownBtn : MenuItem? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
@ -113,6 +116,7 @@ class DownloadLogFragment : Fragment() {
|
|||
|
||||
contentScrollView.enableFastScroll()
|
||||
|
||||
scrollDownBtn = bottomAppBar?.menu?.children?.first { it.itemId == R.id.scroll_down }
|
||||
|
||||
val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
|
||||
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
|
||||
|
|
@ -124,16 +128,17 @@ class DownloadLogFragment : Fragment() {
|
|||
scrollView.removeAllViews()
|
||||
parent.removeView(scrollView)
|
||||
parent.addView(content, 0)
|
||||
contentScrollView.setPadding(0,0,0,
|
||||
(requireContext().resources.displayMetrics.density * 150).toInt()
|
||||
)
|
||||
// contentScrollView.setPadding(0,0,0,
|
||||
// (requireContext().resources.displayMetrics.density * 150).toInt()
|
||||
// )
|
||||
updateAutoScrollState()
|
||||
}else{
|
||||
val parent = content.parent as ViewGroup
|
||||
parent.removeView(content)
|
||||
scrollView = HorizontalScrollView(requireContext())
|
||||
scrollView.setPadding(0,0,0,
|
||||
(requireContext().resources.displayMetrics.density * 150).toInt()
|
||||
)
|
||||
// scrollView.setPadding(0,0,0,
|
||||
// (requireContext().resources.displayMetrics.density * 150).toInt()
|
||||
// )
|
||||
contentScrollView.setPadding(0,0,0,0)
|
||||
scrollView.layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
|
|
@ -142,11 +147,13 @@ class DownloadLogFragment : Fragment() {
|
|||
scrollView.addView(content)
|
||||
scrollView.id = R.id.horizontalscroll_output
|
||||
parent.addView(scrollView, 0)
|
||||
updateAutoScrollState()
|
||||
}
|
||||
}
|
||||
|
||||
R.id.scroll_down -> {
|
||||
m.isVisible = false
|
||||
autoScroll = true
|
||||
contentScrollView.fullScroll(View.FOCUS_DOWN)
|
||||
}
|
||||
|
||||
|
|
@ -171,9 +178,12 @@ class DownloadLogFragment : Fragment() {
|
|||
}
|
||||
|
||||
contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy ->
|
||||
if (sy < osy){
|
||||
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
|
||||
}
|
||||
updateAutoScrollState()
|
||||
}
|
||||
|
||||
contentScrollView.setOnTouchListener { view, motionEvent ->
|
||||
autoScroll = false
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -185,9 +195,11 @@ class DownloadLogFragment : Fragment() {
|
|||
content.setText(logItem.content, TextView.BufferType.SPANNABLE)
|
||||
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
|
||||
}
|
||||
if (!bottomAppBar.menu.children.first { it.itemId == R.id.scroll_down }.isVisible){
|
||||
content.scrollTo(0, content.height)
|
||||
contentScrollView.fullScroll(View.FOCUS_DOWN)
|
||||
if (autoScroll){
|
||||
//content.scrollTo(0, content.height)
|
||||
content.post {
|
||||
contentScrollView.fullScroll(View.FOCUS_DOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +207,11 @@ class DownloadLogFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateAutoScrollState() {
|
||||
val canVerticallyScroll = contentScrollView.canScrollVertically(1)
|
||||
scrollDownBtn?.isVisible = canVerticallyScroll
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DownloadLogActivity"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
editor.putString("command_path", FileUtil.getDefaultCommandPath())
|
||||
}
|
||||
|
||||
if((VERSION.SDK_INT >= 30 && Environment.isExternalStorageManager()) ||
|
||||
VERSION.SDK_INT < 30) {
|
||||
if (FileUtil.hasAllFilesAccess()) {
|
||||
accessAllFiles!!.isVisible = false
|
||||
cacheDownloads!!.isEnabled = true
|
||||
}else{
|
||||
|
|
@ -90,8 +89,6 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
cacheDownloads!!.isEnabled = false
|
||||
}
|
||||
|
||||
editor.apply()
|
||||
|
||||
musicPath!!.summary = FileUtil.formatPath(preferences.getString("music_path", "")!!)
|
||||
musicPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
|
|
|
|||
|
|
@ -261,20 +261,27 @@ object Extensions {
|
|||
}
|
||||
}
|
||||
|
||||
// fun String.appendLineToLog(line: String): String {
|
||||
// val lines = this.split("\n")
|
||||
// if (!lines.takeLast(3).contains(line)){
|
||||
// //clean dublicate progress + add newline
|
||||
// var newLine = line
|
||||
// if (newLine.contains("[download")) {
|
||||
// newLine = "[download]" + line.split("[download]").last()
|
||||
// }
|
||||
//
|
||||
// return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
||||
// }
|
||||
//
|
||||
// return this
|
||||
// }
|
||||
fun String.appendLineToLog(line: String = ""): String {
|
||||
val lines = this.lines().toMutableList()
|
||||
val finishingProgressLinesRegex = Pattern.compile("\\[download]\\h+(100%|[a-zA-Z])")
|
||||
|
||||
if (line.isNotBlank()) {
|
||||
var newLine = ""
|
||||
val newLines = line.lines()
|
||||
if (!lines.takeLast(3).any { newLines.contains(it) }) {
|
||||
lines.addAll(newLines.dropLast(1))
|
||||
newLine = "\n${newLines.last()}"
|
||||
}
|
||||
|
||||
return lines.dropLastWhile {
|
||||
it.contains("[download") && !finishingProgressLinesRegex.matcher(it).find()
|
||||
}.joinToString("\n") + newLine
|
||||
}
|
||||
|
||||
return lines.filterNot {
|
||||
it.contains("[download") && !finishingProgressLinesRegex.matcher(it).find()
|
||||
}.joinToString("\n")
|
||||
}
|
||||
|
||||
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
|
||||
if(!hideThumb){
|
||||
|
|
|
|||
|
|
@ -400,4 +400,9 @@ object FileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun hasAllFilesAccess() : Boolean {
|
||||
if (Build.VERSION.SDK_INT < 30) return true
|
||||
return Environment.isExternalStorageManager()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Resources.NotFoundException
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
|
|
@ -43,7 +42,6 @@ import java.lang.reflect.Type
|
|||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
class InfoUtil(private val context: Context) {
|
||||
|
|
@ -168,6 +166,13 @@ class InfoUtil(private val context: Context) {
|
|||
fun getYoutubeVideo(url: String): List<ResultItem>? {
|
||||
val theURL = url.replace("\\?list.*".toRegex(), "")
|
||||
try{
|
||||
//TODO NEWPIPE TEST
|
||||
// val extractor = ServiceList.YouTube.getStreamExtractor(url)
|
||||
// extractor.fetchPage()
|
||||
// val extractor2 = ServiceList.YouTube.getPlaylistExtractor(url)
|
||||
// extractor2.fetchPage()
|
||||
// var info = extractor2.getPage(extractor2.initialPage.nextPage)
|
||||
|
||||
val id = getIDFromYoutubeURL(theURL)
|
||||
val res = genericRequest("$pipedURL/streams/$id")
|
||||
if (res.length() == 0) {
|
||||
|
|
@ -678,7 +683,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
val lang = sharedPreferences.getString("app_language", "en")
|
||||
if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
|
||||
var extractorArgs = "player_client=default,mediaconnect,android"
|
||||
var extractorArgs = "player_client=default,mediaconnect"
|
||||
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
|
||||
extractorArgs += ";lang=$lang"
|
||||
}
|
||||
|
|
@ -1034,6 +1039,12 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<String>.addOption(vararg options: String) {
|
||||
options.forEach {
|
||||
this.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
|
||||
|
|
@ -1052,14 +1063,16 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
val metadataCommands = mutableListOf<String>()
|
||||
|
||||
if (downloadItem.playlistIndex != null && useItemURL) {
|
||||
request.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
|
||||
metadataCommands.addOption("--parse-metadata", " ${downloadItem.playlistIndex}: %(playlist_index)s")
|
||||
}
|
||||
|
||||
val type = downloadItem.type
|
||||
|
||||
val downDir : File
|
||||
val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite() || sharedPreferences.getBoolean("access_all_files", false)
|
||||
val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
|
||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && canWrite){
|
||||
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
||||
request.addOption("--no-quiet")
|
||||
|
|
@ -1162,18 +1175,14 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if(downloadItem.title.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(180)))
|
||||
metadataCommands.addOption("--replace-in-metadata", "title", ".+", downloadItem.title.take(180))
|
||||
metadataCommands.addOption("--parse-metadata", "%(title)s:%(meta_title)s")
|
||||
}
|
||||
|
||||
|
||||
if (downloadItem.author.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
|
||||
}
|
||||
|
||||
request.addOption("--parse-metadata", "uploader:^(?P<uploader>.*?)(?:(?= - Topic)|$)")
|
||||
|
||||
if (embedMetadata){
|
||||
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
|
||||
metadataCommands.addOption("--replace-in-metadata", "uploader", ".+", downloadItem.author.take(30))
|
||||
metadataCommands.addOption("--parse-metadata", "%(uploader)s:%(artist)s")
|
||||
}
|
||||
|
||||
if (downloadItem.downloadSections.isNotBlank()){
|
||||
|
|
@ -1196,7 +1205,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_audio_quality", false)){
|
||||
request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0))
|
||||
request.addOption("--audio-quality", sharedPreferences.getInt("audio_quality", 0).toString())
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("write_description", false)){
|
||||
|
|
@ -1204,7 +1213,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if (downloadItem.url.isYoutubeURL()) {
|
||||
var extractorArgs = "player_client=default,mediaconnect,android"
|
||||
var extractorArgs = "player_client=default,mediaconnect"
|
||||
val lang = sharedPreferences.getString("app_language", "en")
|
||||
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
|
||||
extractorArgs += ";lang=$lang"
|
||||
|
|
@ -1261,7 +1270,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
request.addOption("-x")
|
||||
|
||||
val formatSorting = StringBuilder("hasaud")
|
||||
val formatSorting = StringBuilder("hasaud,size")
|
||||
|
||||
if (aCodecPref.isNotBlank()){
|
||||
formatSorting.append(",acodec:$aCodecPref")
|
||||
|
|
@ -1285,31 +1294,46 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("-P", downDir.absolutePath)
|
||||
request.addOption("-S", formatSorting.toString())
|
||||
|
||||
metadataCommands.addOption("--parse-metadata", """%(uploader,artist,channel,creator|null)s:^(?P<uploader>.*?)(?:(?= - Topic)|$)""")
|
||||
|
||||
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||
request.addOption("--split-chapters")
|
||||
request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
|
||||
}else{
|
||||
|
||||
if (embedMetadata){
|
||||
request.addOption("--embed-metadata")
|
||||
request.addOption("--parse-metadata", "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)")
|
||||
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
|
||||
metadataCommands.addOption("--embed-metadata")
|
||||
|
||||
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
|
||||
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\\d+)")
|
||||
val emptyAuthor = downloadItem.author.isEmpty()
|
||||
val usePlaylistMetadata = sharedPreferences.getBoolean("playlist_as_album", true)
|
||||
|
||||
if (downloadItem.playlistTitle.isNotEmpty()) {
|
||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:(?P<track_number>\\d+)")
|
||||
} else {
|
||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||
if (emptyAuthor) {
|
||||
if (usePlaylistMetadata) {
|
||||
metadataCommands.addOption("--parse-metadata", "%(playlist_uploader,artist,uploader|)s:^(?P<first_artist>.*?)(?:(?=,\\s+)|$)")
|
||||
}else{
|
||||
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)
|
||||
if (downloadItem.audioPreferences.embedThumb){
|
||||
request.addOption("--embed-thumbnail")
|
||||
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
|
||||
metadataCommands.addOption("--embed-thumbnail")
|
||||
if (!request.hasOption("--convert-thumbnails")) metadataCommands.addOption("--convert-thumbnails", thumbnailFormat!!)
|
||||
|
||||
val thumbnailConfig = StringBuilder("")
|
||||
val cropConfig = """-vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"""""
|
||||
|
|
@ -1326,7 +1350,7 @@ class InfoUtil(private val context: Context) {
|
|||
runCatching {
|
||||
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
|
||||
config.writeText(thumbnailConfig.toString())
|
||||
request.addOption("--config", config.absolutePath)
|
||||
metadataCommands.addOption("--config", config.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1339,6 +1363,8 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
metadataCommands.addOption("--parse-metadata", """%(uploader,channel,creator,artist|null)s:^(?P<uploader>.*?)(?:(?= - Topic)|$)""")
|
||||
|
||||
val supportedContainers = context.resources.getStringArray(R.array.video_containers)
|
||||
|
||||
if (downloadItem.videoPreferences.addChapters) {
|
||||
|
|
@ -1359,7 +1385,8 @@ class InfoUtil(private val context: Context) {
|
|||
){
|
||||
cont = outputContainer
|
||||
|
||||
if (downloadItem.videoPreferences.recodeVideo) {
|
||||
val cantRecode = listOf("avi")
|
||||
if (downloadItem.videoPreferences.recodeVideo && !cantRecode.contains(cont)) {
|
||||
request.addOption("--recode-video", outputContainer.lowercase())
|
||||
}else{
|
||||
request.addOption("--merge-output-format", outputContainer.lowercase())
|
||||
|
|
@ -1368,7 +1395,7 @@ class InfoUtil(private val context: Context) {
|
|||
if (!listOf("webm", "avi", "flv").contains(outputContainer.lowercase())) {
|
||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||
if (embedThumb) {
|
||||
request.addOption("--embed-thumbnail")
|
||||
metadataCommands.addOption("--embed-thumbnail")
|
||||
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
|
||||
}
|
||||
}
|
||||
|
|
@ -1382,7 +1409,6 @@ class InfoUtil(private val context: Context) {
|
|||
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
|
||||
val format = downloadItem.allFormats.find { it.format_id == f }
|
||||
format?.run {
|
||||
println(format_id)
|
||||
if (this.format_id.matches(".*-[0-9]+".toRegex())) {
|
||||
if (!this.lang.isNullOrBlank() && this.lang != "None") {
|
||||
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
|
||||
|
|
@ -1591,7 +1617,9 @@ class InfoUtil(private val context: Context) {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
if (metadataCommands.isNotEmpty()){
|
||||
request.addCommands(metadataCommands)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ object NavbarUtil {
|
|||
val pref = settings.getString("start_destination", "")!!
|
||||
val items = getDefaultNavBarItems(context)
|
||||
val navBarHasPref = getNavBarPrefs().contains(items.indexOfFirst { it.itemId == navItems[pref] }.toString())
|
||||
println("START FRAGMENT: $pref")
|
||||
return if (pref == "") {
|
||||
R.id.homeFragment
|
||||
}else {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.core.content.FileProvider
|
|||
import androidx.core.os.bundleOf
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
|
||||
|
|
@ -148,11 +149,9 @@ class NotificationUtil(var context: Context) {
|
|||
fun createDownloadServiceNotification(
|
||||
pendingIntent: PendingIntent?,
|
||||
title: String?,
|
||||
itemID: Int,
|
||||
): Notification {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(title)
|
||||
.setOngoing(true)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ import java.text.SimpleDateFormat
|
|||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.Queue
|
||||
import java.util.function.Predicate
|
||||
|
||||
|
||||
object UiUtil {
|
||||
|
|
@ -862,14 +863,16 @@ object UiUtil {
|
|||
true
|
||||
}
|
||||
|
||||
if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE
|
||||
fun String?.doesntExist() : Boolean { return this.isNullOrBlank() || this == "None"}
|
||||
|
||||
if (format.format_id.doesntExist()) formatIdParent?.visibility = View.GONE
|
||||
else {
|
||||
formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id
|
||||
formatIdParent?.setOnClickListener(clicker)
|
||||
formatIdParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
if (format.url.isNullOrBlank()) formatURLParent?.visibility = View.GONE
|
||||
if (format.url.doesntExist()) formatURLParent?.visibility = View.GONE
|
||||
else {
|
||||
formatURLParent?.findViewById<TextView>(R.id.format_url_value)?.text = format.url
|
||||
formatURLParent?.setOnClickListener(clicker)
|
||||
|
|
@ -877,7 +880,7 @@ object UiUtil {
|
|||
}
|
||||
|
||||
|
||||
if (format.container.isBlank()) containerParent?.visibility = View.GONE
|
||||
if (format.container.doesntExist()) containerParent?.visibility = View.GONE
|
||||
else {
|
||||
containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container
|
||||
containerParent?.setOnClickListener(clicker)
|
||||
|
|
@ -893,7 +896,7 @@ object UiUtil {
|
|||
format.acodec.uppercase()
|
||||
}
|
||||
|
||||
if (codecField.isBlank()) codecParent?.visibility = View.GONE
|
||||
if (codecField.doesntExist()) codecParent?.visibility = View.GONE
|
||||
else {
|
||||
codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField
|
||||
codecParent?.setOnClickListener(clicker)
|
||||
|
|
@ -907,28 +910,28 @@ object UiUtil {
|
|||
filesizeParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
if (format.format_note.isBlank()) formatnoteParent?.visibility = View.GONE
|
||||
if (format.format_note.doesntExist()) formatnoteParent?.visibility = View.GONE
|
||||
else {
|
||||
formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note
|
||||
formatnoteParent?.setOnClickListener(clicker)
|
||||
formatnoteParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
if (format.fps.isNullOrBlank() || format.fps == "0") fpsParent?.visibility = View.GONE
|
||||
if (format.fps.doesntExist() || format.fps == "0") fpsParent?.visibility = View.GONE
|
||||
else {
|
||||
fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps
|
||||
fpsParent?.setOnClickListener(clicker)
|
||||
fpsParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
if (format.asr.isNullOrBlank()) asrParent?.visibility = View.GONE
|
||||
if (format.asr.doesntExist()) asrParent?.visibility = View.GONE
|
||||
else {
|
||||
asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr
|
||||
asrParent?.setOnClickListener(clicker)
|
||||
asrParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
if (format.tbr.isNullOrBlank()) bitrateParent?.visibility = View.GONE
|
||||
if (format.tbr.doesntExist()) bitrateParent?.visibility = View.GONE
|
||||
else{
|
||||
bitrateParent?.findViewById<TextView>(R.id.bitrate_value)?.text = format.tbr
|
||||
bitrateParent?.setOnClickListener(clicker)
|
||||
|
|
@ -990,7 +993,21 @@ object UiUtil {
|
|||
val chips = mutableListOf<Chip>()
|
||||
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
|
||||
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
||||
tmp.text = Locale(s).displayLanguage
|
||||
s.split("-", ".*").run {
|
||||
if (s.endsWith(".*")) {
|
||||
val name = "${Locale(this[0]).displayName} (${context.getString(R.string.all)})"
|
||||
tmp.text = name
|
||||
}else{
|
||||
if (this.size == 1) {
|
||||
Locale(this[0])
|
||||
} else {
|
||||
Locale(this[0], this[1])
|
||||
}.apply {
|
||||
tmp.text = this.displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmp.tag = s
|
||||
tmp.id = index
|
||||
|
||||
|
|
@ -1564,24 +1581,20 @@ object UiUtil {
|
|||
}
|
||||
|
||||
|
||||
|
||||
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState, closed: () -> Unit){
|
||||
val title = context.getString(it.errorMessage!!.first)
|
||||
val message = it.errorMessage!!.second
|
||||
|
||||
fun handleNoResults(context: Activity, message: String, continueAnyway: Boolean = false, continued: () -> Unit, closed: () -> Unit) {
|
||||
val errDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(title)
|
||||
.setTitle(R.string.no_results)
|
||||
.setMessage(message)
|
||||
|
||||
for (a in it.actions!!){
|
||||
when(a.second){
|
||||
ResultViewModel.ResultAction.COPY_LOG -> {
|
||||
errDialog.setPositiveButton(a.first) { d:DialogInterface?, _:Int ->
|
||||
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setText(message)
|
||||
d?.dismiss()
|
||||
}
|
||||
}
|
||||
errDialog.setPositiveButton(R.string.copy_log) { d:DialogInterface?, _:Int ->
|
||||
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setText(message)
|
||||
d?.dismiss()
|
||||
}
|
||||
|
||||
if (continueAnyway) {
|
||||
errDialog.setNeutralButton(R.string.continue_anyway) {d: DialogInterface?, _:Int ->
|
||||
continued()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
package com.deniscerri.ytdl.work
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.DisplayMetrics
|
||||
|
|
@ -20,6 +23,7 @@ import androidx.work.await
|
|||
import androidx.work.workDataOf
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.App
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||
|
|
@ -73,15 +77,28 @@ class DownloadWorker(
|
|||
//val createResultItem = inputData.getBoolean("createResultItem", true)
|
||||
|
||||
val confTmp = Configuration(context.resources.configuration)
|
||||
val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-")
|
||||
confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1]))
|
||||
val locale = if (Build.VERSION.SDK_INT < 33) {
|
||||
sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }
|
||||
}else{
|
||||
Locale.getDefault().language
|
||||
}.run {
|
||||
split("-")
|
||||
}.run {
|
||||
if (this.size == 1) Locale(this[0]) else Locale(this[0], this[1])
|
||||
}
|
||||
confTmp.setLocale(locale)
|
||||
val metrics = DisplayMetrics()
|
||||
val resources = Resources(context.assets, metrics, confTmp)
|
||||
|
||||
val pendingIntent = NavDeepLinkBuilder(context)
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.downloadQueueMainFragment)
|
||||
.createPendingIntent()
|
||||
val openQueueIntent = Intent(context, MainActivity::class.java)
|
||||
openQueueIntent.setAction(Intent.ACTION_VIEW)
|
||||
openQueueIntent.putExtra("destination", "Queue")
|
||||
val openDownloadQueue = PendingIntent.getActivity(
|
||||
context,
|
||||
1000000000,
|
||||
openQueueIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val workNotif = notificationUtil.createDefaultWorkerNotification()
|
||||
val foregroundInfo = ForegroundInfo(1000000000, workNotif)
|
||||
|
|
@ -112,7 +129,7 @@ class DownloadWorker(
|
|||
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
||||
|
||||
eligibleDownloads.forEach{downloadItem ->
|
||||
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title.ifEmpty { downloadItem.url }, downloadItem.id.toInt())
|
||||
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
|
||||
notificationUtil.notify(downloadItem.id.toInt(), notification)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
|
@ -189,7 +206,7 @@ class DownloadWorker(
|
|||
}
|
||||
//val wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||
runBlocking {
|
||||
var finalPaths : MutableList<String>?
|
||||
var finalPaths = mutableListOf<String>()
|
||||
|
||||
if (noCache){
|
||||
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id))
|
||||
|
|
@ -212,9 +229,6 @@ class DownloadWorker(
|
|||
finalPaths.sortBy { File(it).lastModified() }
|
||||
finalPaths = finalPaths.distinct().toMutableList()
|
||||
FileUtil.scanMedia(finalPaths, context)
|
||||
if (finalPaths.isEmpty()){
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}else{
|
||||
//move file from internal to set download directory
|
||||
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
|
||||
|
|
@ -227,11 +241,8 @@ class DownloadWorker(
|
|||
|
||||
if (finalPaths.isNotEmpty()){
|
||||
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
|
||||
}else{
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
e.printStackTrace()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -246,51 +257,52 @@ class DownloadWorker(
|
|||
add("description")
|
||||
add("txt")
|
||||
}
|
||||
finalPaths = finalPaths?.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }?.toMutableList()
|
||||
finalPaths = finalPaths.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }.toMutableList()
|
||||
if (finalPaths.isEmpty()){
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
FileUtil.deleteConfigFiles(request)
|
||||
|
||||
//put download in history
|
||||
if (!downloadItem.incognito) {
|
||||
if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) {
|
||||
if (request.hasOption("--download-archive") && finalPaths.isEmpty()) {
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
||||
}, 100)
|
||||
}else{
|
||||
val unixTime = System.currentTimeMillis() / 1000
|
||||
finalPaths?.apply {
|
||||
this.first().apply {
|
||||
val file = File(this)
|
||||
var duration = downloadItem.duration
|
||||
val d = file.getMediaDuration(context)
|
||||
if (d > 0) duration = d.toStringDuration(Locale.US)
|
||||
finalPaths.first().apply {
|
||||
val file = File(this)
|
||||
var duration = downloadItem.duration
|
||||
val d = file.getMediaDuration(context)
|
||||
if (d > 0) duration = d.toStringDuration(Locale.US)
|
||||
|
||||
downloadItem.format.filesize = file.length()
|
||||
downloadItem.format.container = file.extension
|
||||
downloadItem.duration = duration
|
||||
}
|
||||
|
||||
val historyItem = HistoryItem(0,
|
||||
downloadItem.url,
|
||||
downloadItem.title,
|
||||
downloadItem.author,
|
||||
downloadItem.duration,
|
||||
downloadItem.thumb,
|
||||
downloadItem.type,
|
||||
unixTime,
|
||||
this,
|
||||
downloadItem.website,
|
||||
downloadItem.format,
|
||||
downloadItem.id,
|
||||
commandString)
|
||||
historyDao.insert(historyItem)
|
||||
downloadItem.format.filesize = file.length()
|
||||
downloadItem.format.container = file.extension
|
||||
downloadItem.duration = duration
|
||||
}
|
||||
|
||||
val historyItem = HistoryItem(0,
|
||||
downloadItem.url,
|
||||
downloadItem.title,
|
||||
downloadItem.author,
|
||||
downloadItem.duration,
|
||||
downloadItem.thumb,
|
||||
downloadItem.type,
|
||||
unixTime,
|
||||
finalPaths,
|
||||
downloadItem.website,
|
||||
downloadItem.format,
|
||||
downloadItem.id,
|
||||
commandString)
|
||||
historyDao.insert(historyItem)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
||||
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
||||
)
|
||||
|
||||
// if (wasQuickDownloaded && createResultItem){
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class TerminalDownloadWorker(
|
|||
|
||||
val intent = Intent(context, TerminalActivity::class.java)
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65), itemId)
|
||||
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, command.take(65))
|
||||
val foregroundInfo = ForegroundInfo(itemId, notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
|
|
|
|||
5
app/src/main/res/drawable/baseline_album_24.xml
Normal file
5
app/src/main/res/drawable/baseline_album_24.xml
Normal 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>
|
||||
|
|
@ -8,5 +8,5 @@
|
|||
android:bottom="1dp"
|
||||
android:top="1dp" />
|
||||
|
||||
<corners android:radius="3dp" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_height="80dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/downloads_image_view"
|
||||
|
|
@ -49,13 +49,12 @@
|
|||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/download_item_data"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toStartOf="@+id/options"
|
||||
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
@ -66,7 +65,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="15sp"
|
||||
|
|
@ -76,11 +75,11 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/horizontalScrollView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -90,27 +89,30 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_type"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:drawableStartCompat="@drawable/ic_music_formatcard"
|
||||
app:drawableTint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
|
|
@ -123,7 +125,6 @@
|
|||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
@ -142,10 +143,8 @@
|
|||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
@ -161,10 +160,11 @@
|
|||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
@ -172,12 +172,24 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
app:layout_constraintHorizontal_weight="0.3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/incognitoLabel"
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@
|
|||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:drawableStartCompat="@drawable/ic_music_formatcard"
|
||||
app:drawableTint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
@ -173,6 +172,7 @@
|
|||
<TextView
|
||||
android:id="@+id/format_id"
|
||||
android:layout_width="0dp"
|
||||
android:minWidth="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
|
|
|
|||
|
|
@ -1,31 +1,33 @@
|
|||
<?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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/appbarlayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:liftOnScroll="false"
|
||||
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
|
||||
android:id="@+id/title"
|
||||
android:elevation="0dp"
|
||||
android:layout_width="match_parent"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="0dp"
|
||||
app:navigationContentDescription="@string/back"
|
||||
android:layout_height="match_parent"/>
|
||||
app:navigationIcon="@drawable/ic_back" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:visibility="gone"
|
||||
android:id="@+id/textsize_seekbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appbar" />
|
||||
|
|
@ -34,34 +36,45 @@
|
|||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:id="@+id/frameLayout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout3"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appbarlayout">
|
||||
|
||||
<me.zhanghai.android.fastscroll.FastScrollScrollView
|
||||
android:id="@+id/content_scrollview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
>
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="150dp"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:padding="10dp"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="15sp"
|
||||
android:gravity="bottom"
|
||||
android:scrollbars="none"
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_height="wrap_content" />
|
||||
android:id="@+id/horizontalscroll_output"
|
||||
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>
|
||||
|
||||
|
|
@ -69,27 +82,41 @@
|
|||
|
||||
</FrameLayout>
|
||||
|
||||
<include layout="@layout/no_results"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
android:layout_width="match_parent"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinatorLayout3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
style="@style/Widget.Material3.BottomAppBar"
|
||||
app:menu="@menu/log_detail_menu"
|
||||
/>
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<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:layout_anchor="@id/bottomAppBar"
|
||||
app:icon="@drawable/ic_copy"/>
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
style="@style/Widget.Material3.BottomAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/frameLayout"
|
||||
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>
|
||||
|
|
@ -67,7 +67,7 @@
|
|||
app:layout_constraintHorizontal_weight="0.3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
|
||||
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -101,9 +101,12 @@
|
|||
android:id="@+id/download_music"
|
||||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||
android:layout_width="55dp"
|
||||
app:elevation="0dp"
|
||||
android:elevation="0dp"
|
||||
app:borderWidth="0dp"
|
||||
android:contentDescription="@string/audio"
|
||||
android:layout_height="55dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
app:icon="@drawable/ic_music"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_video"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
@ -113,8 +116,9 @@
|
|||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||
android:layout_width="55dp"
|
||||
android:layout_height="55dp"
|
||||
app:elevation="0dp"
|
||||
android:contentDescription="@string/video"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
app:icon="@drawable/ic_video"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<group>
|
||||
<item
|
||||
android:id="@+id/pause"
|
||||
android:title="@string/pause"
|
||||
android:icon="@drawable/exomedia_ic_pause_white"
|
||||
app:showAsAction="ifRoom" />
|
||||
<!-- <group>-->
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/pause"-->
|
||||
<!-- android:title="@string/pause"-->
|
||||
<!-- android:icon="@drawable/exomedia_ic_pause_white"-->
|
||||
<!-- app:showAsAction="ifRoom" />-->
|
||||
|
||||
<item
|
||||
android:id="@+id/resume"
|
||||
android:title="@string/resume"
|
||||
android:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:showAsAction="ifRoom" />
|
||||
</group>
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/resume"-->
|
||||
<!-- android:title="@string/resume"-->
|
||||
<!-- android:icon="@drawable/exomedia_ic_play_arrow_white"-->
|
||||
<!-- app:showAsAction="ifRoom" />-->
|
||||
<!-- </group>-->
|
||||
|
||||
<item
|
||||
android:id="@+id/cancel"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
<item
|
||||
android:id="@+id/scroll_down"
|
||||
android:title="@string/scroll_down"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/ic_down"
|
||||
app:showAsAction="always" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1029,13 +1029,16 @@
|
|||
<item>en-IN</item>,
|
||||
<item>en-GB</item>,
|
||||
<item>en</item>,
|
||||
<item>en.*</item>,
|
||||
<item>es</item>,
|
||||
<item>es-419</item>,
|
||||
<item>es-US</item>,
|
||||
<item>es.*</item>,
|
||||
<item>eu</item>,
|
||||
<item>fil</item>,
|
||||
<item>fr</item>,
|
||||
<item>fr-CA</item>,
|
||||
<item>fr.*</item>,
|
||||
<item>gl</item>,
|
||||
<item>hr</item>,
|
||||
<item>zu</item>,
|
||||
|
|
@ -1051,6 +1054,7 @@
|
|||
<item>pl</item>,
|
||||
<item>pt-PT</item>,
|
||||
<item>pt</item>,
|
||||
<item>pt.*</item>,
|
||||
<item>ro</item>,
|
||||
<item>sq</item>,
|
||||
<item>sk</item>,
|
||||
|
|
@ -1097,6 +1101,7 @@
|
|||
<item>zh-CN</item>,
|
||||
<item>zh-TW</item>,
|
||||
<item>zh-HK</item>,
|
||||
<item>zh.*</item>,
|
||||
<item>ja</item>,
|
||||
<item>ko</item>
|
||||
</string-array>
|
||||
|
|
|
|||
|
|
@ -418,4 +418,7 @@
|
|||
<string name="weekly">Weekly</string>
|
||||
<string name="monthly">Monthly</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>
|
||||
|
|
@ -32,6 +32,11 @@
|
|||
<item name="cornerSize">5dp</item>
|
||||
</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">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeTopLeft">12dp</item>
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@
|
|||
app:summary="@string/crop_thumb_summary"
|
||||
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>
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue