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 versionMajor = 1
def versionMinor = 7
def versionPatch = 0
def versionPatch = 1
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = ""
@ -189,6 +189,6 @@ dependencies {
implementation "com.anggrayudi:storage:1.5.5"
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
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"
}

View file

@ -240,12 +240,14 @@ class MainActivity : BaseActivity() {
if (preferences.getBoolean("auto_update_ytdlp", false)){
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
val version = YoutubeDL.getInstance().version(context)
Snackbar.make(findViewById(android.R.id.content),
this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]",
Snackbar.LENGTH_LONG).show()
kotlin.runCatching {
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
val version = YoutubeDL.getInstance().version(context)
Snackbar.make(findViewById(android.R.id.content),
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
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
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.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.App
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.repository.ResultRepository
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.NotificationUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@ -34,12 +30,14 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
class ResultViewModel(application: Application) : AndroidViewModel(application) {
class ResultViewModel(private val application: Application) : AndroidViewModel(application) {
private val tag: String = "ResultViewModel"
val repository : ResultRepository
private val searchHistoryRepository : SearchHistoryRepository
val items : LiveData<List<ResultItem>>
private val infoUtil: InfoUtil
private val notificationUtil: NotificationUtil
data class ResultsUiState(
var processing: Boolean,
var errorMessage: Pair<Int, String>?,
@ -64,6 +62,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
items = repository.allResults.asLiveData()
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
infoUtil = InfoUtil(application)
notificationUtil = NotificationUtil(application)
}
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)}
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 {
var type = "Search"
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)
updateResultData.emit(result)
}
}
updateResultDataJob?.start()
updateResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(false)
updateResultData.emit(mutableListOf())
updateResultDataJob?.start()
updateResultDataJob?.invokeOnCompletion {
if (it != null){
viewModelScope.launch(Dispatchers.IO) {
updatingData.emit(false)
updateResultData.emit(mutableListOf())
}
}
}
}
@ -230,15 +241,31 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) {
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
updatingFormats.emit(true)
val formats = infoUtil.getFormats(result.url)
updatingFormats.emit(false)
if (formats.isNotEmpty() && isActive) {
getItemByURL(result.url)?.apply {
this.formats = formats.toMutableList()
update(this)
val formats = kotlin.runCatching {
infoUtil.getFormats(result.url)
}.onFailure { e ->
if (e !is kotlinx.coroutines.CancellationException){
uiState.update {it.copy(
processing = false,
errorMessage = Pair(R.string.no_results, e.message.toString()),
actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
)}
Log.e(tag, e.toString())
}
}.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?.invokeOnCompletion {

View file

@ -8,6 +8,7 @@ import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -22,7 +23,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.HistoryItem
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.floatingactionbutton.FloatingActionButton
import com.squareup.picasso.Picasso
@ -89,13 +89,12 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
itemTitle.text = title
// Bottom Info ----------------------------------
val bottomInfo = card.findViewById<TextView>(R.id.downloads_info_bottom)
var info = item.author
if (item.duration.isNotEmpty()) {
if (item.author.isNotEmpty()) info += ""
info += item.duration
}
bottomInfo.text = info
val author = card.findViewById<TextView>(R.id.downloads_info_bottom)
author.text = item.author
val length = card.findViewById<TextView>(R.id.length)
length.text = item.duration
// TIME DOWNLOADED ----------------------------------
val datetime = card.findViewById<TextView>(R.id.downloads_info_time)
@ -122,9 +121,14 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
}
})
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 (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) {
if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
}else{

View file

@ -80,6 +80,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
private lateinit var newCutBtn : Button
private lateinit var resetBtn : Button
private lateinit var chipGroup : ChipGroup
private lateinit var suggestedLabel : View
private var timeSeconds by Delegates.notNull<Int>()
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)
suggestedChips = view.findViewById(R.id.chapters)
suggestedChapters = view.findViewById(R.id.suggested_cuts)
suggestedLabel = view.findViewById(R.id.suggestedLabel)
//cut list section
@ -451,6 +453,12 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
cutListSection.visibility = View.GONE
rangeSlider.setValues(0F, 100F)
player.seekTo(0)
suggestedChips.children.apply {
this.forEach {
it.isVisible = ! selectedCuts.contains((it as Chip).text)
}
suggestedLabel.isVisible = this.any { it.isVisible }
}
}
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.END_ICON_NONE
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
@ -69,8 +70,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
): View? {
fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext())
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -291,6 +292,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
}
},
updateDataClicked = {
CoroutineScope(Dispatchers.IO).launch {
resultItem?.apply {
resultViewModel.updateItemData(this)
}
}
},
extraCommandsClicked = {
val callback = object : ExtraCommandsListener {
override fun onChangeExtraCommand(c: String) {

View file

@ -23,6 +23,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.Player.Command
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
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.ResultItem
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.Type
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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -69,7 +72,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var historyViewModel: HistoryViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var commandTemplateDao : CommandTemplateDao
private lateinit var commandTemplateViewModel : CommandTemplateViewModel
private lateinit var infoUtil: InfoUtil
private lateinit var sharedPreferences : SharedPreferences
private lateinit var updateItem : Button
@ -89,7 +92,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
infoUtil = InfoUtil(requireContext())
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -166,7 +169,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
var commandTemplateNr = 0
lifecycleScope.launch{
withContext(Dispatchers.IO){
commandTemplateNr = commandTemplateDao.getTotalNumber()
commandTemplateNr = commandTemplateViewModel.getTotalNumber()
if (!Patterns.WEB_URL.matcher(result.url).matches()) commandTemplateNr++
if(commandTemplateNr <= 0){
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
@ -242,7 +245,16 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab!!.position == 2 && commandTemplateNr == 0){
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){
tabLayout.selectTab(tabLayout.getTabAt(0))
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()
},
cutClicked = {},
updateDataClicked = {},
extraCommandsClicked = {
val callback = object : ExtraCommandsListener {
override fun onChangeExtraCommand(c: String) {
@ -509,6 +510,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.dismiss()
},
cutClicked = {},
updateDataClicked = {},
filenameTemplateSet = { 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.EndIconMode
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@ -72,8 +74,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
): View? {
fragmentView = inflater.inflate(R.layout.fragment_download_video, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext())
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -310,6 +312,13 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
}
},
updateDataClicked = {
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
resultItem?.apply {
resultViewModel.updateItemData(this)
}
}
},
filenameTemplateSet = {
downloadItem.customFileNameTemplate = it
},

View file

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

View file

@ -1,9 +1,11 @@
package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Outline
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.media.MediaMetadataRetriever
@ -12,13 +14,23 @@ import android.net.Uri
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.EditText
import android.widget.ScrollView
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 com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.io.File
import java.util.regex.Pattern
@ -109,4 +121,41 @@ object Extensions {
duration?.toIntOrNull()?.div(1000) ?: 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
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
@ -553,6 +554,7 @@ class InfoUtil(private val context: Context) {
urlsFile.delete()
}
@SuppressLint("RestrictedApi")
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
val items = arrayListOf<ResultItem?>()
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
@ -978,6 +980,7 @@ class InfoUtil(private val context: Context) {
}
}
@SuppressLint("RestrictedApi")
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || downloadItem.playlistIndex == null){
YoutubeDLRequest(downloadItem.url)
@ -1108,15 +1111,14 @@ class InfoUtil(private val context: Context) {
request.addOption("--force-keyframes-at-cuts")
}
}
if (filenameTemplate.isBlank()){
filenameTemplate = "%(section_title& {}|)s %(title)s"
filenameTemplate = if (filenameTemplate.isBlank()){
"%(section_title& {}|)s%(title)s"
}else{
filenameTemplate += " %(section_title& {}|)s "
"%(section_title& {}|)s$filenameTemplate"
}
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)){
@ -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)){
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){
try {
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)
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
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") {
videoF = "bv"
}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)) {
videoF = "bv[height<="+videoF.split("_")[0].dropLast(1)+"]"
}
@ -1316,7 +1319,7 @@ class InfoUtil(private val context: Context) {
.filter { it.isNotBlank() }
.ifEmpty {
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
}.apply {
@ -1346,7 +1349,7 @@ class InfoUtil(private val context: Context) {
if (!f.contains(al)) f.append(al)
}
//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
f.append("$v/")
}

View file

@ -549,6 +549,31 @@ class NotificationUtil(var context: Context) {
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 {
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
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_ERRORED_NOTIFICATION_ID = 60000
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_CURR = 0

View file

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

View file

@ -26,7 +26,6 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.R
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.chip.Chip
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.Markwon
import io.noties.markwon.MarkwonConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStreamReader
@ -122,7 +123,8 @@ class UpdateUtil(var context: Context) {
)
.setAllowedOverRoaming(true)
.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)
)
@ -176,42 +178,7 @@ class UpdateUtil(var context: Context) {
layoutParams.setMargins(20, 20, 20, 0)
scrollView.layoutParams = layoutParams
scrollView.addView(linearLayout)
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 releases = getGithubReleases()
val changeLogDialog = MaterialAlertDialogBuilder(context)
.setTitle(activity.getString(R.string.changelog))
@ -222,6 +189,46 @@ class UpdateUtil(var context: Context) {
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 {
if (it.message != null){
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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:paddingBottom="20dp"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/frame_layout"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_constraint"
android:layout_width="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_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
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
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_marginTop="10dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<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.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_marginTop="10dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="@id/frame_layout"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
</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_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
@ -119,9 +134,8 @@
android:layout_width="0dp"
android:layout_marginHorizontal="10dp"
android:scrollbars="none"
app:layout_constraintWidth_percent=".5"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintStart_toEndOf="@id/frame_layout"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintStart_toEndOf="@id/player_constraint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

View file

@ -1,334 +1,362 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<androidx.core.widget.NestedScrollView xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:paddingBottom="20dp"
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" >
<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" />
android:scrollbars="none"
android:orientation="vertical"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cut_section"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/frame_layout">
android:paddingBottom="20dp"
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"
android:layout_width="match_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"
android:layout_marginVertical="10dp"
android:valueFrom="00.0"
android:valueTo="100.0"
app:labelBehavior="gone"
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
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" />
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" />
<LinearLayout
android:id="@+id/linearLayout5"
<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
android:id="@+id/cut_section"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp"
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">
app:layout_constraintTop_toBottomOf="@+id/frame_layout">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content"
<com.google.android.material.slider.RangeSlider
android:id="@+id/rangeSlider"
android:layout_width="match_parent"
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>
<TextView
android:id="@+id/colon"
android:layout_width="wrap_content"
<LinearLayout
android:id="@+id/linearLayout5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingHorizontal="10dp"
android:text=":"
android:textSize="20sp"
android:textStyle="bold" />
android:layout_marginHorizontal="15dp"
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/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
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="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
android:id="@+id/suggested_cuts"
android:id="@+id/list_section"
android:visibility="gone"
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"
>
android:paddingVertical="10dp"
app:layout_constraintTop_toBottomOf="@+id/frame_layout"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/suggested" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
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"
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="0dp"
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
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"
>
</androidx.constraintlayout.widget.ConstraintLayout>
<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"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<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
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/cut_list_chip_group"
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>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
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>
</androidx.core.widget.NestedScrollView>

View file

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

View file

@ -37,36 +37,62 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/downloads_title"
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:ellipsize="end"
android:maxLines="3"
android:paddingHorizontal="10dp"
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"
android:dividerPadding="5dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/downloads_info_time"
app:layout_constraintEnd_toStartOf="@+id/downloads_download_button_type"
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
android:id="@+id/downloads_info_bottom"
android:id="@+id/length"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="1"
android:padding="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
@ -77,10 +103,8 @@
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/downloads_info_time"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/downloads_download_button_type" />
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/downloads_info_time"

View file

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

View file

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

View file

@ -330,4 +330,5 @@
<string name="changelog">Changelog</string>
<string name="source">Source</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>

View file

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

View file

@ -16,14 +16,14 @@ buildscript {
commonsCompressVer = '1.12'
youtubedlAndroidVer = "-SNAPSHOT"
youtubedlAndroidVerNoPyCrypto = "97bee6c0e8"
workVer = "2.8.1"
workVer = "2.9.0"
composeVer = '1.4.2'
kotlinVer = "1.7.21"
coroutineVer = '1.7.3'
retrofitVer = "2.9.0"
kodeinVer = "7.16.0"
navVer = "2.7.5"
media3_version = "1.1.1"
navVer = "2.7.6"
media3_version = "1.2.0"
agp_version = '8.1.0'
agp_version1 = '7.4.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.