alot of stuff

added remux-video for audio
made copy url similar to history card
fixed padding on cards
fixed configured download sheet not scrolling
implemented chapter suggestions in cuts
This commit is contained in:
deniscerri 2023-04-08 21:21:01 +02:00
parent 9f0d8ab727
commit f396787b65
No known key found for this signature in database
GPG key ID: 95C43D517D830350
27 changed files with 383 additions and 181 deletions

View file

@ -22,7 +22,7 @@ android {
keyPassword properties["signingConfig.keyPassword"] keyPassword properties["signingConfig.keyPassword"]
} }
} }
compileSdkVersion "android-UpsideDownCake" compileSdkVersion 33
defaultConfig { defaultConfig {
applicationId "com.deniscerri.ytdl" applicationId "com.deniscerri.ytdl"
minSdkVersion 23 minSdkVersion 23
@ -119,7 +119,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appCompatVer" implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation 'com.google.android.material:material:1.10.0-alpha01' implementation 'com.google.android.material:material:1.9.0-beta01'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.core:core:1.9.0' implementation 'androidx.core:core:1.9.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.0'

View file

@ -0,0 +1,7 @@
package com.deniscerri.ytdlnis.database.models
data class ChapterItem(
var start_time: Long,
var end_time: Long,
var title: String,
)

View file

@ -4,16 +4,20 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.* import android.content.*
import android.content.Context.CLIPBOARD_SERVICE import android.content.Context.CLIPBOARD_SERVICE
import android.content.res.ColorStateList
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.InputType
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import android.view.View.* import android.view.View.*
import android.widget.* import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.text.TextUtilsCompat import androidx.core.text.TextUtilsCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.children import androidx.core.view.children
@ -42,6 +46,7 @@ import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
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
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.search.SearchBar import com.google.android.material.search.SearchBar
@ -350,6 +355,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
if (present == null) { if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = searchView.editText.text chip.text = searchView.editText.text
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener { chip.setOnClickListener {
queriesChipGroup!!.removeView(chip) queriesChipGroup!!.removeView(chip)
} }

View file

@ -4,14 +4,17 @@ import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.DBManager
@ -21,6 +24,8 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
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.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
@ -36,7 +41,10 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var commandTemplateDao: CommandTemplateDao private lateinit var commandTemplateDao: CommandTemplateDao
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
private lateinit var uiUtil: UiUtil
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -44,7 +52,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
onDownloadItemUpdateListener = listener onDownloadItemUpdateListener = listener
uiUtil = UiUtil(FileUtil())
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -58,20 +66,31 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
super.setupDialog(dialog, style) super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null) val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null)
dialog.setContentView(view) dialog.setContentView(view)
//view.minimumHeight = resources.displayMetrics.heightPixels
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels - 700
}
tabLayout = view.findViewById(R.id.download_tablayout) tabLayout = view.findViewById(R.id.download_tablayout)
viewPager2 = view.findViewById(R.id.download_viewpager) viewPager2 = view.findViewById(R.id.download_viewpager)
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, downloadItem), DownloadVideoFragment(resultItem, downloadItem)) (viewPager2.getChildAt(0) as? RecyclerView)?.apply {
isNestedScrollingEnabled = false
overScrollMode = View.OVER_SCROLL_NEVER
}
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, downloadItem), DownloadVideoFragment(resultItem, downloadItem))
var commandTemplateNr = 0
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber() commandTemplateNr = commandTemplateDao.getTotalNumber()
if(nr > 0){ if(commandTemplateNr > 0){
fragments.add(DownloadCommandFragment(resultItem, downloadItem)) fragments.add(DownloadCommandFragment(resultItem, downloadItem))
}else{ }else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
} }
} }
@ -107,7 +126,12 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) { override fun onTabSelected(tab: TabLayout.Tab?) {
viewPager2.currentItem = tab!!.position if (tab!!.position == 2 && commandTemplateNr == 0){
tabLayout.selectTab(tabLayout.getTabAt(1))
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
viewPager2.setCurrentItem(tab.position, false)
}
} }
override fun onTabUnselected(tab: TabLayout.Tab?) { override fun onTabUnselected(tab: TabLayout.Tab?) {
@ -147,6 +171,16 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
dismiss() dismiss()
} }
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = resultItem.url
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), resultItem.url, null)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
true
}
} }

View file

@ -3,6 +3,8 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.res.ColorStateList
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -13,8 +15,10 @@ import android.view.View
import android.widget.* import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.forEach
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
@ -31,15 +35,19 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
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
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.RangeSlider import com.google.android.material.slider.RangeSlider
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.lang.reflect.Type
import java.util.* import java.util.*
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -58,12 +66,17 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
private lateinit var toTextInput : TextInputLayout private lateinit var toTextInput : TextInputLayout
private lateinit var cancelBtn : Button private lateinit var cancelBtn : Button
private lateinit var okBtn : Button private lateinit var okBtn : Button
private lateinit var suggestedChips: ChipGroup
private lateinit var suggestedChapters : LinearLayout
private lateinit var cutListSection : LinearLayout private lateinit var cutListSection : LinearLayout
private lateinit var newCutBtn : Button private lateinit var newCutBtn : Button
private lateinit var resetBtn : Button
private lateinit var chipGroup : ChipGroup private lateinit var chipGroup : ChipGroup
private var timeSeconds by Delegates.notNull<Int>() private var timeSeconds by Delegates.notNull<Int>()
private lateinit var chapters: List<ChapterItem>
private lateinit var selectedCuts: MutableList<String>
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -92,6 +105,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
val videoView = view.findViewById<PlayerView>(R.id.video_view) val videoView = view.findViewById<PlayerView>(R.id.video_view)
videoView.player = player videoView.player = player
timeSeconds = convertStringToTimestamp(item.duration) timeSeconds = convertStringToTimestamp(item.duration)
chapters = emptyList()
//cut section //cut section
cutSection = view.findViewById(R.id.cut_section) cutSection = view.findViewById(R.id.cut_section)
@ -101,12 +115,25 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
toTextInput = view.findViewById(R.id.to_textinput) toTextInput = view.findViewById(R.id.to_textinput)
cancelBtn = view.findViewById(R.id.cancelButton) cancelBtn = view.findViewById(R.id.cancelButton)
okBtn = view.findViewById(R.id.okButton) okBtn = view.findViewById(R.id.okButton)
suggestedChips = view.findViewById(R.id.chapters)
suggestedChapters = view.findViewById(R.id.suggested_cuts)
initCutSection() initCutSection()
//cut list section //cut list section
cutListSection = view.findViewById(R.id.list_section) cutListSection = view.findViewById(R.id.list_section)
newCutBtn = view.findViewById(R.id.new_cut) newCutBtn = view.findViewById(R.id.new_cut)
resetBtn = view.findViewById(R.id.reset_all)
chipGroup = view.findViewById(R.id.cut_list_chip_group) chipGroup = view.findViewById(R.id.cut_list_chip_group)
selectedCuts = if (chipGroup.childCount == 0){
mutableListOf()
}else {
chipGroup.children.forEach { c ->
if ( ! (c as Chip).text.contains(":")) c.isEnabled = false
}
chipGroup.children.map { (it as Chip).text.toString() }.toMutableList()
}
initCutListSection() initCutListSection()
if (item.downloadSections.isBlank()) cutSection.visibility = View.VISIBLE if (item.downloadSections.isBlank()) cutSection.visibility = View.VISIBLE
@ -114,26 +141,32 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val url = withContext(Dispatchers.IO){ val data = withContext(Dispatchers.IO){
infoUtil.getStreamingUrl(item.url) infoUtil.getStreamingUrlAndChapters(item.url)
} }
if (data.isEmpty()) throw Exception("No Data found!")
try{
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
chapters = Gson().fromJson(data.first().toString(), listType)
}catch (ignored: Exception) {}
data.removeFirst()
if (url.isBlank()) throw Exception("No Streaming URL found!")
val urls = url.split("\n") if (data.isEmpty()) throw Exception("No Streaming URL found!")
if (urls.size == 2){ if (data.size == 2){
val audioSource: MediaSource = val audioSource: MediaSource =
DefaultMediaSourceFactory(requireContext()) DefaultMediaSourceFactory(requireContext())
.createMediaSource(fromUri(Uri.parse(urls[0]))) .createMediaSource(fromUri(Uri.parse(data[0])))
val videoSource: MediaSource = val videoSource: MediaSource =
DefaultMediaSourceFactory(requireContext()) DefaultMediaSourceFactory(requireContext())
.createMediaSource(fromUri(Uri.parse(urls[1]))) .createMediaSource(fromUri(Uri.parse(data[1])))
(player as ExoPlayer).setMediaSource(MergingMediaSource(videoSource, audioSource)) (player as ExoPlayer).setMediaSource(MergingMediaSource(videoSource, audioSource))
}else{ }else{
player.addMediaItem(fromUri(Uri.parse(urls[0]))) player.addMediaItem(fromUri(Uri.parse(data[0])))
} }
progress.visibility = View.GONE progress.visibility = View.GONE
populateSuggestedChapters()
player.prepare() player.prepare()
player.seekTo((((rangeSlider.valueFrom.toInt() * timeSeconds) / 100) * 1000).toLong()) player.seekTo((((rangeSlider.valueFrom.toInt() * timeSeconds) / 100) * 1000).toLong())
@ -166,23 +199,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
} }
private fun initCutSection(){ private fun initCutSection(){
if (item.downloadSections.isEmpty()){ fromTextInput.editText!!.setText("0:00")
fromTextInput.editText!!.setText("0:00") toTextInput.editText!!.setText(item.duration)
toTextInput.editText!!.setText(item.duration)
}else{
val stamps = item.downloadSections.split("-")
fromTextInput.editText!!.setText(stamps[0])
toTextInput.editText!!.setText(stamps[1].replace(";", ""))
val startSeconds = convertStringToTimestamp(stamps[0])
val endSeconds = convertStringToTimestamp(stamps[1].replace(";", ""))
val startValue = (startSeconds.toFloat() / timeSeconds) * 100
val endValue = (endSeconds.toFloat() / timeSeconds) * 100
rangeSlider.setValues(startValue, endValue)
}
rangeSlider.addOnChangeListener { rangeSlider, value, fromUser -> rangeSlider.addOnChangeListener { rangeSlider, value, fromUser ->
val values = rangeSlider.values val values = rangeSlider.values
@ -270,21 +288,53 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
}) })
cancelBtn.setOnClickListener { cancelBtn.setOnClickListener {
cutSection.visibility = View.GONE if (chipGroup.childCount == 0){
cutListSection.visibility = View.VISIBLE player.stop()
dismiss()
}else{
cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE
}
} }
okBtn.isEnabled = false okBtn.isEnabled = false
okBtn.setOnClickListener { okBtn.setOnClickListener {
val chip = createChip("${fromTextInput.editText!!.text}-${toTextInput.editText!!.text}") val chip = createChip("${fromTextInput.editText!!.text}-${toTextInput.editText!!.text}")
listener.onChangeCut(chipGroup.children.map { c -> (c as Chip).text.toString() })
chip.performClick() chip.performClick()
player.seekTo((((rangeSlider.valueFrom.toInt() * timeSeconds) / 100) * 1000).toLong())
player.play()
cutSection.visibility = View.GONE cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE cutListSection.visibility = View.VISIBLE
} }
populateSuggestedChapters()
}
private fun populateSuggestedChapters(){
if (chapters.isEmpty()) suggestedChapters.visibility = View.GONE
else {
suggestedChapters.visibility = View.VISIBLE
suggestedChips.removeAllViews()
chapters.forEach {
val chip = layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
chip.text = it.title
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false
suggestedChips.addView(chip)
chip.setOnClickListener { c ->
val createdChip = createChapterChip(it, null)
createdChip.performClick()
cutSection.visibility = View.GONE
cutListSection.visibility = View.VISIBLE
}
//replace existing chip to enable click events
if (selectedCuts.contains(it.title)){
val idx = selectedCuts.indexOf(it.title)
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text == it.title }
chipGroup.removeView(chipForDeletion)
createChapterChip(it, idx)
}
}
}
} }
private fun initCutListSection() { private fun initCutListSection() {
@ -295,32 +345,44 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
player.seekTo(0) player.seekTo(0)
} }
resetBtn.setOnClickListener {
chipGroup.removeAllViews()
listener.onChangeCut(emptyList())
player.stop()
dismiss()
}
if (item.downloadSections.isNotBlank()){ if (item.downloadSections.isNotBlank()){
chipGroup.removeAllViews() chipGroup.removeAllViews()
item.downloadSections.split(";").forEachIndexed { index, it -> item.downloadSections.split(";").forEachIndexed { index, it ->
if (it.isBlank()) return if (it.isBlank()) return
val startingValue = ((convertStringToTimestamp(it.split("-")[0]).toFloat() / timeSeconds) * 100).toInt() if (it.contains(":")) createChip(it.replace(";", ""))
val endingValue = ((convertStringToTimestamp(it.split("-")[1].replace(";", "")).toFloat() / timeSeconds) * 100).toInt() else createChapterChip(ChapterItem(0, 0, it), null)
createChip(it.replace(";", ""))
if (index == 0) rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
} }
} }
} }
private fun createChip(timestamp: String) : Chip { private fun createChip(timestamp: String) : Chip {
val startingValue = ((convertStringToTimestamp(timestamp.split("-")[0]).toFloat() / timeSeconds) * 100).toInt() val startTimestamp = convertStringToTimestamp(timestamp.split("-")[0].replace(";", ""))
val endingValue = ((convertStringToTimestamp(timestamp.split("-")[1].replace(";", "")).toFloat() / timeSeconds) * 100).toInt() val endTimestamp = convertStringToTimestamp(timestamp.split("-")[1].replace(";", ""))
val startingValue = ((startTimestamp.toFloat() / timeSeconds) * 100).toInt()
val endingValue = ((endTimestamp.toFloat() / timeSeconds) * 100).toInt()
val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
chip.text = timestamp chip.text = timestamp
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false chip.isCheckedIconVisible = false
chipGroup.addView(chip) chipGroup.addView(chip)
listener.onChangeCut(chipGroup.children.map { c -> (c as Chip).text.toString() }) selectedCuts.add(chip.text.toString())
listener.onChangeCut(selectedCuts)
chip.setOnClickListener { chip.setOnClickListener {
if (chip.isChecked) { if (chip.isChecked) {
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat()) rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
player.prepare()
player.seekTo((((startingValue * timeSeconds) / 100) * 1000).toLong())
player.play()
}else { }else {
player.seekTo(0) player.seekTo(0)
player.pause() player.pause()
@ -335,7 +397,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
player.seekTo(0) player.seekTo(0)
player.pause() player.pause()
chipGroup.removeView(chip) chipGroup.removeView(chip)
listener.onChangeCut(chipGroup.children.map { c -> (c as Chip).text.toString() }) selectedCuts.remove(chip.text.toString())
listener.onChangeCut(selectedCuts)
} }
deleteDialog.show() deleteDialog.show()
true true
@ -344,6 +407,60 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
return chip return chip
} }
private fun createChapterChip(chapter: ChapterItem, position: Int?) : Chip {
val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
chip.text = chapter.title
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false
if (position != null) chipGroup.addView(chip, position)
else chipGroup.addView(chip)
if (! selectedCuts.contains(chapter.title))
selectedCuts.add(chip.text.toString())
listener.onChangeCut(selectedCuts)
if (chapter.start_time == 0L && chapter.end_time == 0L) {
chip.isEnabled = false
}else{
val startTimestamp = chapter.start_time.toInt()
val endTimestamp = chapter.end_time.toInt()
val startingValue = ((startTimestamp.toFloat() / timeSeconds) * 100).toInt()
val endingValue = ((endTimestamp.toFloat() / timeSeconds) * 100).toInt()
chip.setOnClickListener {
if (chip.isChecked) {
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
player.prepare()
player.seekTo((((startingValue * timeSeconds) / 100) * 1000).toLong())
player.play()
}else {
player.seekTo(0)
player.pause()
}
}
chip.setOnLongClickListener {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + chip.text + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
player.seekTo(0)
player.pause()
chipGroup.removeView(chip)
selectedCuts.remove(chip.text.toString())
listener.onChangeCut(selectedCuts)
}
deleteDialog.show()
true
}
}
return chip
}
private fun videoProgress(player: Player?) = flow { private fun videoProgress(player: Player?) = flow {
while (true) { while (true) {
emit((player!!.currentPosition / 1000).toInt()) emit((player!!.currentPosition / 1000).toInt())
@ -386,5 +503,5 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val list
} }
interface VideoCutListener{ interface VideoCutListener{
fun onChangeCut(list: Sequence<String>) fun onChangeCut(list: List<String>)
} }

View file

@ -1,9 +1,7 @@
package com.deniscerri.ytdlnis.ui.downloadcard package com.deniscerri.ytdlnis.ui.downloadcard
import android.app.Activity import android.app.Activity
import android.content.ClipboardManager import android.content.*
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
@ -11,10 +9,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.*
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
@ -249,10 +244,10 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
val cut = view.findViewById<Chip>(R.id.cut) val cut = view.findViewById<Chip>(R.id.cut)
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: Sequence<String>) { override fun onChangeCut(list: List<String>) {
if (list.count() == 0){ if (list.isEmpty()){
downloadItem.downloadSections = "" downloadItem.downloadSections = ""
cut.text = "" cut.text = getString(R.string.cut)
splitByChapters.isEnabled = true splitByChapters.isEnabled = true
splitByChapters.isChecked = downloadItem.audioPreferences.splitByChapters splitByChapters.isChecked = downloadItem.audioPreferences.splitByChapters
@ -274,13 +269,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
bottomSheet.show(parentFragmentManager, "cutVideoSheet") bottomSheet.show(parentFragmentManager, "cutVideoSheet")
} }
val copyURL = view.findViewById<Chip>(R.id.copy_url)
copyURL.setOnClickListener {
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(downloadItem.url)
}
}catch (e : Exception){ }catch (e : Exception){
e.printStackTrace() e.printStackTrace()
} }

View file

@ -83,14 +83,14 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
} }
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, null), DownloadVideoFragment(resultItem, null)) val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, null), DownloadVideoFragment(resultItem, null))
var commandTemplateNr = 0
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber() commandTemplateNr = commandTemplateDao.getTotalNumber()
if(nr > 0){ if(commandTemplateNr > 0){
fragments.add(DownloadCommandFragment(resultItem, null)) fragments.add(DownloadCommandFragment(resultItem, null))
}else{ }else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
} }
} }
@ -128,7 +128,12 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) { override fun onTabSelected(tab: TabLayout.Tab?) {
viewPager2.setCurrentItem(tab!!.position, false) if (tab!!.position == 2 && commandTemplateNr == 0){
tabLayout.selectTab(tabLayout.getTabAt(1))
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
viewPager2.setCurrentItem(tab.position, false)
}
} }
override fun onTabUnselected(tab: TabLayout.Tab?) { override fun onTabUnselected(tab: TabLayout.Tab?) {
@ -168,6 +173,16 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
} }
dismiss() dismiss()
} }
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
link.text = resultItem.url
link.setOnClickListener{
uiUtil.openLinkIntent(requireContext(), resultItem.url, null)
}
link.setOnLongClickListener{
uiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
true
}
} }
private fun getDownloadItem() : DownloadItem{ private fun getDownloadItem() : DownloadItem{

View file

@ -192,13 +192,6 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
} }
} }
val copyURL = view.findViewById<Chip>(R.id.copy_url)
copyURL.setOnClickListener {
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(downloadItem.url)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

View file

@ -10,10 +10,7 @@ import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.*
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
@ -269,8 +266,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
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: Sequence<String>) { override fun onChangeCut(list: List<String>) {
if (list.count() == 0){ if (list.isEmpty()){
downloadItem.downloadSections = "" downloadItem.downloadSections = ""
cut.text = getString(R.string.cut) cut.text = getString(R.string.cut)
@ -302,13 +299,6 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
bottomSheet.show(parentFragmentManager, "cutVideoSheet") bottomSheet.show(parentFragmentManager, "cutVideoSheet")
} }
val copyURL = view.findViewById<Chip>(R.id.copy_url)
copyURL.setOnClickListener {
val clipboard: ClipboardManager =
requireContext().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(downloadItem.url)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }

View file

@ -3,8 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.*
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
@ -109,13 +108,21 @@ class FormatSelectionBottomSheetDialog(private val item: DownloadItem, private v
linearLayout.removeAllViews() linearLayout.removeAllViews()
Log.e("aa", formats.toString()) Log.e("aa", formats.toString())
for (i in formats.lastIndex downTo 0){ for (i in formats.lastIndex downTo 0){
val it = formats[i] val format = formats[i]
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null) val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
uiUtil.populateFormatCard(formatItem as ConstraintLayout, it) uiUtil.populateFormatCard(formatItem as ConstraintLayout, format)
formatItem.setOnClickListener{_ -> formatItem.setOnClickListener{_ ->
listener.onFormatClick(formats, it) listener.onFormatClick(formats, format)
dismiss() dismiss()
} }
formatItem.setOnLongClickListener {
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("format_id", format.format_id)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, requireContext().getString(R.string.formatid_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
true
}
linearLayout.addView(formatItem) linearLayout.addView(formatItem)
} }
} }

View file

@ -629,10 +629,11 @@ class InfoUtil(context: Context) {
return format return format
} }
fun getStreamingUrl(url: String) : String { fun getStreamingUrlAndChapters(url: String) : MutableList<String?> {
try { try {
val request = YoutubeDLRequest(url) val request = YoutubeDLRequest(url)
request.addOption("-j") request.addOption("--get-url")
request.addOption("--print", "%(chapters)s")
request.addOption("--skip-download") request.addOption("--skip-download")
request.addOption("-R", "1") request.addOption("-R", "1")
request.addOption("--socket-timeout", "5") request.addOption("--socket-timeout", "5")
@ -643,10 +644,9 @@ class InfoUtil(context: Context) {
} catch (e: Exception) { } catch (e: Exception) {
arrayOf(youtubeDLResponse.out) arrayOf(youtubeDLResponse.out)
} }
val jsonObject = JSONObject(results[0]!!) return results.toMutableList()
return jsonObject.getString("urls")
} catch (e: Exception) { } catch (e: Exception) {
return "" return mutableListOf()
} }
} }

View file

@ -118,9 +118,13 @@ class DownloadWorker(
if (downloadItem.downloadSections.isNotBlank()){ if (downloadItem.downloadSections.isNotBlank()){
downloadItem.downloadSections.split(";").forEach { downloadItem.downloadSections.split(";").forEach {
if (it.isBlank()) return@forEach if (it.isBlank()) return@forEach
request.addOption("--download-sections", "*$it") if (it.contains(":"))
request.addOption("--download-sections", "*$it")
else
request.addOption("--download-sections", it)
} }
downloadItem.customFileNameTemplate += "%(autonumber)s" downloadItem.customFileNameTemplate += " %(section_title)s %(autonumber)s"
request.addOption("--output-na-placeholder", " ")
} }
} }
@ -130,9 +134,8 @@ class DownloadWorker(
when(type){ when(type){
DownloadViewModel.Type.audio -> { DownloadViewModel.Type.audio -> {
request.addOption("-x")
var audioQualityId : String = downloadItem.format.format_id var audioQualityId : String = downloadItem.format.format_id
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality)) audioQualityId = "" if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality)) audioQualityId = "bestaudio"
else if (audioQualityId == context.getString(R.string.worst_quality)) audioQualityId = "worstaudio" else if (audioQualityId == context.getString(R.string.worst_quality)) audioQualityId = "worstaudio"
if (audioQualityId.isNotBlank()){ if (audioQualityId.isNotBlank()){
@ -141,18 +144,33 @@ class DownloadWorker(
val ext = downloadItem.format.container val ext = downloadItem.format.container
if(ext != context.getString(R.string.defaultValue) && ext != "webm"){ if(ext != context.getString(R.string.defaultValue) && ext != "webm"){
request.addOption("--audio-format", ext) val codec = when(downloadItem.format.container){
"m4a" -> "aac"
"aac" -> "aac"
"mp3" -> "libmp3lame"
"flac" -> "flac"
"opus" -> "libopus"
else -> ""
}
Log.e("aa", codec)
if (codec.isEmpty()){
request.addOption("-x")
}else{
request.addOption("--remux-video", ext)
request.addOption("--ppa", "VideoRemuxer:-c:a $codec")
}
} }
request.addOption("--embed-metadata") request.addOption("--embed-metadata")
if (downloadItem.audioPreferences.embedThumb) { if (downloadItem.audioPreferences.embedThumb) {
request.addOption("--embed-thumbnail") request.addOption("--embed-thumbnail")
request.addOption("--convert-thumbnails", "png") request.addOption("--convert-thumbnails", "jpg")
try { try {
val config = File(context.cacheDir.absolutePath + "/downloads/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt") val config = File(context.cacheDir.absolutePath + "/downloads/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
val configData = val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
"--ppa \"ffmpeg: -c:v png -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("--config", config.absolutePath) request.addOption("--config", config.absolutePath)
} catch (ignored: Exception) {} } catch (ignored: Exception) {}
} }

View file

@ -24,7 +24,7 @@
android:checkable="true" android:checkable="true"
app:strokeWidth="0dp" app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
android:layout_margin="10dp"> android:layout_margin="20dp">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image_view" android:id="@+id/image_view"

View file

@ -66,7 +66,6 @@
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/download_viewpager" android:id="@+id/download_viewpager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingHorizontal="10dp"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</LinearLayout> </LinearLayout>
<include layout="@layout/history_no_results" <include layout="@layout/history_no_results"

View file

@ -87,7 +87,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textSize="15sp"
app:icon="@drawable/ic_link" />
</LinearLayout> </LinearLayout>

View file

@ -69,7 +69,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="15dp" android:layout_marginHorizontal="15dp"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@id/suggested_cuts"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.533" app:layout_constraintHorizontal_bias="0.533"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -119,6 +119,43 @@
</LinearLayout> </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: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 <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -127,7 +164,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="20dp" android:layout_marginHorizontal="20dp"
app:layout_constraintTop_toBottomOf="@+id/linearLayout5" app:layout_constraintTop_toBottomOf="@+id/suggested_cuts"
app:layout_constraintVertical_bias="1.0" app:layout_constraintVertical_bias="1.0"
> >
@ -180,6 +217,17 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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 <Button
android:id="@+id/new_cut" android:id="@+id/new_cut"

View file

@ -105,6 +105,18 @@
android:id="@+id/download_viewpager" android:id="@+id/download_viewpager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textSize="15sp"
app:icon="@drawable/ic_link" />
</LinearLayout> </LinearLayout>

View file

@ -11,7 +11,8 @@
android:layout_width="0dp" android:layout_width="0dp"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="10dp" android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
android:checkable="true" android:checkable="true"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"

View file

@ -5,6 +5,7 @@
android:clickable="true" android:clickable="true"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="5dp" android:paddingVertical="5dp"
android:paddingHorizontal="10dp"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:focusable="true"> android:focusable="true">

View file

@ -59,7 +59,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:scrollbars="none" android:scrollbars="none"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<LinearLayout <LinearLayout

View file

@ -12,7 +12,6 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="10dp"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
@ -20,7 +19,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:paddingBottom="10dp" android:padding="10dp"
android:hint="@string/title" android:hint="@string/title"
style="@style/Widget.Material3.TextInputLayout.FilledBox"> style="@style/Widget.Material3.TextInputLayout.FilledBox">
@ -35,7 +34,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="10dp" android:padding="10dp"
android:orientation="horizontal"> android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
@ -80,13 +79,16 @@
android:id="@+id/audio_quality_title" android:id="@+id/audio_quality_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:text="@string/audio_quality" /> android:text="@string/audio_quality" />
<include layout="@layout/format_item" /> <include layout="@layout/format_item" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/outputPath" android:id="@+id/outputPath"
android:paddingHorizontal="10dp"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingBottom="0dp"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -102,8 +104,7 @@
<TextView <TextView
android:id="@+id/freespace" android:id="@+id/freespace"
android:paddingHorizontal="10dp" android:paddingHorizontal="20dp"
android:paddingVertical="5dp"
android:textSize="13sp" android:textSize="13sp"
android:textColor="@android:color/tab_indicator_text" android:textColor="@android:color/tab_indicator_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -112,7 +113,7 @@
<LinearLayout <LinearLayout
android:id="@+id/adjust_audio" android:id="@+id/adjust_audio"
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingVertical="10dp" android:padding="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
> >
@ -205,22 +206,6 @@
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>
<com.google.android.material.chip.Chip
android:id="@+id/copy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/copy_url"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_clipboard"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> </LinearLayout>

View file

@ -111,21 +111,6 @@
android:text="@string/edit_selected" android:text="@string/edit_selected"
app:chipIcon="@drawable/ic_edit" /> app:chipIcon="@drawable/ic_edit" />
<com.google.android.material.chip.Chip
android:id="@+id/copy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/copy_url"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_clipboard"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>

View file

@ -9,7 +9,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -20,7 +19,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:hint="@string/title" android:hint="@string/title"
android:paddingBottom="10dp"> android:padding="10dp">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
@ -33,7 +32,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingBottom="10dp"> android:padding="10dp">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/author_textinput" android:id="@+id/author_textinput"
@ -74,6 +73,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="10dp"
android:text="@string/video_quality" /> android:text="@string/video_quality" />
<include layout="@layout/format_item" /> <include layout="@layout/format_item" />
@ -85,7 +85,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/save_dir" android:hint="@string/save_dir"
android:paddingTop="10dp"> android:paddingHorizontal="10dp"
android:paddingTop="10dp"
android:paddingBottom="0dp">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
@ -97,8 +99,7 @@
<TextView <TextView
android:id="@+id/freespace" android:id="@+id/freespace"
android:paddingHorizontal="10dp" android:paddingHorizontal="20dp"
android:paddingVertical="5dp"
android:textSize="13sp" android:textSize="13sp"
android:textColor="@android:color/tab_indicator_text" android:textColor="@android:color/tab_indicator_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -110,7 +111,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingVertical="10dp"> android:padding="10dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -213,24 +214,6 @@
</HorizontalScrollView> </HorizontalScrollView>
<com.google.android.material.chip.Chip
android:id="@+id/copy_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/copy_url"
app:chipIconVisible="true"
app:chipIcon="@drawable/ic_clipboard"
android:minWidth="30dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> </LinearLayout>

View file

@ -36,6 +36,7 @@
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/queries" android:id="@+id/queries"
android:layout_margin="10dp" android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"

View file

@ -57,7 +57,8 @@
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/shortcutsChipGroup" android:id="@+id/shortcutsChipGroup"
android:layout_width="wrap_content" android:layout_width="wrap_content"
app:chipSpacingVertical="0dp" app:chipSpacingVertical="-10dp"
app:chipSpacing="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="false"> app:singleLine="false">

View file

@ -192,4 +192,7 @@
<string name="ignore_battery_optimization">Ignore Battery Optimization</string> <string name="ignore_battery_optimization">Ignore Battery Optimization</string>
<string name="cut">Cut</string> <string name="cut">Cut</string>
<string name="new_cut">New Cut</string> <string name="new_cut">New Cut</string>
<string name="add_template_first">You need to have at least one command template created!</string>
<string name="formatid_copied_to_clipboard">Format ID copied to clipboard</string>
<string name="suggested">Suggested</string>
</resources> </resources>

View file

@ -1,4 +1,4 @@
#Sun Mar 05 19:03:41 CET 2023 #Sat Apr 08 15:57:50 CEST 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists