This commit is contained in:
zaednasr 2023-12-25 12:50:21 +01:00
parent f2077df2c9
commit 461d4679ac
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
28 changed files with 919 additions and 529 deletions

View file

@ -10,7 +10,7 @@ plugins {
def properties = new Properties() def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 7 def versionMinor = 7
def versionPatch = 0 def versionPatch = 1
def versionBuild = 0 // bump for dogfood builds, public betas, etc. def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = "" def versionExt = ""
@ -189,6 +189,6 @@ dependencies {
implementation "com.anggrayudi:storage:1.5.5" implementation "com.anggrayudi:storage:1.5.5"
implementation 'me.zhanghai.android.fastscroll:library:1.3.0' implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
implementation 'com.google.accompanist:accompanist-webview:0.30.1' implementation 'com.google.accompanist:accompanist-webview:0.30.1'
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha10' implementation 'androidx.compose.material3:material3-android:1.2.0-beta01'
implementation "io.noties.markwon:core:4.6.2" implementation "io.noties.markwon:core:4.6.2"
} }

View file

@ -240,12 +240,14 @@ class MainActivity : BaseActivity() {
if (preferences.getBoolean("auto_update_ytdlp", false)){ if (preferences.getBoolean("auto_update_ytdlp", false)){
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){ kotlin.runCatching {
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) { if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
val version = YoutubeDL.getInstance().version(context) if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
Snackbar.make(findViewById(android.R.id.content), val version = YoutubeDL.getInstance().version(context)
this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]", Snackbar.make(findViewById(android.R.id.content),
Snackbar.LENGTH_LONG).show() this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]",
Snackbar.LENGTH_LONG).show()
}
} }
} }

View file

@ -1,17 +1,15 @@
package com.deniscerri.ytdlnis.database.viewmodel package com.deniscerri.ytdlnis.database.viewmodel
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.view.View
import androidx.compose.material3.formatWithSkeleton
import androidx.core.os.bundleOf
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.App import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
@ -21,10 +19,8 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.repository.ResultRepository import com.deniscerri.ytdlnis.database.repository.ResultRepository
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadAudioFragment
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadVideoFragment
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -34,12 +30,14 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.regex.Pattern import java.util.regex.Pattern
class ResultViewModel(application: Application) : AndroidViewModel(application) {
class ResultViewModel(private val application: Application) : AndroidViewModel(application) {
private val tag: String = "ResultViewModel" private val tag: String = "ResultViewModel"
val repository : ResultRepository val repository : ResultRepository
private val searchHistoryRepository : SearchHistoryRepository private val searchHistoryRepository : SearchHistoryRepository
val items : LiveData<List<ResultItem>> val items : LiveData<List<ResultItem>>
private val infoUtil: InfoUtil private val infoUtil: InfoUtil
private val notificationUtil: NotificationUtil
data class ResultsUiState( data class ResultsUiState(
var processing: Boolean, var processing: Boolean,
var errorMessage: Pair<Int, String>?, var errorMessage: Pair<Int, String>?,
@ -64,6 +62,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
items = repository.allResults.asLiveData() items = repository.allResults.asLiveData()
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
infoUtil = InfoUtil(application) infoUtil = InfoUtil(application)
notificationUtil = NotificationUtil(application)
} }
fun checkTrending() = viewModelScope.launch(Dispatchers.IO){ fun checkTrending() = viewModelScope.launch(Dispatchers.IO){
@ -117,11 +116,23 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
} }
} }
if (!isForegrounded()){
notificationUtil.showQueriesFinished()
}
uiState.update {it.copy(processing = false)} uiState.update {it.copy(processing = false)}
res res
} }
} }
private fun isForegrounded(): Boolean {
return kotlin.runCatching {
val appProcessInfo = RunningAppProcessInfo()
ActivityManager.getMyMemoryState(appProcessInfo)
appProcessInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND ||
appProcessInfo.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE
}.getOrElse { true }
}
private fun getQueryType(inputQuery: String) : String { private fun getQueryType(inputQuery: String) : String {
var type = "Search" var type = "Search"
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)") val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
@ -201,14 +212,14 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
updatingData.emit(false) updatingData.emit(false)
updateResultData.emit(result) updateResultData.emit(result)
} }
}
updateResultDataJob?.start() updateResultDataJob?.start()
updateResultDataJob?.invokeOnCompletion { updateResultDataJob?.invokeOnCompletion {
if (it != null){ if (it != null){
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(false) updatingData.emit(false)
updateResultData.emit(mutableListOf()) updateResultData.emit(mutableListOf())
}
} }
} }
} }
@ -230,15 +241,31 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) { if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) {
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) { updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(true) updatingFormats.emit(true)
val formats = infoUtil.getFormats(result.url) val formats = kotlin.runCatching {
updatingFormats.emit(false) infoUtil.getFormats(result.url)
if (formats.isNotEmpty() && isActive) { }.onFailure { e ->
getItemByURL(result.url)?.apply { if (e !is kotlinx.coroutines.CancellationException){
this.formats = formats.toMutableList() uiState.update {it.copy(
update(this) 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())
}
}.getOrNull()
updatingFormats.emit(false)
formats?.apply {
if (formats.isNotEmpty() && isActive) {
getItemByURL(result.url)?.apply {
this.formats = formats.toMutableList()
update(this)
}
updateFormatsResultData.emit(formats.toMutableList())
} }
updateFormatsResultData.emit(formats.toMutableList())
} }
} }
updateFormatsResultDataJob?.start() updateFormatsResultDataJob?.start()
updateFormatsResultDataJob?.invokeOnCompletion { updateFormatsResultDataJob?.invokeOnCompletion {

View file

@ -8,6 +8,7 @@ import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter import android.graphics.ColorMatrixColorFilter
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -22,7 +23,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.HistoryItem import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
@ -89,13 +89,12 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
itemTitle.text = title itemTitle.text = title
// Bottom Info ---------------------------------- // Bottom Info ----------------------------------
val bottomInfo = card.findViewById<TextView>(R.id.downloads_info_bottom) val author = card.findViewById<TextView>(R.id.downloads_info_bottom)
var info = item.author author.text = item.author
if (item.duration.isNotEmpty()) {
if (item.author.isNotEmpty()) info += "" val length = card.findViewById<TextView>(R.id.length)
info += item.duration length.text = item.duration
}
bottomInfo.text = info
// TIME DOWNLOADED ---------------------------------- // TIME DOWNLOADED ----------------------------------
val datetime = card.findViewById<TextView>(R.id.downloads_info_time) val datetime = card.findViewById<TextView>(R.id.downloads_info_time)
@ -122,9 +121,14 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
} }
}) })
thumbnail.alpha = 0.7f thumbnail.alpha = 0.7f
btn.backgroundTintList = ContextCompat.getColorStateList(activity, R.color.black)
btn.imageTintList = ContextCompat.getColorStateList(activity, R.color.white)
} }
if (item.type == DownloadViewModel.Type.audio) { if (item.type == DownloadViewModel.Type.audio) {
if (filePresent) btn.setImageResource(R.drawable.ic_music_downloaded) else btn.setImageResource(R.drawable.ic_music) if (filePresent) btn.setImageResource(R.drawable.ic_music_downloaded) else {
btn.setImageResource(R.drawable.ic_music)
}
} else if (item.type == DownloadViewModel.Type.video) { } else if (item.type == DownloadViewModel.Type.video) {
if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video) if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
}else{ }else{

View file

@ -80,6 +80,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
private lateinit var newCutBtn : Button private lateinit var newCutBtn : Button
private lateinit var resetBtn : Button private lateinit var resetBtn : Button
private lateinit var chipGroup : ChipGroup private lateinit var chipGroup : ChipGroup
private lateinit var suggestedLabel : View
private var timeSeconds by Delegates.notNull<Int>() private var timeSeconds by Delegates.notNull<Int>()
private lateinit var selectedCuts: MutableList<String> private lateinit var selectedCuts: MutableList<String>
@ -127,6 +128,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
okBtn = view.findViewById(R.id.okButton) okBtn = view.findViewById(R.id.okButton)
suggestedChips = view.findViewById(R.id.chapters) suggestedChips = view.findViewById(R.id.chapters)
suggestedChapters = view.findViewById(R.id.suggested_cuts) suggestedChapters = view.findViewById(R.id.suggested_cuts)
suggestedLabel = view.findViewById(R.id.suggestedLabel)
//cut list section //cut list section
@ -451,6 +453,12 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
cutListSection.visibility = View.GONE cutListSection.visibility = View.GONE
rangeSlider.setValues(0F, 100F) rangeSlider.setValues(0F, 100F)
player.seekTo(0) player.seekTo(0)
suggestedChips.children.apply {
this.forEach {
it.isVisible = ! selectedCuts.contains((it as Chip).text)
}
suggestedLabel.isVisible = this.any { it.isVisible }
}
} }
resetBtn.setOnClickListener { resetBtn.setOnClickListener {

View file

@ -39,6 +39,7 @@ 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -69,8 +70,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false) fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext()) infoUtil = InfoUtil(requireContext())
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources) genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -291,6 +292,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
bottomSheet.show(parentFragmentManager, "cutVideoSheet") bottomSheet.show(parentFragmentManager, "cutVideoSheet")
} }
}, },
updateDataClicked = {
CoroutineScope(Dispatchers.IO).launch {
resultItem?.apply {
resultViewModel.updateItemData(this)
}
}
},
extraCommandsClicked = { extraCommandsClicked = {
val callback = object : ExtraCommandsListener { val callback = object : ExtraCommandsListener {
override fun onChangeExtraCommand(c: String) { override fun onChangeExtraCommand(c: String) {

View file

@ -23,6 +23,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.Player.Command
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -34,6 +35,7 @@ import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
@ -47,6 +49,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -69,7 +72,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var historyViewModel: HistoryViewModel private lateinit var historyViewModel: HistoryViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var commandTemplateDao : CommandTemplateDao private lateinit var commandTemplateViewModel : CommandTemplateViewModel
private lateinit var infoUtil: InfoUtil private lateinit var infoUtil: InfoUtil
private lateinit var sharedPreferences : SharedPreferences private lateinit var sharedPreferences : SharedPreferences
private lateinit var updateItem : Button private lateinit var updateItem : Button
@ -89,7 +92,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java] historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java] resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
infoUtil = InfoUtil(requireContext()) infoUtil = InfoUtil(requireContext())
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -166,7 +169,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
var commandTemplateNr = 0 var commandTemplateNr = 0
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
commandTemplateNr = commandTemplateDao.getTotalNumber() commandTemplateNr = commandTemplateViewModel.getTotalNumber()
if (!Patterns.WEB_URL.matcher(result.url).matches()) commandTemplateNr++ if (!Patterns.WEB_URL.matcher(result.url).matches()) commandTemplateNr++
if(commandTemplateNr <= 0){ if(commandTemplateNr <= 0){
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
@ -242,7 +245,16 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
override fun onTabSelected(tab: TabLayout.Tab?) { override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab!!.position == 2 && commandTemplateNr == 0){ if (tab!!.position == 2 && commandTemplateNr == 0){
tabLayout.selectTab(tabLayout.getTabAt(1)) tabLayout.selectTab(tabLayout.getTabAt(1))
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show() val s = Snackbar.make(view, getString(R.string.add_template_first), Snackbar.LENGTH_LONG)
s.setAction(R.string.new_template){
UiUtil.showCommandTemplateCreationOrUpdatingSheet(item = null, context = requireActivity(), lifeCycle = this@DownloadBottomSheetDialog, commandTemplateViewModel = commandTemplateViewModel){
commandTemplateNr = 1
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 1f
tabLayout.selectTab(tabLayout.getTabAt(2))
}
}
s.show()
}else if (tab.position == 1 && isAudioOnly){ }else if (tab.position == 1 && isAudioOnly){
tabLayout.selectTab(tabLayout.getTabAt(0)) tabLayout.selectTab(tabLayout.getTabAt(0))
Toast.makeText(context, getString(R.string.audio_only_item), Toast.LENGTH_SHORT).show() Toast.makeText(context, getString(R.string.audio_only_item), Toast.LENGTH_SHORT).show()

View file

@ -455,6 +455,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.dismiss() bottomSheet.dismiss()
}, },
cutClicked = {}, cutClicked = {},
updateDataClicked = {},
extraCommandsClicked = { extraCommandsClicked = {
val callback = object : ExtraCommandsListener { val callback = object : ExtraCommandsListener {
override fun onChangeExtraCommand(c: String) { override fun onChangeExtraCommand(c: String) {
@ -509,6 +510,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.dismiss() bottomSheet.dismiss()
}, },
cutClicked = {}, cutClicked = {},
updateDataClicked = {},
filenameTemplateSet = { checked -> filenameTemplateSet = { checked ->
items.forEach { it.customFileNameTemplate = checked } items.forEach { it.customFileNameTemplate = checked }
}, },

View file

@ -40,7 +40,9 @@ 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -72,8 +74,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.fragment_download_video, container, false) fragmentView = inflater.inflate(R.layout.fragment_download_video, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext()) infoUtil = InfoUtil(requireContext())
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources) genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -310,6 +312,13 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
bottomSheet.show(parentFragmentManager, "cutVideoSheet") bottomSheet.show(parentFragmentManager, "cutVideoSheet")
} }
}, },
updateDataClicked = {
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
resultItem?.apply {
resultViewModel.updateItemData(this)
}
}
},
filenameTemplateSet = { filenameTemplateSet = {
downloadItem.customFileNameTemplate = it downloadItem.customFileNameTemplate = it
}, },

View file

@ -19,6 +19,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.WindowManager
import android.widget.* import android.widget.*
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
@ -45,6 +46,7 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel 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.util.Extensions.setFullScreen
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.NotificationUtil import com.deniscerri.ytdlnis.util.NotificationUtil
@ -126,11 +128,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
val displayMetrics = DisplayMetrics() val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){ if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
behavior.state = BottomSheetBehavior.STATE_EXPANDED dialog.setFullScreen()
behavior.peekHeight = displayMetrics.heightPixels
}
if (!resources.getBoolean(R.bool.isTablet) && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
behavior.maxWidth = displayMetrics.widthPixels
} }
} }
} }

View file

@ -1,9 +1,11 @@
package com.deniscerri.ytdlnis.util package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.graphics.Outline
import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape import android.graphics.drawable.shapes.OvalShape
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
@ -12,13 +14,23 @@ import android.net.Uri
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.EditText import android.widget.EditText
import android.widget.ScrollView import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.Px
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.whenStarted
import androidx.lifecycle.withStarted
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.neo.highlight.core.Highlight import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.io.File import java.io.File
import java.util.regex.Pattern import java.util.regex.Pattern
@ -109,4 +121,41 @@ object Extensions {
duration?.toIntOrNull()?.div(1000) ?: 0 duration?.toIntOrNull()?.div(1000) ?: 0
}.getOrElse { 0 } }.getOrElse { 0 }
} }
fun Dialog.setFullScreen(
@Px cornerRadius: Int = 0,
skipCollapsed: Boolean = true
) {
check(this is BottomSheetDialog) {
"Dialog must be a BottomSheetBottomSheetDialog."
}
lifecycleScope.launch {
withStarted {
val bottomSheetLayout = findViewById<ViewGroup>(com.google.android.material.R.id.design_bottom_sheet) ?: return@withStarted
with(bottomSheetLayout) {
updateLayoutParams {
height = ViewGroup.LayoutParams.MATCH_PARENT
width = ViewGroup.LayoutParams.MATCH_PARENT
}
clipToOutline = true
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0,
0,
view.width,
view.height + cornerRadius,
cornerRadius.toFloat()
)
}
}
}
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.maxWidth = ViewGroup.LayoutParams.MATCH_PARENT
behavior.skipCollapsed = skipCollapsed
}
}
}
} }

View file

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.util package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
@ -553,6 +554,7 @@ class InfoUtil(private val context: Context) {
urlsFile.delete() urlsFile.delete()
} }
@SuppressLint("RestrictedApi")
fun getFromYTDL(query: String): ArrayList<ResultItem?> { fun getFromYTDL(query: String): ArrayList<ResultItem?> {
val items = arrayListOf<ResultItem?>() val items = arrayListOf<ResultItem?>()
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch") val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
@ -978,6 +980,7 @@ class InfoUtil(private val context: Context) {
} }
} }
@SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{ fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || downloadItem.playlistIndex == null){ val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || downloadItem.playlistIndex == null){
YoutubeDLRequest(downloadItem.url) YoutubeDLRequest(downloadItem.url)
@ -1108,15 +1111,14 @@ class InfoUtil(private val context: Context) {
request.addOption("--force-keyframes-at-cuts") request.addOption("--force-keyframes-at-cuts")
} }
} }
if (filenameTemplate.isBlank()){ filenameTemplate = if (filenameTemplate.isBlank()){
filenameTemplate = "%(section_title& {}|)s %(title)s" "%(section_title& {}|)s%(title)s"
}else{ }else{
filenameTemplate += " %(section_title& {}|)s " "%(section_title& {}|)s$filenameTemplate"
} }
if (downloadItem.downloadSections.split(";").size > 1){ if (downloadItem.downloadSections.split(";").size > 1){
filenameTemplate = "%(autonumber)d. %(section_start& {}|)s $filenameTemplate" filenameTemplate = "%(autonumber)d. %(section_start>%H:%M:%S)s $filenameTemplate"
} }
request.addOption("--output-na-placeholder", " ")
} }
if (sharedPreferences.getBoolean("use_audio_quality", false)){ if (sharedPreferences.getBoolean("use_audio_quality", false)){
@ -1163,7 +1165,7 @@ class InfoUtil(private val context: Context) {
if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "ba", "best", "").contains(audioQualityId)){ if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "ba", "best", "").contains(audioQualityId)){
audioQualityId = "" audioQualityId = ""
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){ }else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
audioQualityId = "worstaudio" audioQualityId = "wa/w"
} }
@ -1224,7 +1226,7 @@ class InfoUtil(private val context: Context) {
if (cropThumb){ 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)'\\\"\""
config.writeText(configData) config.writeText(configData)
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1") request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
request.addOption("--config", config.absolutePath) request.addOption("--config", config.absolutePath)
@ -1296,7 +1298,8 @@ class InfoUtil(private val context: Context) {
if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best") { if (videoF == context.resources.getString(R.string.best_quality) || videoF == "best") {
videoF = "bv" videoF = "bv"
}else if (videoF == context.resources.getString(R.string.worst_quality) || videoF == "worst") { }else if (videoF == context.resources.getString(R.string.worst_quality) || videoF == "worst") {
videoF = "worst" videoF = "wv"
if (audioF == "ba") audioF = "wa"
}else if (defaultFormats.contains(videoF)) { }else if (defaultFormats.contains(videoF)) {
videoF = "bv[height<="+videoF.split("_")[0].dropLast(1)+"]" videoF = "bv[height<="+videoF.split("_")[0].dropLast(1)+"]"
} }
@ -1316,7 +1319,7 @@ class InfoUtil(private val context: Context) {
.filter { it.isNotBlank() } .filter { it.isNotBlank() }
.ifEmpty { .ifEmpty {
val list = mutableListOf<String>() val list = mutableListOf<String>()
if (preferredAudioLanguage.isNotEmpty()) list.add("ba[language^=$preferredAudioLanguage]") if (preferredAudioLanguage.isNotEmpty() && !downloadItem.videoPreferences.removeAudio) list.add("ba[language^=$preferredAudioLanguage]")
list.add(audioF) list.add(audioF)
list list
}.apply { }.apply {
@ -1346,7 +1349,7 @@ class InfoUtil(private val context: Context) {
if (!f.contains(al)) f.append(al) if (!f.contains(al)) f.append(al)
} }
//build format with best audio //build format with best audio
if (!f.contains("$v+ba/")) f.append("$v+ba/") if (!f.contains("$v+ba/") && !f.contains("wa")) f.append("$v+ba/")
//build format with standalone video //build format with standalone video
f.append("$v/") f.append("$v/")
} }

View file

@ -549,6 +549,31 @@ class NotificationUtil(var context: Context) {
notificationManager.notify(FORMAT_UPDATING_FINISHED_NOTIFICATION_ID, notificationBuilder.build()) notificationManager.notify(FORMAT_UPDATING_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
} }
fun showQueriesFinished() {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
val openMultipleDownloads = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.homeFragment)
.createPendingIntent()
notificationBuilder
.setContentTitle(resources.getString(R.string.finished_download_notification_channel_name))
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
.setLargeIcon(
BitmapFactory.decodeResource(
resources,
R.drawable.ic_launcher_foreground_large
)
)
.setContentIntent(openMultipleDownloads)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions()
notificationManager.notify(QUERY_PROCESS_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
}
companion object { companion object {
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"
@ -562,6 +587,7 @@ class NotificationUtil(var context: Context) {
const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 50000 const val DOWNLOAD_UPDATING_NOTIFICATION_ID = 50000
const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 60000 const val DOWNLOAD_ERRORED_NOTIFICATION_ID = 60000
const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 70000 const val FORMAT_UPDATING_FINISHED_NOTIFICATION_ID = 70000
const val QUERY_PROCESS_FINISHED_NOTIFICATION_ID = 80000
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

@ -29,10 +29,14 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.ui.text.font.Typeface
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@ -896,6 +900,7 @@ object UiUtil {
) )
} }
@SuppressLint("RestrictedApi")
fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){ fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){
val builder = MaterialAlertDialogBuilder(context) val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.subtitle_languages)) builder.setTitle(context.getString(R.string.subtitle_languages))
@ -1073,7 +1078,8 @@ object UiUtil {
saveSubtitlesClicked: (Boolean) -> Unit, saveSubtitlesClicked: (Boolean) -> Unit,
subtitleLanguagesSet: (String) -> Unit, subtitleLanguagesSet: (String) -> Unit,
removeAudioClicked: (Boolean) -> Unit, removeAudioClicked: (Boolean) -> Unit,
extraCommandsClicked: () -> Unit extraCommandsClicked: () -> Unit,
updateDataClicked: () -> Unit
){ ){
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles) val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles) val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
@ -1162,7 +1168,7 @@ object UiUtil {
else{ else{
if(items[0].duration.isNotEmpty()){ if(items[0].duration.isNotEmpty()){
val downloadItem = items[0] val downloadItem = items[0]
cut.isEnabled = true cut.alpha = 1f
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
val cutVideoListener = object : VideoCutListener { val cutVideoListener = object : VideoCutListener {
@ -1198,7 +1204,14 @@ object UiUtil {
cutClicked(cutVideoListener) cutClicked(cutVideoListener)
} }
}else{ }else{
cut.isEnabled = false cut.alpha = 0.3f
cut.setOnClickListener {
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
snack.setAction(R.string.update){
updateDataClicked()
}
snack.show()
}
} }
} }
@ -1268,7 +1281,7 @@ object UiUtil {
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit, sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
cutClicked: (VideoCutListener) -> Unit, cutClicked: (VideoCutListener) -> Unit,
extraCommandsClicked: () -> Unit, extraCommandsClicked: () -> Unit,
updateDataClicked: () -> Unit
){ ){
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) val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
@ -1351,7 +1364,7 @@ object UiUtil {
else{ else{
val downloadItem = items[0] val downloadItem = items[0]
if (downloadItem.duration.isNotEmpty()){ if (downloadItem.duration.isNotEmpty()){
cut.isEnabled = true cut.alpha = 1f
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
val cutVideoListener = object : VideoCutListener { val cutVideoListener = object : VideoCutListener {
override fun onChangeCut(list: List<String>) { override fun onChangeCut(list: List<String>) {
@ -1379,7 +1392,14 @@ object UiUtil {
} }
}else{ }else{
cut.isEnabled = false cut.alpha = 0.3f
cut.setOnClickListener {
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
snack.setAction(R.string.update){
updateDataClicked()
}
snack.show()
}
} }
} }
@ -1632,6 +1652,7 @@ object UiUtil {
deleteDialog.show() deleteDialog.show()
} }
@SuppressLint("RestrictedApi")
fun showFilenameTemplateDialog(context: Activity, currentFilename: String, dialogTitle: String = context.getString(R.string.file_name_template), filenameSelected: (f: String) -> Unit){ fun showFilenameTemplateDialog(context: Activity, currentFilename: String, dialogTitle: String = context.getString(R.string.file_name_template), filenameSelected: (f: String) -> Unit){
val builder = MaterialAlertDialogBuilder(context) val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(dialogTitle) builder.setTitle(dialogTitle)
@ -1783,11 +1804,13 @@ object UiUtil {
val builder = MaterialAlertDialogBuilder(context) val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.command)) builder.setTitle(context.getString(R.string.command))
val view = context.layoutInflater.inflate(R.layout.command_dialog, null) val view = context.layoutInflater.inflate(R.layout.command_dialog, null)
val text = view.findViewById<TextView>(R.id.commandText) view.findViewById<TextView>(R.id.commandText).apply {
text.text = command text = command
text.isLongClickable = true isLongClickable = true
text.setTextIsSelectable(true) setTextIsSelectable(true)
text.enableTextHighlight() enableTextHighlight()
setPadding(20, 0, 20, 0)
}
builder.setView(view) builder.setView(view)
builder.setPositiveButton( builder.setPositiveButton(

View file

@ -26,7 +26,6 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.GithubRelease import com.deniscerri.ytdlnis.database.models.GithubRelease
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
@ -38,7 +37,9 @@ import com.yausername.youtubedl_android.YoutubeDL.UpdateStatus
import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration import io.noties.markwon.MarkwonConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
@ -122,7 +123,8 @@ class UpdateUtil(var context: Context) {
) )
.setAllowedOverRoaming(true) .setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(context.getString(R.string.downloading_update)) .setTitle(releaseVersion.name)
.setDescription(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name) .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
) )
@ -176,42 +178,7 @@ class UpdateUtil(var context: Context) {
layoutParams.setMargins(20, 20, 20, 0) layoutParams.setMargins(20, 20, 20, 0)
scrollView.layoutParams = layoutParams scrollView.layoutParams = layoutParams
scrollView.addView(linearLayout) scrollView.addView(linearLayout)
val releases = getGithubReleases()
getGithubReleases().forEach {
(activity.layoutInflater.inflate(R.layout.changelog_item, null) as MaterialCardView).apply {
this.layoutParams = layoutParams
findViewById<TextView>(R.id.version).text = it.tag_name
findViewById<TextView>(R.id.date).text = SimpleDateFormat(
DateFormat.getBestDateTimePattern(
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
val mdText = findViewById<TextView>(R.id.content)
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(context, browserIntent, Bundle())
}
}
}).build()
mw.setMarkdown(mdText, it.body)
val assetGroup = findViewById<ChipGroup>(R.id.assets)
it.assets.forEachIndexed { idx, c ->
val tmp = activity.layoutInflater.inflate(R.layout.suggestion_chip, assetGroup, false) as Chip
tmp.text = c.name
tmp.id = idx
tmp.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
startActivity(context, browserIntent, Bundle())
}
assetGroup!!.addView(tmp)
}
linearLayout.addView(this)
}
}
val changeLogDialog = MaterialAlertDialogBuilder(context) val changeLogDialog = MaterialAlertDialogBuilder(context)
.setTitle(activity.getString(R.string.changelog)) .setTitle(activity.getString(R.string.changelog))
@ -222,6 +189,46 @@ class UpdateUtil(var context: Context) {
changeLogDialog.show() changeLogDialog.show()
} }
CoroutineScope(Dispatchers.IO).launch {
releases.forEach {
(activity.layoutInflater.inflate(R.layout.changelog_item, null) as MaterialCardView).apply {
this.layoutParams = layoutParams
findViewById<TextView>(R.id.version).text = it.tag_name
findViewById<TextView>(R.id.date).text = SimpleDateFormat(
DateFormat.getBestDateTimePattern(
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
val mdText = findViewById<TextView>(R.id.content)
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(context, browserIntent, Bundle())
}
}
}).build()
mw.setMarkdown(mdText, it.body)
val assetGroup = findViewById<ChipGroup>(R.id.assets)
it.assets.forEachIndexed { idx, c ->
val tmp = activity.layoutInflater.inflate(R.layout.suggestion_chip, assetGroup, false) as Chip
tmp.text = c.name
tmp.id = idx
tmp.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
startActivity(context, browserIntent, Bundle())
}
assetGroup!!.addView(tmp)
}
withContext(Dispatchers.Main){
linearLayout.addView(this@apply)
}
}
}
}
}.onFailure { }.onFailure {
if (it.message != null){ if (it.message != null){
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorSurfaceContainerLow"/>
<corners android:topLeftRadius="20dp" />
</shape>

View file

@ -3,114 +3,129 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingBottom="20dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/frame_layout" android:id="@+id/player_constraint"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintWidth_percent=".5"
app:layout_constraintHorizontal_bias="0.0"
app:cardCornerRadius="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/info"
app:layout_constraintTop_toTopOf="parent">
<androidx.media3.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fixed_height"
app:useDefaultControls="true"
app:use_controller="true" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/download_thumb"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_image"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintTop_toTopOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_marginTop="10dp"
android:textStyle="bold"
android:layout_marginHorizontal="10dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="17sp"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<TextView
android:id="@+id/bottom_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
app:layout_constraintVertical_bias="0.0"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" /> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent=".67">
<com.google.android.material.card.MaterialCardView
android:id="@+id/frame_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.button.MaterialButton <androidx.media3.ui.PlayerView
android:id="@+id/download_music" android:id="@+id/video_view"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" android:layout_width="match_parent"
android:layout_width="55dp" android:layout_height="match_parent"
android:layout_marginTop="10dp" app:layout_constraintTop_toTopOf="parent"
android:layout_height="55dp" app:resize_mode="fixed_height"
android:layout_marginHorizontal="10dp" app:useDefaultControls="true"
app:icon="@drawable/ic_music" app:use_controller="true" />
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<com.google.android.material.button.MaterialButton </com.google.android.material.card.MaterialCardView>
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" <com.google.android.material.button.MaterialButton
android:layout_width="55dp" android:id="@+id/download_thumb"
android:layout_marginTop="10dp" style="@style/Widget.Material3.Button.IconButton"
android:layout_height="55dp" android:layout_width="wrap_content"
android:layout_marginHorizontal="10dp" android:layout_height="wrap_content"
app:icon="@drawable/ic_video" app:icon="@drawable/ic_image"
app:layout_constraintEnd_toEndOf="@id/frame_layout" app:iconSize="30dp"
app:layout_constraintTop_toBottomOf="@id/frame_layout" /> app:iconTint="?android:colorAccent"
app:layout_constraintTop_toTopOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="17sp"
android:textStyle="bold"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.277"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_layout" />
<TextView
android:id="@+id/bottom_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
app:layout_constraintVertical_bias="0.0"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="@id/frame_layout"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/app_name"
android:textAlignment="textStart"
android:textSize="15sp"
app:icon="@drawable/ic_link"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/download_music"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/app_name"
android:textAlignment="textStart"
android:textSize="15sp"
app:icon="@drawable/ic_link"
app:layout_constraintEnd_toStartOf="@+id/info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/download_music" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
@ -119,9 +134,8 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="10dp"
android:scrollbars="none" android:scrollbars="none"
app:layout_constraintWidth_percent=".5" app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_bias="0.5" app:layout_constraintStart_toEndOf="@id/player_constraint"
app:layout_constraintStart_toEndOf="@id/frame_layout"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View file

@ -1,334 +1,362 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingBottom="20dp" android:scrollbars="none"
android:layout_height="wrap_content"> android:orientation="vertical"
android:layout_height="wrap_content"
<com.google.android.material.card.MaterialCardView android:fillViewport="true"
android:id="@+id/frame_layout" app:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp" app:layout_constraintStart_toStartOf="parent"
android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent"
app:cardCornerRadius="0dp" xmlns:app="http://schemas.android.com/apk/res-auto"
app:layout_constraintDimensionRatio="H,16:9" xmlns:android="http://schemas.android.com/apk/res/android">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<androidx.media3.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fixed_height"
app:useDefaultControls="true"
app:use_controller="false" />
</com.google.android.material.card.MaterialCardView>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:indeterminate="true"
android:translationZ="10dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/pause"
android:visibility="gone"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_play_arrow_white"
android:layout_width="wrap_content"
android:indeterminate="true"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/rewind"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_rewind_white"
android:layout_marginHorizontal="20dp"
android:layout_width="wrap_content"
android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mute"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_music"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<TextView
android:id="@+id/durationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#80000000"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cut_section"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:paddingBottom="20dp"
app:layout_constraintTop_toBottomOf="@+id/frame_layout"> android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/frame_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" >
<com.google.android.material.slider.RangeSlider
android:id="@+id/rangeSlider" <androidx.media3.ui.PlayerView
android:layout_width="match_parent" android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fixed_height"
app:useDefaultControls="true"
app:use_controller="false" />
</com.google.android.material.card.MaterialCardView>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:indeterminate="true"
android:translationZ="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="10dp" app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
android:valueFrom="00.0"
android:valueTo="100.0"
app:labelBehavior="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:thumbRadius="0dp"
app:trackHeight="20dp" <com.google.android.material.button.MaterialButton
app:values="@array/initial_slider_values" /> android:id="@+id/pause"
android:visibility="gone"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_play_arrow_white"
android:layout_width="wrap_content"
android:indeterminate="true"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/rewind"
android:clickable="false"
app:iconSize="50dp"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="?android:colorAccent"
app:icon="@drawable/exomedia_ic_rewind_white"
android:layout_marginHorizontal="20dp"
android:layout_width="wrap_content"
android:indeterminate="true"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/mute"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_music"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<LinearLayout <TextView
android:id="@+id/linearLayout5" android:id="@+id/durationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="#80000000"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="15sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cut_section"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp" app:layout_constraintTop_toBottomOf="@+id/frame_layout">
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@id/suggested_cuts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rangeSlider"
app:layout_constraintVertical_bias="0.0">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput" <com.google.android.material.slider.RangeSlider
style="@style/Widget.Material3.TextInputLayout.FilledBox" android:id="@+id/rangeSlider"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/start"> android:layout_marginVertical="10dp"
android:valueFrom="00.0"
android:valueTo="100.0"
app:labelBehavior="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:thumbRadius="0dp"
app:trackHeight="20dp"
app:values="@array/initial_slider_values" />
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout> <LinearLayout
android:id="@+id/linearLayout5"
<TextView android:layout_width="match_parent"
android:id="@+id/colon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_marginHorizontal="15dp"
android:paddingHorizontal="10dp" android:orientation="horizontal"
android:text=":" app:layout_constraintBottom_toTopOf="@id/suggested_cuts"
android:textSize="20sp" app:layout_constraintEnd_toEndOf="parent"
android:textStyle="bold" /> app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rangeSlider"
app:layout_constraintVertical_bias="0.0">
<com.google.android.material.textfield.TextInputLayout
<com.google.android.material.textfield.TextInputLayout android:id="@+id/from_textinput"
android:id="@+id/to_textinput" style="@style/Widget.Material3.TextInputLayout.FilledBox"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text" /> android:hint="@string/start">
</com.google.android.material.textfield.TextInputLayout> <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</LinearLayout> </com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/colon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingHorizontal="10dp"
android:text=":"
android:textSize="20sp"
android:textStyle="bold" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/suggested_cuts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout5"
app:layout_constraintVertical_bias="0.0"
>
<TextView
android:id="@+id/suggestedLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/suggested" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chapters"
android:layout_width="wrap_content"
app:chipSpacingVertical="-10dp"
app:chipSpacing="0dp"
android:layout_height="wrap_content"
app:singleLine="true" />
</HorizontalScrollView>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="20dp"
app:layout_constraintTop_toBottomOf="@+id/suggested_cuts"
app:layout_constraintVertical_bias="1.0"
>
<Button
android:id="@+id/cancelButton"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content"
android:text="@string/cancel"
app:icon="@drawable/ic_baseline_delete_outline_24"/>
<Button
android:id="@+id/okButton"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cut"
app:icon="@drawable/ic_check"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout <LinearLayout
android:id="@+id/suggested_cuts" android:id="@+id/list_section"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="20dp" android:paddingHorizontal="20dp"
android:paddingTop="10dp" android:paddingVertical="10dp"
android:orientation="vertical" app:layout_constraintTop_toBottomOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="parent" android:orientation="vertical">
app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout5"
app:layout_constraintVertical_bias="0.0"
>
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/suggested" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup <TextView
android:id="@+id/chapters" android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content" android:layout_width="0dp"
app:chipSpacingVertical="-10dp"
app:chipSpacing="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true" /> android:layout_marginTop="4dp"
android:maxLines="2"
android:singleLine="false"
android:text="@string/cut"
android:textSize="25sp"
app:layout_constraintEnd_toStartOf="@+id/new_cut"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</HorizontalScrollView> <Button
android:id="@+id/reset_all"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/new_cut"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> <Button
android:id="@+id/new_cut"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/new_cut"
app:icon="@drawable/ic_cut"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout </androidx.constraintlayout.widget.ConstraintLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="20dp"
app:layout_constraintTop_toBottomOf="@+id/suggested_cuts"
app:layout_constraintVertical_bias="1.0"
>
<Button <ScrollView
android:id="@+id/cancelButton" android:layout_width="match_parent"
style="@style/Widget.Material3.Button.ElevatedButton" android:layout_height="wrap_content">
android:layout_width="wrap_content"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content"
android:text="@string/cancel"
app:icon="@drawable/ic_baseline_delete_outline_24"/>
<Button <LinearLayout
android:id="@+id/okButton" android:layout_width="match_parent"
style="@style/Widget.Material3.Button.ElevatedButton" android:layout_height="wrap_content">
android:layout_width="wrap_content"
android:layout_height="wrap_content" <com.google.android.material.chip.ChipGroup
android:text="@string/cut" android:id="@+id/cut_list_chip_group"
app:icon="@drawable/ic_check"/> android:paddingTop="10dp"
app:chipSpacingVertical="-7dp"
app:chipSpacingHorizontal="5dp"
app:singleSelection="true"
app:selectionRequired="false"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
</ScrollView>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/force_keyframes"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:checked="true"
android:textStyle="bold"
android:textSize="15sp"
android:text="@string/force_keyframes"/>
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout </androidx.core.widget.NestedScrollView>
android:id="@+id/list_section"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
app:layout_constraintTop_toBottomOf="@+id/frame_layout"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:maxLines="2"
android:singleLine="false"
android:text="@string/cut"
android:textSize="25sp"
app:layout_constraintEnd_toStartOf="@+id/new_cut"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/reset_all"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/new_cut"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/new_cut"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/new_cut"
app:icon="@drawable/ic_cut"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.chip.ChipGroup
android:id="@+id/cut_list_chip_group"
android:paddingTop="10dp"
app:singleSelection="true"
app:selectionRequired="false"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/force_keyframes"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:checked="true"
android:textStyle="bold"
android:textSize="15sp"
android:text="@string/force_keyframes"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -49,10 +49,11 @@
<TextView <TextView
android:id="@+id/bottom_sheet_title" android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:maxLines="2" android:maxLines="2"
android:layout_marginEnd="20dp"
android:singleLine="false" android:singleLine="false"
android:text="@string/download" android:text="@string/download"
android:textSize="25sp" android:textSize="25sp"
@ -84,9 +85,10 @@
<TextView <TextView
android:id="@+id/bottom_sheet_subtitle" android:id="@+id/bottom_sheet_subtitle"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:maxLines="2" android:maxLines="2"
android:layout_marginEnd="20dp"
android:singleLine="false" android:singleLine="false"
android:text="@string/configure_download" android:text="@string/configure_download"
android:textSize="12sp" android:textSize="12sp"

View file

@ -37,36 +37,62 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <LinearLayout
android:id="@+id/downloads_title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:ellipsize="end" android:dividerPadding="5dp"
android:maxLines="3" android:orientation="vertical"
android:paddingHorizontal="10dp" app:layout_constraintBottom_toTopOf="@+id/downloads_info_time"
android:paddingTop="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="10"
android:textColor="#FFF"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/downloads_info_bottom"
app:layout_constraintEnd_toStartOf="@+id/downloads_download_button_type" app:layout_constraintEnd_toStartOf="@+id/downloads_download_button_type"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/downloads_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="2"
android:textColor="#FFF"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/downloads_info_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="2"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
android:textColor="#FFF"
android:textSize="11sp"
android:textStyle="bold" />
</LinearLayout>
<TextView <TextView
android:id="@+id/downloads_info_bottom" android:id="@+id/length"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="10dp"
android:ellipsize="end" android:ellipsize="end"
android:gravity="bottom" android:gravity="bottom"
android:maxLines="1" android:maxLines="1"
android:padding="10dp"
android:scrollbars="none" android:scrollbars="none"
android:shadowColor="@color/black" android:shadowColor="@color/black"
android:shadowDx="4" android:shadowDx="4"
@ -77,10 +103,8 @@
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/downloads_info_time" app:layout_constraintEnd_toStartOf="@+id/downloads_info_time"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintVertical_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/downloads_download_button_type" />
<TextView <TextView
android:id="@+id/downloads_info_time" android:id="@+id/downloads_info_time"

View file

@ -20,6 +20,7 @@
<LinearLayout <LinearLayout
android:id="@+id/linearLayout2" android:id="@+id/linearLayout2"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginEnd="20dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@+id/download_button_type" app:layout_constraintEnd_toStartOf="@+id/download_button_type"

View file

@ -37,6 +37,7 @@
<string-array name="video_formats"> <string-array name="video_formats">
<item>@string/worst_quality</item> <item>@string/worst_quality</item>
<item>240p</item>
<item>360p</item> <item>360p</item>
<item>480p</item> <item>480p</item>
<item>720p</item> <item>720p</item>
@ -48,6 +49,7 @@
<string-array name="video_formats_values"> <string-array name="video_formats_values">
<item>worst</item> <item>worst</item>
<item>240p_ytdlnisgeneric</item>
<item>360p_ytdlnisgeneric</item> <item>360p_ytdlnisgeneric</item>
<item>480p_ytdlnisgeneric</item> <item>480p_ytdlnisgeneric</item>
<item>720p_ytdlnisgeneric</item> <item>720p_ytdlnisgeneric</item>

View file

@ -330,4 +330,5 @@
<string name="changelog">Changelog</string> <string name="changelog">Changelog</string>
<string name="source">Source</string> <string name="source">Source</string>
<string name="app">App</string> <string name="app">App</string>
<string name="cut_unavailable">You need to update the item data in order to be able to use cut feature</string>
</resources> </resources>

View file

@ -32,7 +32,7 @@
app:title="@string/auto_update_ytdlp" /> app:title="@string/auto_update_ytdlp" />
<ListPreference <ListPreference
android:defaultValue="yt-dlp" android:defaultValue="piped"
android:entries="@array/formats_source" android:entries="@array/formats_source"
android:entryValues="@array/formats_source_values" android:entryValues="@array/formats_source_values"
android:icon="@drawable/baseline_manage_search_24" android:icon="@drawable/baseline_manage_search_24"

View file

@ -16,14 +16,14 @@ buildscript {
commonsCompressVer = '1.12' commonsCompressVer = '1.12'
youtubedlAndroidVer = "-SNAPSHOT" youtubedlAndroidVer = "-SNAPSHOT"
youtubedlAndroidVerNoPyCrypto = "97bee6c0e8" youtubedlAndroidVerNoPyCrypto = "97bee6c0e8"
workVer = "2.8.1" workVer = "2.9.0"
composeVer = '1.4.2' composeVer = '1.4.2'
kotlinVer = "1.7.21" kotlinVer = "1.7.21"
coroutineVer = '1.7.3' coroutineVer = '1.7.3'
retrofitVer = "2.9.0" retrofitVer = "2.9.0"
kodeinVer = "7.16.0" kodeinVer = "7.16.0"
navVer = "2.7.5" navVer = "2.7.6"
media3_version = "1.1.1" media3_version = "1.2.0"
agp_version = '8.1.0' agp_version = '8.1.0'
agp_version1 = '7.4.2' agp_version1 = '7.4.2'
roomVer = '2.5.2' roomVer = '2.5.2'

View file

@ -0,0 +1,79 @@
## What's New
- removed trim filenames from command type downloads
- fixed app not showing formats when it has generic formats
- fixed author getting written as NA for kick videos
- added separate channel for the worker notification that users can turn off
- include search history when searching
- removed scroll bug from command tab
- added spacing between command template title and ok button in selection card
- made download progress not dublicate in terminal
- made ability to store terminal state
- added ability to create multiple terminal instances and show them as a list similar to download queue
- fixed thumbnail download not working
- fixed app crashing when clicking on format updated notification
- fixed app crashing when double clicking format on multiple download card
- added custom sponsorblock api preference
- removed contextual app bar when you its enabled and the user taps the log button in the erorred tab
- made app always show quick download card and asynchoronously load data. Quick Download now if its on, it wont load data at all
- added shimmer when loading data in the download card
- fixed app showing no formats if there were no common formats. Now it will give you generic formats
- made open command template list be half the screen, shortcuts third of the screen so the user can see what its being added
- fixed sometimes app slipping queued downloads even though its told to pause all
- fixed trim filenames cutting files too short
- made mediastore scanning of files one by one
- fixed filename template not working in multiple download card
- fixed -F in terminal not being inline
- added preferred audio codec
- made auto update on boot if there are no active downloads
- fixed format text overlapping
- added a new error dialog in cases yt-dlp data fetching in the home screen fails. You can copy the log
- formats auto updating as soon as the download card opens if auto-update is on
- added preferred audio format always in the video tab
- made app post downloads for queue in chunks
- made app always save logs in case it fails, and if succeeds and logs are off it deletes it
- fixed app navigating to home screen when cancelling download card in history screen
- added a button to skip incoming app update so it wont bother you anymore
- fixed settings not restoring some fields
- fixed crunchyroll not working with cookies
- added search for command templates
- added sort filtering for command templates
- added all shortcuts inside filename template creation dialog. Long click them to see the explanation
- added preference to hide elements from the download card
- made avc1 and m4a as preferred codecs for noobs
- u can edit the duplicated download item right from the error dialog or access the history item to view the file
- added extractor args lang when searching in yt-dlp
- removed webarchive search engine since its not supported anymore
- fixed terminal prematurely closing
- made format auto-update on by default for new users
- fixed main activity getting removed from recents when using the app with rvx
- added ability to have the last used command template for the next download
- fixed app crashing in landscape logs
- fixed app constantly going back to home in landscape or config change. now it keeps state
- add subtitle language suggestions in the subtitle dialog
- made command template scroll state hold even if fragment is destroyed
- added slovak sk language
- fixed terminal icon being blank in white mode, and now its red
- fixed share files from notification showing 2 files even though its 1
- fixed history item not being deleted from the bottom sheet
- cleared outdated player urls for stale result items
- added export cookies as file
- added export command templates for selected templates
- added icons for history details sheet chips
- added markdown in the update dialog
- and other random bug fixes
## Autogenerated Changes
* Translations update from Hosted Weblate by @weblate in https://github.com/deniscerri/ytdlnis/pull/283
* Create README-id.MD (Indonesian README) by @teddysulaimanGL in https://github.com/deniscerri/ytdlnis/pull/291
* Translations update from Hosted Weblate by @weblate in https://github.com/deniscerri/ytdlnis/pull/288
* Update README-pt.md by @gigoloinc in https://github.com/deniscerri/ytdlnis/pull/293
* Translations update from Hosted Weblate by @weblate in https://github.com/deniscerri/ytdlnis/pull/292
* Translations update from Hosted Weblate by @weblate in https://github.com/deniscerri/ytdlnis/pull/301
* Translations update from Hosted Weblate by @weblate in https://github.com/deniscerri/ytdlnis/pull/305
* Translations update from Hosted Weblate by @weblate in https://github.com/deniscerri/ytdlnis/pull/310
## New Contributors
* @teddysulaimanGL made their first contribution in https://github.com/deniscerri/ytdlnis/pull/291
* @gigoloinc made their first contribution in https://github.com/deniscerri/ytdlnis/pull/293
**Full Changelog**: https://github.com/deniscerri/ytdlnis/compare/v1.6.7...v1.6.8

View file

@ -0,0 +1,25 @@
## What's New
- Errored downloads sometimes had no title if it was quick downloaded. showed url instead
- Fixed logs not being highlightable
- Fixed app crashing when moving to landscape when having download card on
- same thing for the details card
- Fixed app crashing if you pressed download before data is loaded
- Fixed app going to main activity when using rvx
- Fixed errored downloads log button crashing the app
- added hungarian
- added serbian
- added ability to enable/disable swipe gestures on any screen
- added ability to choose whether extra command applies to audio video or both
- hide search providers if the user has typed out an url in the searchview
- 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 card showing extra command checkboxes even though extra command is disabled
- fix preferred audio codec disrupting preferred audio id
- made command tab sync title and author changes in the download item
- fixed app duplicating --download-sections when spamming the extra commands page
- added BUFFER SIZE as a preference in download settings
- fixed prx series search engine not working

View file

@ -0,0 +1,31 @@
## What's New
- fixed error notification not being dismissed and having a progress bar
- fixed editing filename template not using multiple copies of the same tag and writing at the cursor
- fixed appending search items in the search view not working for links
- fixed terminal removing any instance of yt-dlp in the command instead of just the beginning
- added ability to long press an item in the format details sheet to see the full string, and copy it/strings
- ellipsised really long titles and authors in history/download details bottom sheet
- now u can see all available piped instances in the piped instance dialog for you to choose
- removed really long format command and replaced them with -S format sorting
- fixed app not hiding adjust templates if user unchecked it
- added ability to show the command that was used in a history item, u can also see that in a queued,cancelled ... download
- Implemented preferred Audio Language. App will automatically choose an audio with your preference if it can find it, both in the download card also if you quick downloaded it
- added subtitle language codes suggestions in the settings page
- made the extractor chips in history page Sentence case
- added a changelog screen where you can see recent releases and you can download the apks from it too
- prevented app from crashing when trying to backup from a corrupted backup
- added uploader_id as fallback for author data fetching in yt-dlp in case others are empty
- fixed null pointer exception when running the update multiple items formats worker
- added the seconds where the cut starts on downloads with cuts in them
- made autonumbers be normal numbers instead of being 5 digits
- fixed filename templates in cut files and added the index in the beginning and fixed bugs if the users left the template as empty
- added 240p as a generic format
## Intent integration with the app
You can use intents or apps like tasker or macrodroid to run commands to the app to run a download without user interaction
Accepted variables:
TYPE -> it can be: audio,video,command
BACKGROUND -> whether the app downloads on its own and wont show the download card if you have it on
COMMAND -> if your preferred type is audio/video or you set the TYPE variable as so, this will be appended to your Extra Commands string. If the type is command, the whole command will be used for the download
The intent that needs to be created is of android.intent.action.SEND and the intent text should be the url that you need to download.