some changes

- fixed log removing some lines
- added MASTER channel in yt-dlp updates
- made errored downloads as a separate notification channel
- fix notification language conflict for portugal brasil in worker notification
- kept state of download card when going in landscape, even while updating data
- add crop thumbnail to adjust audio preferences
- fix command templates creation showing extra command checkboxes even though extra command is disabled
- fix preferred audio codec disrupting preferred audio id
This commit is contained in:
zaednasr 2023-11-19 16:31:00 +01:00
parent 6eeb662a2c
commit 3e4d89577a
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
26 changed files with 316 additions and 170 deletions

View file

@ -10,8 +10,8 @@ plugins {
def properties = new Properties() def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 6 def versionMinor = 6
def versionPatch = 8 def versionPatch = 9
def versionBuild = 1 // bump for dogfood builds, public betas, etc. def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = "" def versionExt = ""
if (versionBuild > 0){ if (versionBuild > 0){

View file

@ -7,20 +7,22 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.Gravity
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.forEach import androidx.core.view.forEach
@ -30,7 +32,6 @@ import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
@ -42,7 +43,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.BaseActivity import com.deniscerri.ytdlnis.ui.BaseActivity
import com.deniscerri.ytdlnis.ui.HomeFragment import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.ThemeUtil import com.deniscerri.ytdlnis.util.ThemeUtil

View file

@ -6,6 +6,7 @@ import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class AudioPreferences( data class AudioPreferences(
var embedThumb: Boolean = true, var embedThumb: Boolean = true,
var cropThumb: Boolean? = null,
var splitByChapters: Boolean = false, var splitByChapters: Boolean = false,
var sponsorBlockFilters: ArrayList<String> = arrayListOf() var sponsorBlockFilters: ArrayList<String> = arrayListOf()
) : Parcelable ) : Parcelable

View file

@ -41,25 +41,17 @@ class LogRepository(private val logDao: LogDao) {
suspend fun update(line: String, id: Long){ suspend fun update(line: String, id: Long){
runCatching { runCatching {
val item = getItem(id) val item = getItem(id) ?: return
val log = item.content val log = item.content ?: ""
//clean duplicate progress + add newline val lines = log.split("\n")
val lines = log.split("\n").toMutableList() //clean dublicate progress + add newline
run loop@ { var newLine = line
for(i in lines.size - 1 downTo 0){ if (newLine.contains("[download")){
val l = lines[i] newLine = "[download]" + line.split("[download]").last()
if(l.contains("\\[download]( *?)(\\d)(.*?)".toRegex())){
lines[i] = ""
return@loop
}
}
} }
val l = if (line.contains("[download]")) {
"[download]" + line.split("[download]").last() val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
}else { item.content = l
line
}
item.content = lines.filter { it.isNotBlank() }.joinToString("\n") + "\n${l}"
logDao.update(item) logDao.update(item)
} }
} }

View file

@ -233,6 +233,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val addChapters = sharedPreferences.getBoolean("add_chapters", false) val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
var type = getDownloadType(givenType, resultItem.url) var type = getDownloadType(givenType, resultItem.url)
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
@ -257,7 +258,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!)) val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val preferredAudioFormats = arrayListOf<String>() val preferredAudioFormats = arrayListOf<String>()
for (f in resultItem.formats.sortedBy { it.format_id }){ for (f in resultItem.formats.sortedBy { it.format_id }){
@ -391,6 +392,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val addChapters = sharedPreferences.getBoolean("add_chapters", false) val addChapters = sharedPreferences.getBoolean("add_chapters", false)
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
val customFileNameTemplate = when(historyItem.type) { val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s") Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s")
@ -418,7 +421,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
else -> "" else -> ""
} }
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!)) val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs) val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs)
val downloadPath = File(historyItem.downloadPath) val downloadPath = File(historyItem.downloadPath)
val path = if (downloadPath.exists()) downloadPath.parent else defaultPath val path = if (downloadPath.exists()) downloadPath.parent else defaultPath
@ -625,6 +628,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
alarmScheduler.schedule() alarmScheduler.schedule()
} }
if (items.any { it.playlistTitle.isEmpty() } && items.size > 1){
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
}
items.forEach { items.forEach {
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString() if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
val currentCommand = infoUtil.buildYoutubeDLRequest(it) val currentCommand = infoUtil.buildYoutubeDLRequest(it)
@ -679,14 +686,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
} }
} }
if (items.any { it.playlistTitle.isEmpty() }){ if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
withContext(Dispatchers.IO){
dao.updateMultiple(items)
}
}
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
startDownloadWorker(queuedItems) startDownloadWorker(queuedItems)
if(!useScheduler){ if(!useScheduler){

View file

@ -29,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.regex.Pattern import java.util.regex.Pattern
@ -195,50 +196,58 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
suspend fun updateItemData(res: ResultItem){ suspend fun updateItemData(res: ResultItem){
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){ if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) { updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching { updatingData.emit(true)
updatingData.emit(true) val result = parseQueries(listOf(res.url))
val result = parseQueries(listOf(res.url)) updatingData.emit(false)
updatingData.emit(false) updateResultData.emit(result)
updateResultData.emit(result) }
}.onFailure { }
updateResultDataJob?.start()
updateResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(false) updatingData.emit(false)
updateResultData.emit(mutableListOf()) updateResultData.emit(mutableListOf())
} }
} }
} }
updateResultDataJob?.start()
} }
suspend fun cancelUpdateItemData(){ suspend fun cancelUpdateItemData(){
updateResultDataJob?.cancel()
updatingData.emit(false) updatingData.emit(false)
updateResultData.emit(null) updateResultData.emit(null)
updateResultDataJob?.cancel()
} }
suspend fun cancelUpdateFormatsItemData(){ suspend fun cancelUpdateFormatsItemData(){
updateFormatsResultDataJob?.cancel()
updatingFormats.emit(false) updatingFormats.emit(false)
updateFormatsResultData.emit(null) updateFormatsResultData.emit(null)
updateFormatsResultDataJob?.cancel()
} }
suspend fun updateFormatItemData(result: ResultItem){ suspend fun updateFormatItemData(result: ResultItem){
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) { if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) {
kotlin.runCatching { updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(true) updatingFormats.emit(true)
val formats = infoUtil.getFormats(result.url) val formats = infoUtil.getFormats(result.url)
updatingFormats.emit(false) updatingFormats.emit(false)
if (formats.isNotEmpty()){ if (formats.isNotEmpty() && isActive) {
val res = getItemByURL(result.url) val res = getItemByURL(result.url)
res.formats = formats.toMutableList() res.formats = formats.toMutableList()
update(res) update(res)
} }
updateFormatsResultData.emit(formats.toMutableList()) updateFormatsResultData.emit(formats.toMutableList())
}.onFailure { }
updatingFormats.emit(false) updateFormatsResultDataJob?.start()
updateFormatsResultData.emit(mutableListOf()) updateFormatsResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(false)
updateFormatsResultData.emit(mutableListOf())
}
}
} }
} }
updateFormatsResultDataJob?.start()
} }
} }

View file

@ -33,10 +33,13 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
@ -258,6 +261,9 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
embedThumbClicked = { embedThumbClicked = {
downloadItem.audioPreferences.embedThumb = it downloadItem.audioPreferences.embedThumb = it
}, },
cropThumbClicked = {
downloadItem.audioPreferences.cropThumb = it
},
splitByChaptersClicked = { splitByChaptersClicked = {
downloadItem.audioPreferences.splitByChapters = it downloadItem.audioPreferences.splitByChapters = it
}, },

View file

@ -50,6 +50,7 @@ import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -90,6 +91,33 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
infoUtil = InfoUtil(requireContext()) infoUtil = InfoUtil(requireContext())
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val res: ResultItem?
val dwl: DownloadItem?
if (Build.VERSION.SDK_INT >= 33){
res = arguments?.getParcelable("result", ResultItem::class.java)
dwl = arguments?.getParcelable("downloadItem", DownloadItem::class.java)
}else{
res = arguments?.getParcelable<ResultItem>("result")
dwl = arguments?.getParcelable<DownloadItem>("downloadItem")
}
type = arguments?.getSerializable("type") as Type
if (res == null){
dismiss()
return
}
result = res
currentDownloadItem = dwl
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val downloadItem = getDownloadItem()
arguments?.putParcelable("result", result)
arguments?.putParcelable("downloadItem", downloadItem)
arguments?.putSerializable("type", downloadItem.type)
} }
@SuppressLint("RestrictedApi", "InflateParams") @SuppressLint("RestrictedApi", "InflateParams")
@ -98,26 +126,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null) view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
dialog.setContentView(view) dialog.setContentView(view)
if (Build.VERSION.SDK_INT >= 33){
arguments?.getParcelable("result", ResultItem::class.java)
}else{
arguments?.getParcelable<ResultItem>("result")
}.apply {
if (this == null){
dismiss()
return
}else{
result = this
}
}
type = arguments?.getSerializable("type") as Type
currentDownloadItem = if (Build.VERSION.SDK_INT >= 33){
arguments?.getParcelable("downloadItem", DownloadItem::class.java)
}else{
arguments?.getParcelable<DownloadItem>("downloadItem")
}
dialog.setOnShowListener { dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View) behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
@ -297,16 +305,18 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
} }
download!!.setOnClickListener { download!!.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
resultViewModel.cancelUpdateItemData() withContext(Dispatchers.IO){
resultViewModel.cancelUpdateFormatsItemData() resultViewModel.cancelUpdateItemData()
resultViewModel.cancelUpdateFormatsItemData()
}
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem()
runBlocking {
downloadViewModel.queueDownloads(listOf(item))
}
dismiss()
} }
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem()
runBlocking {
downloadViewModel.queueDownloads(listOf(item))
}
dismiss()
} }
download.setOnLongClickListener { download.setOnLongClickListener {
@ -371,7 +381,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
} }
} }
CoroutineScope(Dispatchers.IO).launch{ lifecycleScope.launch{
downloadViewModel.uiState.collectLatest { res -> downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) { if (res.errorMessage != null) {
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
@ -427,6 +437,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
lifecycleScope.launch { lifecycleScope.launch {
resultViewModel.updatingFormats.collectLatest { resultViewModel.updatingFormats.collectLatest {
if (it){ if (it){
delay(500)
runCatching { runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
@ -528,6 +539,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
} }
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{ private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){ return when(selectedTabPosition){
0 -> { 0 -> {

View file

@ -412,6 +412,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
it.audioPreferences.embedThumb = enabled it.audioPreferences.embedThumb = enabled
} }
}, },
cropThumbClicked = {enabled ->
items.forEach {
it.audioPreferences.cropThumb = enabled
}
},
splitByChaptersClicked = {enabled -> splitByChaptersClicked = {enabled ->
items.forEach { items.forEach {
it.audioPreferences.splitByChapters = enabled it.audioPreferences.splitByChapters = enabled

View file

@ -34,12 +34,15 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.android.material.textfield.TextInputLayout.EndIconMode import com.google.android.material.textfield.TextInputLayout.EndIconMode
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File

View file

@ -31,6 +31,7 @@ import androidx.core.os.bundleOf
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -57,7 +58,11 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/** /**
* A fragment representing a list of Items. * A fragment representing a list of Items.
@ -172,8 +177,32 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
} }
} }
} }
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
lifecycleScope.launch{
downloadViewModel.uiState.collectLatest { res ->
if (res.errorMessage != null) {
withContext(Dispatchers.Main){
kotlin.runCatching {
UiUtil.handleDownloadsResponse(
requireActivity(),
requireActivity().lifecycleScope,
requireActivity().supportFragmentManager,
res,
downloadViewModel,
historyViewModel)
}
}
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
errorMessage = null,
actions = null
)
}
}
}
initMenu() initMenu()
initChips() initChips()
} }

View file

@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UpdateUtil import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar

View file

@ -437,47 +437,51 @@ class InfoUtil(private val context: Context) {
} }
private fun getFormatsFromYTDL(url: String) : List<Format> { private fun getFormatsFromYTDL(url: String) : List<Format> {
val request = YoutubeDLRequest(url) try {
request.addOption("--print", "%(formats)s") val request = YoutubeDLRequest(url)
request.addOption("--print", "%(duration)s") request.addOption("--print", "%(formats)s")
request.addOption("--skip-download") request.addOption("--print", "%(duration)s")
request.addOption("-R", "1") request.addOption("--skip-download")
request.addOption("--socket-timeout", "5") request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5")
if (sharedPreferences.getBoolean("use_cookies", false)){ if (sharedPreferences.getBoolean("use_cookies", false)){
FileUtil.getCookieFile(context){ FileUtil.getCookieFile(context){
request.addOption("--cookies", it) request.addOption("--cookies", it)
}
val useHeader = sharedPreferences.getBoolean("use_header", false)
val header = sharedPreferences.getString("useragent_header", "")
if (useHeader && !header.isNullOrBlank()){
request.addOption("--add-header","User-Agent:${header}")
}
} }
val useHeader = sharedPreferences.getBoolean("use_header", false) val proxy = sharedPreferences.getString("proxy", "")
val header = sharedPreferences.getString("useragent_header", "") if (proxy!!.isNotBlank()) {
if (useHeader && !header.isNullOrBlank()){ request.addOption("--proxy", proxy)
request.addOption("--add-header","User-Agent:${header}")
} }
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
res.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(res.out)
}
val json = results[0]
val jsonArray = JSONArray(json)
return parseYTDLFormats(jsonArray)
}catch (e: Exception){
return listOf()
} }
val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()) {
request.addOption("--proxy", proxy)
}
val res = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = try {
val lineSeparator = System.getProperty("line.separator")
res.out.split(lineSeparator!!).toTypedArray()
} catch (e: Exception) {
arrayOf(res.out)
}
val json = results[0]
val jsonArray = JSONArray(json)
return parseYTDLFormats(jsonArray)
} }
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){ fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
val urlsFile = File(context.cacheDir, "urls.txt") val urlsFile = File(context.cacheDir, "urls.txt")
urlsFile.delete() urlsFile.delete()
urlsFile.createNewFile() urlsFile.createNewFile()
@ -575,6 +579,7 @@ class InfoUtil(private val context: Context) {
request.addOption("-j") request.addOption("-j")
request.addOption("--skip-download") request.addOption("--skip-download")
request.addOption("-R", "1") request.addOption("-R", "1")
request.addOption("--compat-options", "manifest-filesize-approx")
request.addOption("--socket-timeout", "5") request.addOption("--socket-timeout", "5")
if (sharedPreferences.getBoolean("use_cookies", false)){ if (sharedPreferences.getBoolean("use_cookies", false)){
@ -1016,7 +1021,7 @@ class InfoUtil(private val context: Context) {
val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']" val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']"
if(downloadItem.type != DownloadViewModel.Type.command){ if(downloadItem.type != DownloadViewModel.Type.command){
request.addOption("--trim-filenames", downDir.absolutePath.length + 120) request.addOption("--trim-filenames", downDir.absolutePath.length + 117)
if (downloadItem.SaveThumb) { if (downloadItem.SaveThumb) {
request.addOption("--write-thumbnail") request.addOption("--write-thumbnail")
@ -1161,7 +1166,8 @@ class InfoUtil(private val context: Context) {
if (downloadItem.audioPreferences.embedThumb) { if (downloadItem.audioPreferences.embedThumb) {
request.addOption("--embed-thumbnail") request.addOption("--embed-thumbnail")
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg") if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
if (sharedPreferences.getBoolean("crop_thumbnail", true)){ val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
if (cropThumb){
try { try {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt") val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"" val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
@ -1222,11 +1228,14 @@ class InfoUtil(private val context: Context) {
val f = StringBuilder() val f = StringBuilder()
if(!usingGenericFormat){ if(!usingGenericFormat){
if (preferredAudioCodec.isNotBlank()){ if (audiof.isBlank()){
f.append("$videof+$aCodecPref/") f.append("$videof/bv/best")
}else{
f.append("$videof+$audiof/")
if (preferredAudioCodec.isNotBlank())
f.append("$videof+$aCodecPref/")
f.append("$videof/best")
} }
val aa = if (audiof.isNotBlank()) "+$audiof" else ""
f.append("$videof$aa/$videof/best")
if (audiof.contains("+")){ if (audiof.contains("+")){
request.addOption("--audio-multistreams") request.addOption("--audio-multistreams")

View file

@ -75,6 +75,14 @@ class NotificationUtil(var context: Context) {
channel.description = description channel.description = description
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
//finished or errored downloads
name = resources.getString(R.string.errored_downloads)
description =
resources.getString(R.string.errored_download_notification_channel_description)
channel = NotificationChannel(DOWNLOAD_ERRORED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
channel.description = description
notificationManager.createNotificationChannel(channel)
//misc //misc
name = resources.getString(R.string.misc) name = resources.getString(R.string.misc)
description = "" description = ""
@ -264,7 +272,7 @@ class NotificationUtil(var context: Context) {
logID: Long?, logID: Long?,
res: Resources res: Resources
) { ) {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID) val notificationBuilder = getBuilder(DOWNLOAD_ERRORED_CHANNEL_ID)
val bundle = Bundle() val bundle = Bundle()
if (logID != null){ if (logID != null){
@ -304,7 +312,7 @@ class NotificationUtil(var context: Context) {
notificationBuilder.setContentIntent(errorPendingIntent) notificationBuilder.setContentIntent(errorPendingIntent)
notificationBuilder.addAction(0, res.getString(R.string.logs), errorPendingIntent) notificationBuilder.addAction(0, res.getString(R.string.logs), errorPendingIntent)
} }
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build()) notificationManager.notify(DOWNLOAD_ERRORED_NOTIFICATION_ID, notificationBuilder.build())
} }
@ -540,14 +548,16 @@ class NotificationUtil(var context: Context) {
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1" const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2" const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
const val DOWNLOAD_FINISHED_CHANNEL_ID = "3" const val DOWNLOAD_FINISHED_CHANNEL_ID = "3"
const val DOWNLOAD_WORKER_CHANNEL_ID = "5"
const val DOWNLOAD_MISC_CHANNEL_ID = "4"
const val DOWNLOAD_ERRORED_CHANNEL_ID = "6"
const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3 const val DOWNLOAD_FINISHED_NOTIFICATION_ID = 3
const val DOWNLOAD_RESUME_NOTIFICATION_ID = 40000 const val DOWNLOAD_RESUME_NOTIFICATION_ID = 40000
const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5 const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 5
const val FORMAT_UPDATING_NOTIFICATION_ID = 6
const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7 const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 7
const val DOWNLOAD_MISC_CHANNEL_ID = "4" const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 6
const val DOWNLOAD_MISC_NOTIFICATION_ID = 4
const val DOWNLOAD_WORKER_CHANNEL_ID = "5"
private const val PROGRESS_MAX = 100 private const val PROGRESS_MAX = 100
private const val PROGRESS_CURR = 0 private const val PROGRESS_CURR = 0
} }

View file

@ -36,6 +36,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isNotEmpty
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@ -189,8 +190,13 @@ object UiUtil {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) { override fun afterTextChanged(p0: Editable?) {
ok.isEnabled = ok.isEnabled = title.editText!!.text.isNotEmpty() &&
!(title.editText!!.text.isEmpty() || content.editText!!.text.isEmpty()) content.editText!!.text.isNotEmpty() &&
if (extraCommandsSwitch.isChecked){
((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked))
}else{
true
}
} }
}) })
@ -198,8 +204,14 @@ object UiUtil {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) { override fun afterTextChanged(p0: Editable?) {
ok.isEnabled = ok.isEnabled = title.editText!!.text.isNotEmpty() &&
(title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()) content.editText!!.text.isNotEmpty() &&
if (extraCommandsSwitch.isChecked){
((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked))
}else{
true
}
if (content.editText!!.text.isNotEmpty()){ if (content.editText!!.text.isNotEmpty()){
content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_all) content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_all)
}else{ }else{
@ -241,11 +253,11 @@ object UiUtil {
} }
extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b -> extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
} }
extraCommandsVideo.setOnCheckedChangeListener { compoundButton, b -> extraCommandsVideo.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
} }
commandTemplateViewModel.shortcuts.observe(lifeCycle){ commandTemplateViewModel.shortcuts.observe(lifeCycle){
@ -1210,6 +1222,7 @@ object UiUtil {
context: Activity, context: Activity,
items: List<DownloadItem>, items: List<DownloadItem>,
embedThumbClicked: (Boolean) -> Unit, embedThumbClicked: (Boolean) -> Unit,
cropThumbClicked: (Boolean) -> Unit,
splitByChaptersClicked: (Boolean) -> Unit, splitByChaptersClicked: (Boolean) -> Unit,
filenameTemplateSet: (String) -> Unit, filenameTemplateSet: (String) -> Unit,
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit, sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
@ -1218,9 +1231,18 @@ object UiUtil {
){ ){
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb) val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
embedThumb!!.isChecked = items.all { it.audioPreferences.embedThumb } embedThumb!!.isChecked = items.all { it.audioPreferences.embedThumb }
cropThumb.isVisible = embedThumb.isChecked
embedThumb.setOnClickListener { embedThumb.setOnClickListener {
embedThumbClicked(embedThumb.isChecked) embedThumbClicked(embedThumb.isChecked)
cropThumb.isVisible = embedThumb.isChecked
}
cropThumb!!.isChecked = items.all { it.audioPreferences.cropThumb == true }
cropThumb.setOnClickListener {
cropThumbClicked(cropThumb.isChecked)
} }
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters) val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)

View file

@ -182,11 +182,15 @@ class UpdateUtil(var context: Context) {
} }
updatingYTDL = true updatingYTDL = true
val channelMap = mapOf(
"stable" to YoutubeDL.UpdateChannel._STABLE,
"nightly" to YoutubeDL.UpdateChannel._NIGHTLY,
"master" to YoutubeDL.UpdateChannel._MASTER,
)
val channel = sharedPreferences.getString("ytdlp_source", "nightly")
try { try {
YoutubeDL.getInstance().updateYoutubeDL( YoutubeDL.getInstance().updateYoutubeDL(context, channelMap[channel] ?: YoutubeDL.UpdateChannel._NIGHTLY).apply {
context, if (sharedPreferences.getBoolean("nightly_ytdl", false) ) YoutubeDL.UpdateChannel._NIGHTLY else YoutubeDL.UpdateChannel._STABLE
).apply {
updatingYTDL = false updatingYTDL = false
} }
}catch (e: Exception){ }catch (e: Exception){

View file

@ -102,6 +102,7 @@ class DownloadWorker(
notificationUtil.notify(downloadItem.id.toInt(), notification) notificationUtil.notify(downloadItem.id.toInt(), notification)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
val request = infoUtil.buildYoutubeDLRequest(downloadItem) val request = infoUtil.buildYoutubeDLRequest(downloadItem)
downloadItem.status = DownloadRepository.Status.Active.toString() downloadItem.status = DownloadRepository.Status.Active.toString()
@ -142,8 +143,6 @@ class DownloadWorker(
dao.update(downloadItem) dao.update(downloadItem)
} }
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
runCatching { runCatching {
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line -> YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id)) setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
@ -212,11 +211,17 @@ class DownloadWorker(
//put download in history //put download in history
val incognito = sharedPreferences.getBoolean("incognito", false) val incognito = sharedPreferences.getBoolean("incognito", false)
if (!incognito) { if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000 if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) {
val file = File(finalPaths?.first()!!) Looper.prepare().run {
downloadItem.format.filesize = file.length() Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString) }
historyDao.insert(historyItem) }else{
val unixTime = System.currentTimeMillis() / 1000
val file = File(finalPaths?.first()!!)
downloadItem.format.filesize = file.length()
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixTime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString)
historyDao.insert(historyItem)
}
} }
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt()) notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M20,6h-8l-2,-2H4C2.9,4 2.01,4.9 2.01,6L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8C22,6.9 21.1,6 20,6zM14,16H6v-2h8V16zM18,12H6v-2h12V12z"/>
</vector>

View file

@ -33,6 +33,15 @@
android:checked="false" android:checked="false"
android:text="@string/embed_thumb"/> android:text="@string/embed_thumb"/>
<com.google.android.material.chip.Chip
android:id="@+id/crop_thumb"
style="@style/Widget.Material3.Chip.Filter.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:visibility="gone"
android:text="@string/crop_thumb"/>
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/split_by_chapters" android:id="@+id/split_by_chapters"
style="@style/Widget.Material3.Chip.Filter.Elevated" style="@style/Widget.Material3.Chip.Filter.Elevated"
@ -41,22 +50,6 @@
android:checked="false" android:checked="false"
android:text="@string/split_by_chapters"/> android:text="@string/split_by_chapters"/>
<com.google.android.material.chip.Chip
android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_cut"
android:text="@string/cut"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>
@ -103,6 +96,21 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/cut"
style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_cut"
android:text="@string/cut"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>

View file

@ -6,7 +6,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:checkable="true" android:checkable="true"
android:paddingHorizontal="20dp" android:paddingHorizontal="20dp"
android:paddingVertical="5dp" android:paddingBottom="5dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
@ -18,8 +18,9 @@
style="@style/Widget.Material3.Button.TonalButton.Icon" style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:cornerRadius="10dp"
android:autoLink="all" android:autoLink="all"
android:gravity="start" android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
app:layout_constraintEnd_toStartOf="@+id/already_exists_edit" app:layout_constraintEnd_toStartOf="@+id/already_exists_edit"

View file

@ -133,6 +133,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:visibility="gone"
android:checked="true" android:checked="true"
android:text="@string/audio" /> android:text="@string/audio" />
@ -141,6 +142,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="true" android:checked="true"
android:visibility="gone"
android:text="@string/video" /> android:text="@string/video" />
</LinearLayout> </LinearLayout>

View file

@ -308,7 +308,6 @@
<string name="export_file">ファイルのエクスポート</string> <string name="export_file">ファイルのエクスポート</string>
<string name="schedule">スケジュール</string> <string name="schedule">スケジュール</string>
<string name="user_agent_header">User-Agent ヘッダー</string> <string name="user_agent_header">User-Agent ヘッダー</string>
<string name="skip">スキップ</string>
<string name="license">ライセンス</string> <string name="license">ライセンス</string>
<string name="download_already_exists_summary">再度ダウンロードをしますか\?</string> <string name="download_already_exists_summary">再度ダウンロードをしますか\?</string>
<string name="modify_download_card">ダウンロードカードの変更</string> <string name="modify_download_card">ダウンロードカードの変更</string>

View file

@ -1081,4 +1081,17 @@
<item>cookies</item> <item>cookies</item>
</array> </array>
<array name="ytdlp_source">
<item>@string/defaultValue</item>
<item>@string/update_ytdl_nightly</item>
<item>@string/update_ytdl_master</item>
</array>
<array name="ytdlp_source_values">
<item>stable</item>
<item>nightly</item>
<item>master</item>
</array>
</resources> </resources>

View file

@ -232,7 +232,9 @@
<string name="monochrome">Monochrome</string> <string name="monochrome">Monochrome</string>
<string name="downloaded">"Downloaded: "</string> <string name="downloaded">"Downloaded: "</string>
<string name="finished_download_notification_channel_name">Finished Downloads</string> <string name="finished_download_notification_channel_name">Finished Downloads</string>
<string name="finished_download_notification_channel_description">Notification for finished or faulty downloads</string> <string name="finished_download_notification_channel_description">Notification for finished downloads</string>
<string name="errored_downloads">Errored Downloads</string>
<string name="errored_download_notification_channel_description">Notification for faulty downloads</string>
<string name="quick_download">Quick Download</string> <string name="quick_download">Quick Download</string>
<string name="quick_download_summary">Don\'t fetch data in the download card</string> <string name="quick_download_summary">Don\'t fetch data in the download card</string>
<string name="remove_audio">Remove Audio</string> <string name="remove_audio">Remove Audio</string>
@ -319,4 +321,6 @@
<string name="ignore">Ignore</string> <string name="ignore">Ignore</string>
<string name="modify_download_card">Modify Download Card</string> <string name="modify_download_card">Modify Download Card</string>
<string name="export_file">Export File</string> <string name="export_file">Export File</string>
<string name="ytdl_source">yt-dlp Source</string>
<string name="update_ytdl_master">Master Version of yt-dlp</string>
</resources> </resources>

View file

@ -21,13 +21,15 @@
app:key="ytdl-version" app:key="ytdl-version"
app:title="@string/ytdl_version"/> app:title="@string/ytdl_version"/>
<SwitchPreferenceCompat <ListPreference
android:widgetLayout="@layout/preferece_material_switch" android:defaultValue="nightly"
android:defaultValue="true" android:entries="@array/ytdlp_source"
app:icon="@drawable/ic_nightly" android:entryValues="@array/ytdlp_source_values"
app:key="nightly_ytdl" android:icon="@drawable/baseline_source_24"
android:title="@string/update_ytdl_nightly" app:key="ytdlp_source"
/> app:useSimpleSummaryProvider="true"
app:title="@string/ytdl_source" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"

View file

@ -14,7 +14,7 @@ buildscript {
commonsIoVer = '2.5' commonsIoVer = '2.5'
// supports java 1.6 // supports java 1.6
commonsCompressVer = '1.12' commonsCompressVer = '1.12'
youtubedlAndroidVer = "23b26d55f8" youtubedlAndroidVer = "-SNAPSHOT"
workVer = "2.8.1" workVer = "2.8.1"
composeVer = '1.4.2' composeVer = '1.4.2'
kotlinVer = "1.7.21" kotlinVer = "1.7.21"