You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ytdl-android/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt

2204 lines
93 KiB
Kotlin

package com.deniscerri.ytdl.util
import android.animation.Animator
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.text.Editable
import android.text.TextWatcher
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.RadioButton
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.DimenRes
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.text.HtmlCompat
import androidx.core.text.parseAsHtml
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.models.TemplateShortcut
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.ui.downloadcard.VideoCutListener
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.Extensions.getMediaDuration
import com.deniscerri.ytdl.util.Extensions.toStringDuration
import com.deniscerri.ytdl.util.extractors.PipedApiUtil
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
object UiUtil {
@SuppressLint("SetTextI18n")
fun populateFormatCard(context: Context, formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<Format>? = null, showSize: Boolean = true){
var formatNote = chosenFormat.format_note
if (formatNote.isEmpty()) formatNote = context.getString(R.string.defaultValue)
else if (formatNote == "best") formatNote = context.getString(R.string.best_quality)
else if (formatNote == "worst") formatNote = context.getString(R.string.worst_quality)
var container = chosenFormat.container
if (container == "Default" || container.isBlank()) container = context.getString(R.string.defaultValue)
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
formatCard.findViewById<TextView>(R.id.audio_formats).apply {
if (!audioFormats.isNullOrEmpty()){
text = "id: " + audioFormats.joinToString("+") { it.format_id }
visibility = View.VISIBLE
}else if (chosenFormat.vcodec != "none" && chosenFormat.vcodec != "" && chosenFormat.acodec != "none" && chosenFormat.acodec != "") {
text = chosenFormat.acodec
visibility = View.VISIBLE
}else{
visibility = View.GONE
}
setOnClickListener {
formatCard.callOnClick()
}
}
formatCard.findViewById<TextView>(R.id.format_id).apply {
text = "id: ${chosenFormat.format_id}"
setOnClickListener {
formatCard.callOnClick()
}
}
val codec =
if (chosenFormat.encoding != "") {
chosenFormat.encoding.uppercase()
}else if (chosenFormat.vcodec != "none" && chosenFormat.vcodec != ""){
chosenFormat.vcodec.uppercase()
} else {
chosenFormat.acodec.uppercase()
}
formatCard.findViewById<TextView>(R.id.codec).apply {
if (codec == "" || codec == "none"){
visibility = View.GONE
}else{
visibility = View.VISIBLE
text = codec
}
setOnClickListener {
formatCard.callOnClick()
}
}
var filesize = chosenFormat.filesize
if (!audioFormats.isNullOrEmpty() && filesize > 10L) filesize += audioFormats.sumOf { it.filesize }
formatCard.findViewById<TextView>(R.id.file_size).apply {
if (showSize) {
text = FileUtil.convertFileSize(filesize)
}else{
text = "?"
}
setOnClickListener {
formatCard.callOnClick()
}
}
}
fun populateCommandCard(card: MaterialCardView, item: CommandTemplate){
card.findViewById<TextView>(R.id.title).text = item.title
card.findViewById<TextView>(R.id.content).text = item.content
card.alpha = 1f
card.tag = item.id
}
fun showCommandTemplateCreationOrUpdatingSheet(
item: CommandTemplate?,
context: Activity,
lifeCycle: LifecycleOwner,
commandTemplateViewModel: CommandTemplateViewModel,
newTemplate: (newTemplate: CommandTemplate) -> Unit,
dismissed: () -> Unit
){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.create_command_template)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val ok : Button = bottomSheet.findViewById(R.id.template_create)!!
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val content : TextInputLayout = bottomSheet.findViewById(R.id.content)!!
val preferredCommandSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.preferredCommandTemplateSwitch)!!
val extraCommandsSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandsSwitch)!!
val extraCommandsAudio : CheckBox = bottomSheet.findViewById(R.id.checkbox_audio)!!
val extraCommandsVideo : CheckBox = bottomSheet.findViewById(R.id.checkbox_video)!!
val extraCommandsDataFetchingSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandDataFetching)!!
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
extraCommandsDataFetchingSwitch.isVisible = sharedPreferences.getBoolean("enable_data_fetching_extra_commands", false)
if (item != null){
title.editText!!.setText(item.title)
content.editText!!.setText(item.content)
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_subtitle)!!.text = content.resources.getString(R.string.update_template)
ok.text = content.resources.getString(R.string.update)
ok.isEnabled = true
content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_all)
}else{
ok.isEnabled = false
content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_clipboard)
}
title.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
ok.isEnabled = title.editText!!.text.isNotEmpty() &&
content.editText!!.text.isNotEmpty() &&
if (extraCommandsSwitch.isChecked){
((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked))
}else{
true
}
}
})
content.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
ok.isEnabled = title.editText!!.text.isNotEmpty() &&
content.editText!!.text.isNotEmpty() &&
if (extraCommandsSwitch.isChecked){
((extraCommandsAudio.isChecked || extraCommandsVideo.isChecked))
}else{
true
}
if (content.editText!!.text.isNotEmpty()){
content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_delete_all)
}else{
content.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_clipboard)
}
}
})
content.setEndIconOnClickListener {
if(content.editText!!.text.isEmpty()){
val clipboard: ClipboardManager =
context.getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
content.editText!!.setText(clipboard.primaryClip?.getItemAt(0)?.text)
}else{
content.editText!!.setText("")
}
}
if (item != null){
preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "")
extraCommandsDataFetchingSwitch.isChecked = item.useAsExtraCommandDataFetching && extraCommandsDataFetchingSwitch.isVisible
extraCommandsSwitch.isChecked = item.useAsExtraCommand
if (item.useAsExtraCommand){
extraCommandsAudio.isVisible = true
extraCommandsAudio.isChecked = item.useAsExtraCommandAudio
extraCommandsVideo.isVisible = true
extraCommandsVideo.isChecked = item.useAsExtraCommandVideo
}else{
extraCommandsAudio.isVisible = false
extraCommandsAudio.isChecked = false
extraCommandsVideo.isVisible = false
extraCommandsVideo.isChecked = false
}
}
extraCommandsSwitch.setOnCheckedChangeListener { compoundButton, b ->
extraCommandsAudio.isVisible = extraCommandsSwitch.isChecked
extraCommandsAudio.isChecked = true
extraCommandsVideo.isVisible = extraCommandsSwitch.isChecked
extraCommandsVideo.isChecked = true
}
extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
}
extraCommandsVideo.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = (extraCommandsAudio.isChecked || extraCommandsVideo.isChecked) && title.editText!!.text.isNotEmpty() && content.editText!!.text.isNotEmpty()
}
commandTemplateViewModel.shortcuts.observe(lifeCycle){
shortcutsChipGroup.removeAllViews()
it.forEach {shortcut ->
val chip = context.layoutInflater.inflate(R.layout.suggestion_chip, shortcutsChipGroup, false) as Chip
chip.text = shortcut.content
chip.setOnClickListener {
content.editText!!.text.insert(content.editText!!.selectionStart, shortcut.content + " ")
}
shortcutsChipGroup.addView(chip)
}
}
editShortcuts.setOnClickListener {
showShortcutsSheet(context, lifeCycle, commandTemplateViewModel)
}
ok.setOnClickListener {
if (preferredCommandSwitch.isChecked) {
sharedPreferences.edit().putString("preferred_command_template", content.editText!!.text.toString()).apply()
}else if (sharedPreferences.getString("preferred_command_template", "") == item?.content){
sharedPreferences.edit().putString("preferred_command_template", "").apply()
}
if (item == null){
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked, extraCommandsDataFetchingSwitch.isChecked)
commandTemplateViewModel.insert(t)
newTemplate(t)
}else{
item.title = title.editText!!.text.toString()
item.content = content.editText!!.text.toString()
item.useAsExtraCommand = extraCommandsSwitch.isChecked
item.useAsExtraCommandAudio = extraCommandsAudio.isChecked
item.useAsExtraCommandVideo = extraCommandsVideo.isChecked
item.useAsExtraCommandDataFetching = extraCommandsDataFetchingSwitch.isChecked
commandTemplateViewModel.update(item)
newTemplate(item)
}
bottomSheet.cancel()
}
bottomSheet.setOnDismissListener {
dismissed()
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun showShortcutsSheet(context: Activity, lifeCycle: LifecycleOwner, commandTemplateViewModel: CommandTemplateViewModel){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.template_shortcuts)
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
title.isEndIconVisible = false
title.editText!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun afterTextChanged(p0: Editable?) {
title.isEndIconVisible = p0!!.isNotEmpty()
}
})
title.setEndIconOnClickListener {
commandTemplateViewModel.insertShortcut(TemplateShortcut(0, title.editText!!.text.toString()))
title.editText!!.setText("")
title.isEndIconVisible = false
}
commandTemplateViewModel.shortcuts.observe(lifeCycle){
shortcutsChipGroup.removeAllViews()
it.forEach {shortcut ->
val chip = context.layoutInflater.inflate(R.layout.input_chip, shortcutsChipGroup, false) as Chip
chip.text = shortcut.content
chip.setOnClickListener{
commandTemplateViewModel.deleteShortcut(shortcut)
}
shortcutsChipGroup.addView(chip)
}
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun copyLinkToClipBoard(context: Context, url: String) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(context.getString(R.string.url), url)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, context.getString(R.string.link_copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
fun openLinkIntent(context: Context, url: String) {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
context.startActivity(i)
}
fun showDatePickerOnly(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
val date = Calendar.getInstance()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(
CalendarConstraints.Builder()
.setValidator(DateValidatorPointForward.now())
.build()
)
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
.build()
datePicker.addOnPositiveButtonClickListener{
date.timeInMillis = it
onSubmit(date)
}
datePicker.show(fragmentManager, "datepicker")
}
fun showDatePicker(fragmentManager: FragmentManager , preferences: SharedPreferences, onSubmit : (chosenDate: Calendar) -> Unit ){
val currentDate = Calendar.getInstance()
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
val date = Calendar.getInstance()
val datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(
CalendarConstraints.Builder()
.setValidator(DateValidatorPointForward.now())
.build()
)
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
.build()
datePicker.addOnPositiveButtonClickListener{
date.timeInMillis = it
showTimePicker(fragmentManager, preferences) { chosenTime ->
date[Calendar.HOUR_OF_DAY] = chosenTime[Calendar.HOUR_OF_DAY]
date[Calendar.MINUTE] = chosenTime[Calendar.MINUTE]
onSubmit(date)
}
}
datePicker.show(fragmentManager, "datepicker")
}
fun showTimePicker(fragmentManager: FragmentManager , preferences: SharedPreferences, onSubmit : (chosenTime: Calendar) -> Unit ){
val currentDate = Calendar.getInstance()
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
val date = Calendar.getInstance()
val lastTime = preferences.getString("latest_timepicker_date", "${currentDate.get(Calendar.HOUR_OF_DAY)}:${currentDate.get(Calendar.MINUTE)}")!!
val hour = lastTime.split(":")[0].toInt()
val minute = lastTime.split(":")[1].toInt()
val timepicker = MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H)
.setHour(hour)
.setMinute(minute)
.build()
timepicker.addOnPositiveButtonClickListener{
date[Calendar.HOUR_OF_DAY] = timepicker.hour
date[Calendar.MINUTE] = timepicker.minute
preferences.edit().putString("latest_timepicker_date", "${timepicker.hour}:${timepicker.minute}").apply()
onSubmit(date)
}
timepicker.show(fragmentManager, "timepicker")
}
fun showDownloadItemDetailsCard(
item: DownloadItem,
context: Activity,
status: DownloadRepository.Status,
removeItem : (DownloadItem, BottomSheetDialog) -> Unit,
downloadItem: (DownloadItem) -> Unit,
longClickDownloadButton: (DownloadItem) -> Unit,
scheduleButtonClick: (DownloadItem) -> Unit?
){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
text = item.title.ifEmpty { item.url.ifEmpty { item.playlistTitle.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } } }
setOnLongClickListener {
showFullTextDialog(context, text.toString(), context.getString(R.string.title))
true
}
}
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
text = item.author
setOnLongClickListener {
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
true
}
}
// BUTTON ----------------------------------
val btn = bottomSheet.findViewById<FloatingActionButton>(R.id.download_button_type)
val typeImageResource: Int =
when (item.type) {
DownloadViewModel.Type.audio -> {
R.drawable.ic_music
}
DownloadViewModel.Type.video -> {
R.drawable.ic_video
}
else -> {
R.drawable.ic_terminal
}
}
btn?.setImageResource(typeImageResource)
val time = bottomSheet.findViewById<Chip>(R.id.time)
val thumbnail = bottomSheet.findViewById<Chip>(R.id.thumbnail)
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
val command = bottomSheet.findViewById<Chip>(R.id.command)
when(status){
DownloadRepository.Status.Scheduled -> {
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
else {
val calendar = Calendar.getInstance()
calendar.timeInMillis = item.downloadStartTime
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
time.setOnClickListener {
scheduleButtonClick(item)
bottomSheet.dismiss()
}
}
}
else -> {
time!!.visibility = View.GONE
}
}
thumbnail?.isVisible = item.thumb.isNotBlank()
thumbnail?.apply {
isVisible = item.thumb.isNotBlank()
setOnClickListener {
openLinkIntent(context, item.thumb)
}
}
if (item.type != DownloadViewModel.Type.command){
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
View.GONE
else formatNote!!.text = item.format.format_note
}else{
formatNote?.isVisible = false
}
if (item.format.container != "") {
container!!.text = item.format.container.uppercase()
container.setChipIconResource(typeImageResource)
}else {
container!!.visibility = View.GONE
}
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec!!.visibility = View.GONE
}else{
codec!!.visibility = View.VISIBLE
codec.text = codecText
}
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
val ytdlpUtil = YTDLPUtil(context)
command?.setOnClickListener {
showGeneratedCommand(context, ytdlpUtil.parseYTDLRequestString(ytdlpUtil.buildYoutubeDLRequest(item)))
}
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
val url = item.url
link!!.text = url
link.tag = item.id
link.setOnClickListener{
bottomSheet.dismiss()
openLinkIntent(context, item.url)
}
link.setOnLongClickListener{
bottomSheet.dismiss()
copyLinkToClipBoard(context, item.url)
true
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = item.id
remove.setOnClickListener{
removeItem(item, bottomSheet)
}
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
openFile!!.visibility = View.GONE
val download = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
download?.tag = item.id
when(status){
DownloadRepository.Status.Cancelled, DownloadRepository.Status.Saved -> {
download!!.text = context.getString(R.string.download)
download.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_downloads, 0, 0, 0);
download.setOnLongClickListener {
longClickDownloadButton(item)
bottomSheet.cancel()
true
}
}
DownloadRepository.Status.Queued -> {
download!!.text = context.getString(R.string.configure_download)
download.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_retries, 0, 0, 0);
download.setOnClickListener {
longClickDownloadButton(item)
bottomSheet.cancel()
true
}
}
DownloadRepository.Status.Scheduled -> {
download!!.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_downloads, 0, 0, 0);
download.text = context.getString(R.string.download_now)
}
else -> {
download?.setOnLongClickListener {
longClickDownloadButton(item)
bottomSheet.cancel()
true
}
}
}
if (status != DownloadRepository.Status.Queued){
download?.setOnClickListener {
bottomSheet.dismiss()
downloadItem(item)
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun showHistoryItemDetailsCard(
item: HistoryItem?,
context: Activity,
isPresent: Boolean,
removeItem: (item:HistoryItem, removeFiles: Boolean) -> Unit,
redownloadItem: (HistoryItem) -> Unit,
redownloadShowDownloadCard: (HistoryItem) -> Unit,
){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
text = item!!.title.ifEmpty { item.url }
setOnLongClickListener{
showFullTextDialog(context, text.toString(), context.getString(R.string.title))
true
}
}
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
text = item!!.author
setOnLongClickListener{
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
true
}
}
// BUTTON ----------------------------------
val btn = bottomSheet.findViewById<FloatingActionButton>(R.id.download_button_type)
val typeImageResource: Int =
if (item!!.type == DownloadViewModel.Type.audio) {
if (isPresent) {
R.drawable.ic_music_downloaded
} else {
R.drawable.ic_music
}
} else if (item.type == DownloadViewModel.Type.video) {
if (isPresent) {
R.drawable.ic_video_downloaded
} else {
R.drawable.ic_video
}
}else{
R.drawable.ic_terminal
}
btn?.setImageResource(typeImageResource)
if (isPresent){
btn?.apply {
if (item.downloadPath.size > 1){
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
@OptIn(ExperimentalBadgeUtils::class) override fun onGlobalLayout() {
val badgeDrawable = BadgeDrawable.create(context)
badgeDrawable.number = item.downloadPath.size
//Important to change the position of the Badge
badgeDrawable.horizontalOffset = 25
badgeDrawable.verticalOffset = 25
BadgeUtils.attachBadgeDrawable(badgeDrawable, btn, null)
btn.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
setOnClickListener {
FileUtil.shareFileIntent(context, item.downloadPath)
}
}
}
val time = bottomSheet.findViewById<TextView>(R.id.time)
val thumbnail = bottomSheet.findViewById<TextView>(R.id.thumbnail)
val formatNote = bottomSheet.findViewById<TextView>(R.id.format_note)
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
val command = bottomSheet.findViewById<Chip>(R.id.command)
val location = bottomSheet.findViewById<Chip>(R.id.location)
val file = File(item.downloadPath.first())
val calendar = Calendar.getInstance()
calendar.timeInMillis = item.time * 1000L
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
time.isClickable = false
thumbnail?.isVisible = item.thumb.isNotBlank()
thumbnail?.apply {
isVisible = item.thumb.isNotBlank()
setOnClickListener {
openLinkIntent(context, item.thumb)
}
}
if (item.type != DownloadViewModel.Type.command){
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
View.GONE
else formatNote!!.text = item.format.format_note
}else{
formatNote?.isVisible = false
}
if (item.format.container != "" && item.downloadPath.size == 1) {
container!!.text = if (file.exists()) file.extension.uppercase() else item.format.container.uppercase()
container.setChipIconResource(typeImageResource)
}else {
container!!.visibility = View.GONE
}
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none" || item.downloadPath.size > 1){
codec!!.visibility = View.GONE
}else{
codec!!.visibility = View.VISIBLE
codec.text = codecText
}
val fileSizeReadable = FileUtil.convertFileSize(if (file.exists()) file.length() else item.format.filesize)
if (fileSizeReadable == "?" || item.downloadPath.size > 1) fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
command?.setOnClickListener {
showGeneratedCommand(context, item.command)
}
val availableFiles = item.downloadPath.filter { FileUtil.exists(it) }
location?.isVisible = availableFiles.isNotEmpty()
location?.setOnClickListener {
showFullTextDialog(context, availableFiles.joinToString("\n"), context.getString(R.string.location))
}
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
val url = item.url
link!!.text = url
link.tag = item.id
link.setOnClickListener{
bottomSheet.dismiss()
openLinkIntent(context, item.url)
}
link.setOnLongClickListener{
bottomSheet.dismiss()
copyLinkToClipBoard(context, item.url)
true
}
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
remove!!.tag = item.id
remove.setOnClickListener{
showRemoveHistoryItemDialog(item, context, delete = removeItem)
bottomSheet.dismiss()
}
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
openFile!!.tag = item.id
openFile.setOnClickListener{
if (item.downloadPath.size == 1) {
FileUtil.openFileIntent(context, item.downloadPath.first())
}else{
openMultipleFilesIntent(context, item.downloadPath)
}
}
val redownload = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
redownload!!.tag = item.id
redownload.setOnClickListener{
redownloadItem(item)
bottomSheet.cancel()
}
redownload.setOnLongClickListener {
redownloadShowDownloadCard(item)
bottomSheet.cancel()
true
}
if (!isPresent) openFile.visibility = View.GONE
else redownload.visibility = View.GONE
bottomSheet.show()
val displayMetrics = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun showFormatDetails(format: Format, activity: Activity){
val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.format_details_sheet)
val formatIdParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_id_parent)
val formatURLParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_url_parent)
val containerParent = bottomSheet.findViewById<ConstraintLayout>(R.id.container_parent)
val codecParent = bottomSheet.findViewById<ConstraintLayout>(R.id.codec_parent)
val filesizeParent = bottomSheet.findViewById<ConstraintLayout>(R.id.filesize_parent)
val formatnoteParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_note_parent)
val fpsParent = bottomSheet.findViewById<ConstraintLayout>(R.id.fps_parent)
val asrParent = bottomSheet.findViewById<ConstraintLayout>(R.id.asr_parent)
val bitrateParent = bottomSheet.findViewById<ConstraintLayout>(R.id.bitrate_parent)
val clicker = View.OnClickListener {
copyToClipboard(((it as ConstraintLayout).getChildAt(1) as TextView).text.toString(), activity)
}
val longClicker = View.OnLongClickListener {
val txt = ((it as ConstraintLayout).getChildAt(1) as TextView).text.toString()
val snackbar = Snackbar.make(bottomSheet.findViewById(android.R.id.content)!!, txt, Snackbar.LENGTH_LONG)
snackbar.setAction(android.R.string.copy){
copyToClipboard(txt, activity)
}
val snackbarView: View = snackbar.view
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
snackTextView.maxLines = 9999999
snackbar.show()
true
}
fun String?.doesntExist() : Boolean { return this.isNullOrBlank() || this == "None"}
if (format.format_id.doesntExist()) formatIdParent?.visibility = View.GONE
else {
formatIdParent?.findViewById<TextView>(R.id.format_id_value)?.text = format.format_id
formatIdParent?.setOnClickListener(clicker)
formatIdParent?.setOnLongClickListener(longClicker)
}
if (format.url.doesntExist()) formatURLParent?.visibility = View.GONE
else {
formatURLParent?.findViewById<TextView>(R.id.format_url_value)?.text = format.url
formatURLParent?.setOnClickListener(clicker)
formatURLParent?.setOnLongClickListener(longClicker)
}
if (format.container.doesntExist()) containerParent?.visibility = View.GONE
else {
containerParent?.findViewById<TextView>(R.id.container_value)?.text = format.container
containerParent?.setOnClickListener(clicker)
containerParent?.setOnLongClickListener(longClicker)
}
val codecField =
if (format.encoding != "") {
format.encoding.uppercase()
}else if (format.vcodec != "none" && format.vcodec != ""){
format.vcodec.uppercase()
} else {
format.acodec.uppercase()
}
if (codecField.doesntExist()) codecParent?.visibility = View.GONE
else {
codecParent?.findViewById<TextView>(R.id.codec_value)?.text = codecField
codecParent?.setOnClickListener(clicker)
codecParent?.setOnLongClickListener(longClicker)
}
if (format.filesize != 0L) filesizeParent?.visibility = View.GONE
else {
filesizeParent?.findViewById<TextView>(R.id.filesize_value)?.text = FileUtil.convertFileSize(format.filesize)
filesizeParent?.setOnClickListener(clicker)
filesizeParent?.setOnLongClickListener(longClicker)
}
if (format.format_note.doesntExist()) formatnoteParent?.visibility = View.GONE
else {
formatnoteParent?.findViewById<TextView>(R.id.format_note_value)?.text = format.format_note
formatnoteParent?.setOnClickListener(clicker)
formatnoteParent?.setOnLongClickListener(longClicker)
}
if (format.fps.doesntExist() || format.fps == "0") fpsParent?.visibility = View.GONE
else {
fpsParent?.findViewById<TextView>(R.id.fps_value)?.text = format.fps
fpsParent?.setOnClickListener(clicker)
fpsParent?.setOnLongClickListener(longClicker)
}
if (format.asr.doesntExist()) asrParent?.visibility = View.GONE
else {
asrParent?.findViewById<TextView>(R.id.asr_value)?.text = format.asr
asrParent?.setOnClickListener(clicker)
asrParent?.setOnLongClickListener(longClicker)
}
if (format.tbr.doesntExist()) bitrateParent?.visibility = View.GONE
else{
bitrateParent?.findViewById<TextView>(R.id.bitrate_value)?.text = format.tbr
bitrateParent?.setOnClickListener(clicker)
bitrateParent?.setOnLongClickListener(longClicker)
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
@SuppressLint("RestrictedApi")
fun showSubtitleLanguagesDialog(context: Activity, currentValue: String, ok: (newValue: String) -> Unit){
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.subtitle_languages))
val view = context.layoutInflater.inflate(R.layout.subtitle_dialog, null)
val editText = view.findViewById<EditText>(R.id.subtitle_edittext)
view.findViewById<TextInputLayout>(R.id.subtitle).hint = context.getString(R.string.subtitle_languages)
editText.setText(currentValue)
editText.setSelection(editText.text.length)
builder.setView(view)
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
ok(editText.text.toString())
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
builder.setNeutralButton("?") { _: DialogInterface?, _: Int ->
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/yt-dlp/yt-dlp#subtitle-options"))
context.startActivity(browserIntent)
}
view.findViewById<View>(R.id.suggested).visibility = View.GONE
val dialog = builder.create()
dialog.show()
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
//handle suggestion chips
CoroutineScope(Dispatchers.IO).launch {
val chipGroup = view.findViewById<ChipGroup>(R.id.subtitle_suggested_chipgroup)
val chips = mutableListOf<Chip>()
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
s.split("-", ".*").run {
if (s.endsWith(".*")) {
val name = "${Locale(this[0]).displayName} (${context.getString(R.string.all)})"
tmp.text = name
}else{
if (this.size == 1) {
Locale(this[0])
} else {
Locale(this[0], this[1])
}.apply {
tmp.text = this.displayName
}
}
}
tmp.tag = s
tmp.id = index
tmp.setOnClickListener {
val c = it as Chip
val currentLanguages = editText.text.toString().split(",").filter { f -> f.isNotBlank() }.toMutableList()
if(!c.isChecked){
editText.setText(currentLanguages.filter { l -> l != c.tag }.joinToString(","))
editText.setSelection(editText.text.length)
}else{
currentLanguages.add(c.tag.toString())
editText.setText(currentLanguages.joinToString(","))
editText.setSelection(editText.text.length)
}
}
chips.add(tmp)
}
withContext(Dispatchers.Main){
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
chips.forEach {
it.isChecked = editText.text.contains(it.text)
chipGroup!!.addView(it)
}
}
}
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
fun copyToClipboard(text: String, activity: Activity){
val clipboard = activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(text, text)
clipboard.setPrimaryClip(clip)
Toast.makeText(activity, activity.getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT)
.show()
}
suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, onlyOne: Boolean = false, itemSelected: (itemSelected: List<CommandTemplate>) -> Unit) {
val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.command_template_list)
val linearLayout = bottomSheet.findViewById<LinearLayout>(R.id.command_list_linear_layout)
val list = withContext(Dispatchers.IO){
commandTemplateViewModel.getAll()
}
linearLayout!!.removeAllViews()
val selectedItems = mutableListOf<CommandTemplate>()
val ok = bottomSheet.findViewById<MaterialButton>(R.id.command_ok)
ok?.isVisible = !onlyOne
ok?.isEnabled = list.size == 1
list.forEach {template ->
val item = activity.layoutInflater.inflate(R.layout.command_template_item, linearLayout, false) as MaterialCardView
item.findViewById<TextView>(R.id.title).text = template.title
item.findViewById<TextView>(R.id.content).text = template.content
item.setOnClickListener {
if (selectedItems.contains(template)){
selectedItems.remove(template)
(it as MaterialCardView).isChecked = false
}else{
selectedItems.add(template)
(it as MaterialCardView).isChecked = true
}
if (onlyOne) {
itemSelected(listOf(template))
bottomSheet.cancel()
}
ok?.isEnabled = selectedItems.isNotEmpty()
}
linearLayout.addView(item)
}
ok?.setOnClickListener {
itemSelected(selectedItems.ifEmpty { listOf(list.first()) })
bottomSheet.cancel()
}
bottomSheet.setOnShowListener {
val behavior = bottomSheet.behavior
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
}
bottomSheet.show()
}
suspend fun showShortcuts(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: String) -> Unit, itemRemoved: (itemRemoved: String) -> Unit){
val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.template_shortcuts_list)
val chipGroup = bottomSheet.findViewById<ChipGroup>(R.id.shortcutsChipGroup)
val shortcutList = withContext(Dispatchers.IO){
commandTemplateViewModel.getAllShortcuts()
}
chipGroup!!.removeAllViews()
shortcutList.forEach {shortcut ->
val chip = activity.layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
chip.text = shortcut.content
chip.setOnClickListener {
chip.isChecked = false
itemSelected(shortcut.content)
}
chipGroup.addView(chip)
}
bottomSheet.setOnShowListener {
val behavior = bottomSheet.behavior
val displayMetrics = DisplayMetrics()
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 3
}
bottomSheet.show()
}
fun configureVideo(
view: View,
context: Activity,
items: List<DownloadItem>,
embedSubsClicked : (Boolean) -> Unit,
addChaptersClicked: (Boolean) -> Unit,
splitByChaptersClicked: (Boolean) -> Unit,
saveThumbnailClicked: (Boolean) -> Unit,
sponsorBlockItemsSet: (values: Array<String>, checkedItems: List<Boolean>) -> Unit,
cutClicked: (VideoCutListener) -> Unit,
cutValueChanged: (String) -> Unit,
cutDisabledClicked: () -> Unit,
filenameTemplateSet: (String) -> Unit,
saveSubtitlesClicked: (Boolean) -> Unit,
saveAutoSubtitlesClicked: (Boolean) -> Unit,
subtitleLanguagesSet: (String) -> Unit,
removeAudioClicked: (Boolean) -> Unit,
recodeVideoClicked: (Boolean) -> Unit,
alsoDownloadAsAudioClicked: (Boolean) -> Unit,
extraCommandsClicked: () -> Unit
){
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val addChapters = view.findViewById<Chip>(R.id.add_chapters)
addChapters!!.isChecked = items.all { it.videoPreferences.addChapters }
addChapters.setOnClickListener{
addChaptersClicked(addChapters.isChecked)
}
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
if(items.size == 1 && items[0].downloadSections.isNotBlank()){
splitByChapters.isEnabled = false
splitByChapters.isChecked = false
}else{
splitByChapters!!.isChecked = items.all { it.videoPreferences.splitByChapters }
}
if (splitByChapters.isChecked){
items.forEach { it.videoPreferences.addChapters = false }
addChapters.isChecked = false
addChapters.isEnabled = false
addChaptersClicked(false)
}
splitByChapters.setOnClickListener {
if (splitByChapters.isChecked){
addChapters.isEnabled = false
addChapters.isChecked = false
addChaptersClicked(false)
}else{
addChapters.isEnabled = true
}
splitByChaptersClicked(splitByChapters.isChecked)
}
val saveThumbnail = view.findViewById<Chip>(R.id.save_thumbnail)
saveThumbnail!!.isChecked = items.all { it.SaveThumb }
saveThumbnail.setOnClickListener {
saveThumbnailClicked(saveThumbnail.isChecked)
}
val adjustSubtitles = view.findViewById<Chip>(R.id.adjust_subtitles)
adjustSubtitles.setOnClickListener {
val adjustSubtitleView = context.layoutInflater.inflate(R.layout.subtitle_download_preferences_dialog, null)
val embedSubs = adjustSubtitleView.findViewById<MaterialSwitch>(R.id.embed_subtitles)
val saveSubtitles = adjustSubtitleView.findViewById<MaterialSwitch>(R.id.save_subs)
val saveAutoSubtitles = adjustSubtitleView.findViewById<MaterialSwitch>(R.id.save_auto_subs)
val subtitleLanguages = adjustSubtitleView.findViewById<ConstraintLayout>(R.id.subtitle_languages)
val subtitleLanguagesDescription = adjustSubtitleView.findViewById<TextView>(R.id.subtitle)
subtitleLanguagesDescription.text = items.first().videoPreferences.subsLanguages
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
embedSubs.setOnClickListener {
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked
embedSubsClicked(embedSubs.isChecked)
}
if (items.all { it.videoPreferences.writeSubs}) {
saveSubtitles.isChecked = true
subtitleLanguages.visibility = View.VISIBLE
}
if (items.all { it.videoPreferences.writeAutoSubs}) {
saveAutoSubtitles.isChecked = true
subtitleLanguages.visibility = View.VISIBLE
}
saveSubtitles.setOnCheckedChangeListener { _, _ ->
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
saveSubtitlesClicked(saveSubtitles.isChecked)
}
saveAutoSubtitles.setOnCheckedChangeListener { _, _ ->
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
saveAutoSubtitlesClicked(saveAutoSubtitles.isChecked)
}
subtitleLanguages.isClickable = embedSubs.isChecked || saveSubtitles.isChecked
subtitleLanguages.setOnClickListener {
val currentSubtitleLang = if (items.size == 1 || items.all { it.videoPreferences.subsLanguages == items[0].videoPreferences.subsLanguages }){
items[0].videoPreferences.subsLanguages
}else {
""
}.ifEmpty { sharedPreferences.getString("subs_lang", "en.*,.*-orig")!! }
showSubtitleLanguagesDialog(context, currentSubtitleLang){
subtitleLanguagesSet(it)
subtitleLanguagesDescription.text = it
}
}
val adjustSubtitleDialog = MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.subtitles))
.setView(adjustSubtitleView)
.setIcon(R.drawable.ic_subtitles)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
adjustSubtitleDialog.show()
}
if (items.size == 1 && items.first().id == 0L){
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
adjustAudio.setOnClickListener {
val adjustAudioView = context.layoutInflater.inflate(R.layout.audio_download_preferences_dialog, null)
adjustAudioView.findViewById<MaterialSwitch>(R.id.remove_audio).apply {
isChecked = items.first().videoPreferences.removeAudio
setOnCheckedChangeListener { _, b ->
removeAudioClicked(b)
}
}
adjustAudioView.findViewById<MaterialSwitch>(R.id.also_download_audio).apply {
isChecked = items.first().videoPreferences.alsoDownloadAsAudio
setOnCheckedChangeListener { _, b ->
alsoDownloadAsAudioClicked(b)
}
}
val adjustAudioDialog = MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.audio))
.setView(adjustAudioView)
.setIcon(R.drawable.ic_music)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
adjustAudioDialog.show()
}
}else{
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
adjustAudio.isVisible = false
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
removeAudio.isVisible = true
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
removeAudio.setOnCheckedChangeListener { _, _ ->
removeAudioClicked(removeAudio.isChecked)
}
}
val recodeVideo = view.findViewById<Chip>(R.id.recode_video)
recodeVideo.isChecked = items.all { it.videoPreferences.recodeVideo }
recodeVideo.setOnCheckedChangeListener { _, _ ->
recodeVideoClicked(recodeVideo.isChecked)
}
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
sponsorBlock.isEnabled = sharedPreferences.getBoolean("use_sponsorblock", true)
sponsorBlock!!.setOnClickListener {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.select_sponsorblock_filtering))
val values = context.resources.getStringArray(R.array.sponsorblock_settings_values)
val entries = context.resources.getStringArray(R.array.sponsorblock_settings_entries)
val checkedItems : ArrayList<Boolean> = arrayListOf()
values.forEach {
if (items.all{ i -> i.videoPreferences.sponsorBlockFilters.contains(it)}) {
checkedItems.add(true)
}else{
checkedItems.add(false)
}
}
builder.setMultiChoiceItems(
entries,
checkedItems.toBooleanArray()
) { _, which, isChecked ->
checkedItems[which] = isChecked
}
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
sponsorBlockItemsSet(values, checkedItems)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
}
val cut = view.findViewById<Chip>(R.id.cut)
if (items.size > 1 || items.first().url.isEmpty()) cut.isVisible = false
else{
val invalidDuration = items[0].duration == "-1"
if(items[0].duration.isNotEmpty() && !invalidDuration){
val downloadItem = items[0]
cut.alpha = 1f
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
val cutVideoListener = object : VideoCutListener {
override fun onChangeCut(list: List<String>) {
if (list.isEmpty()){
cutValueChanged("")
cut.text = context.getString(R.string.cut)
splitByChapters.isEnabled = true
splitByChapters.isChecked = downloadItem.videoPreferences.splitByChapters
if (splitByChapters.isChecked){
addChapters.isEnabled = false
addChapters.isChecked = false
}else{
addChapters.isEnabled = true
}
}else{
var value = ""
list.forEach {
value += "$it;"
}
cutValueChanged(value)
cut.text = value.dropLast(1)
splitByChapters.isEnabled = false
splitByChapters.isChecked = false
addChapters.isEnabled = true
}
}
}
cut.setOnClickListener {
cutClicked(cutVideoListener)
}
}else{
cut.alpha = 0.3f
if (!invalidDuration) {
cut.setOnClickListener {
cutDisabledClicked()
}
}
}
}
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
filenameTemplate.setOnClickListener {
val currentFilename = if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
items[0].customFileNameTemplate
}else {
""
}
showFilenameTemplateDialog(context, currentFilename) {
filenameTemplateSet(it)
}
}
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE
extraCommands.setOnClickListener {
extraCommandsClicked()
}
}else{
extraCommands.visibility = View.GONE
}
}
fun configureAudio(
view: View,
context: Activity,
items: List<DownloadItem>,
embedThumbClicked: (Boolean) -> Unit,
cropThumbClicked: (Boolean) -> Unit,
splitByChaptersClicked: (Boolean) -> Unit,
filenameTemplateSet: (String) -> Unit,
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
cutClicked: (VideoCutListener) -> Unit,
cutDisabledClicked: () -> Unit,
cutValueChanged: (String) -> Unit,
extraCommandsClicked: () -> Unit
){
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
embedThumb!!.isChecked = items.all { it.audioPreferences.embedThumb }
cropThumb.isVisible = embedThumb.isChecked
embedThumb.setOnClickListener {
embedThumbClicked(embedThumb.isChecked)
cropThumb.isVisible = embedThumb.isChecked
}
cropThumb!!.isChecked = items.all { it.audioPreferences.cropThumb == true }
cropThumb.setOnClickListener {
cropThumbClicked(cropThumb.isChecked)
}
val splitByChapters = view.findViewById<Chip>(R.id.split_by_chapters)
if (items.size == 1 && items[0].downloadSections.isNotBlank()){
splitByChapters.isEnabled = false
splitByChapters.isChecked = false
}else{
splitByChapters!!.isChecked = items.all { it.audioPreferences.splitByChapters }
}
splitByChapters.setOnClickListener {
splitByChaptersClicked(splitByChapters.isChecked)
}
val filenameTemplate = view.findViewById<Chip>(R.id.filename_template)
filenameTemplate.setOnClickListener {
val currentFilename = if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
items[0].customFileNameTemplate
}else {
""
}
showFilenameTemplateDialog(context, currentFilename) {
filenameTemplateSet(it)
}
}
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
sponsorBlock!!.setOnClickListener {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.select_sponsorblock_filtering))
val values = context.resources.getStringArray(R.array.sponsorblock_settings_values)
val entries = context.resources.getStringArray(R.array.sponsorblock_settings_entries)
val checkedItems : ArrayList<Boolean> = arrayListOf()
values.forEach {
if (items.all{ i -> i.audioPreferences.sponsorBlockFilters.contains(it)}) {
checkedItems.add(true)
}else{
checkedItems.add(false)
}
}
builder.setMultiChoiceItems(
entries,
checkedItems.toBooleanArray()
) { _, which, isChecked ->
checkedItems[which] = isChecked
}
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
sponsorBlockItemsSet(values, checkedItems)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
}
val cut = view.findViewById<Chip>(R.id.cut)
if (items.size > 1 || items.first().url.isEmpty()) cut.isVisible = false
else{
val downloadItem = items[0]
val invalidDuration = downloadItem.duration == "-1"
if (downloadItem.duration.isNotEmpty() && !invalidDuration){
cut.alpha = 1f
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
val cutVideoListener = object : VideoCutListener {
override fun onChangeCut(list: List<String>) {
if (list.isEmpty()){
cutValueChanged("")
cut.text = context.getString(R.string.cut)
splitByChapters.isEnabled = true
splitByChapters.isChecked = downloadItem.audioPreferences.splitByChapters
}else{
var value = ""
list.forEach {
value += "$it;"
}
cutValueChanged(value)
cut.text = value.dropLast(1)
splitByChapters.isEnabled = false
splitByChapters.isChecked = false
}
}
}
cut.setOnClickListener {
cutClicked(cutVideoListener)
}
}else{
cut.alpha = 0.3f
if (!invalidDuration) {
cut.setOnClickListener {
cutDisabledClicked()
}
}
}
}
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands.visibility = View.VISIBLE
extraCommands.setOnClickListener {
extraCommandsClicked()
}
}else{
extraCommands.visibility = View.GONE
}
}
fun configureCommand(
view: View,
size: Int,
shortcutCount: Int,
newTemplateClicked: () -> Unit,
editSelectedClicked: () -> Unit,
shortcutClicked: suspend () -> Unit,
){
val newTemplate : Chip = view.findViewById(R.id.newTemplate)
newTemplate.setOnClickListener {
newTemplateClicked()
}
val editSelected : Chip = view.findViewById(R.id.editSelected)
editSelected.isEnabled = size == 1
editSelected.setOnClickListener {
editSelectedClicked()
}
val shortcuts = view.findViewById<View>(R.id.shortcut)
shortcuts.isEnabled = shortcutCount > 0
shortcuts.setOnClickListener {
runBlocking {
shortcutClicked()
}
}
}
fun handleNoResults(context: Activity, message: String, continueAnyway: Boolean = false, continued: () -> Unit, closed: () -> Unit) {
val errDialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.no_results)
.setMessage(message)
errDialog.setPositiveButton(R.string.copy_log) { d:DialogInterface?, _:Int ->
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(message)
d?.dismiss()
}
if (continueAnyway) {
errDialog.setNeutralButton(R.string.continue_anyway) {d: DialogInterface?, _:Int ->
continued()
}
}
errDialog.setOnCancelListener {
closed()
}
errDialog.show()
}
fun showErrorDialog(context: Context, it: String){
val title = context.getString(R.string.errored)
val errDialog = MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(it)
.setPositiveButton(android.R.string.copy) { d:DialogInterface?, _:Int ->
val clipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setText(it)
d?.dismiss()
}
errDialog.show()
}
fun showGenericDeleteDialog(context: Context, itemTitle: String, accepted: () -> Unit){
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete) + " \"" + itemTitle + "\"!")
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
accepted()
}
deleteDialog.show()
}
fun showGenericDeleteAllDialog(context: Context, accepted: () -> Unit){
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
accepted()
}
deleteDialog.show()
}
fun showRemoveHistoryItemDialog(item: HistoryItem, context: Activity, delete: (item: HistoryItem, deleteFile: Boolean) -> Unit){
val deleteFile = booleanArrayOf(false)
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
val path = item.downloadPath
if (path.any { File(it).exists() && it.isNotEmpty() }) {
deleteDialog.setMultiChoiceItems(
arrayOf(context.getString(R.string.delete_file_too)),
booleanArrayOf(false)
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
}
deleteDialog.setNegativeButton(context.getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(context.getString(R.string.ok)) { _: DialogInterface?, _: Int ->
delete(item, deleteFile[0])
}
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)
val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null)
val textInput = view.findViewById<TextInputLayout>(R.id.filename)
val editText = view.findViewById<EditText>(R.id.filename_edittext)
textInput.hint = context.getString(R.string.file_name_template)
editText.setText(currentFilename)
editText.setSelection(editText.text.length)
builder.setView(view)
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
filenameSelected(editText.text.toString())
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
builder.setNeutralButton("?") { _: DialogInterface?, _: Int ->
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/yt-dlp/yt-dlp#user-content-outtmpl-postprocess-note"))
context.startActivity(browserIntent)
}
val dialog = builder.create()
dialog.show()
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val myTemplates = preferences.getStringSet("filename_templates", setOf())!!.toMutableSet()
val myTemplatesView = view.findViewById<View>(R.id.mytemplates)
val myTemplatesChipGroup = view.findViewById<ChipGroup>(R.id.filename_personal_chipgroup)
textInput.setEndIconOnClickListener {
if (editText.text.isBlank()) return@setEndIconOnClickListener
val chip = createPersonalFilenameTemplateChip(context, editText.text.toString(), myTemplatesChipGroup,
onClick = { chip ->
if (chip.isChecked){
editText.setText(chip.text.toString())
val selectionStart = editText.selectionStart
editText.setSelection(selectionStart + chip.text.length)
}else{
editText.setText("")
}
},
onLongClick = {
showGenericDeleteDialog(context, it.text.toString()) {
myTemplates.remove(it.text.toString())
myTemplatesView.isVisible = myTemplates.isNotEmpty()
myTemplatesChipGroup.removeView(it)
preferences.edit().putStringSet("filename_templates", myTemplates).apply()
}
}
)
chip.isChecked = true
textInput.endIconDrawable = null
myTemplates.add(editText.text.toString())
preferences.edit().putStringSet("filename_templates", myTemplates).apply()
myTemplatesChipGroup.addView(chip)
myTemplatesView.isVisible = true
}
CoroutineScope(Dispatchers.IO).launch {
//handle personal template chips
val mychips = mutableListOf<Chip>()
myTemplates.forEachIndexed { index, s ->
val chip = createPersonalFilenameTemplateChip(context, s, myTemplatesChipGroup,
onClick = { c ->
if (c.isChecked){
editText.setText(c.text.toString())
val selectionStart = editText.selectionStart
editText.setSelection(selectionStart + c.text.length)
}else{
editText.setText("")
}
},
onLongClick = { c ->
showGenericDeleteDialog(context, c.text.toString()) {
myTemplates.remove(c.text.toString())
myTemplatesView.isVisible = myTemplates.isNotEmpty()
myTemplatesChipGroup.removeView(c)
preferences.edit().putStringSet("filename_templates", myTemplates).apply()
}
}
)
mychips.add(chip)
}
//handle suggestion chips
val chipGroup = view.findViewById<ChipGroup>(R.id.filename_suggested_chipgroup)
val chips = mutableListOf<Chip>()
context.getStringArray(R.array.filename_templates).forEachIndexed { index, s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
tmp.text = s.split("___")[0]
tmp.id = index
if (Build.VERSION.SDK_INT >= 26){
tmp.tooltipText = s.split("___")[1]
}
tmp.setOnClickListener {
val c = it as Chip
val selectionStart = editText.selectionStart
editText.text.insert(selectionStart, c.text.toString())
editText.setSelection(selectionStart + c.text.length)
}
chips.add(tmp)
}
withContext(Dispatchers.Main){
if (mychips.isNotEmpty()){
myTemplatesView.isVisible = true
mychips.forEach {
it.isChecked = editText.text.contains(it.text)
myTemplatesChipGroup!!.addView(it)
}
}
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
chips.forEach {
it.isChecked = editText.text.contains(it.text)
chipGroup!!.addView(it)
}
editText.doOnTextChanged { text, start, before, count ->
chips.forEach {
it.isChecked = editText.text.contains(it.text)
}
mychips.forEach {
it.isChecked = editText.text.contains(it.text)
}
if (!myTemplates.contains(editText.text.toString()) && editText.text.isNotBlank()){
textInput.endIconDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_plus)
}else{
textInput.endIconDrawable = null
}
}
}
}
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
private fun createPersonalFilenameTemplateChip(context: Activity, text: String, myChipGroup: ChipGroup, onClick: (f: Chip) -> Unit, onLongClick: (f: Chip) -> Unit) : Chip {
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, myChipGroup, false) as Chip
tmp.text = text
if (Build.VERSION.SDK_INT >= 26){
tmp.tooltipText = text
}
tmp.setOnClickListener {
onClick(it as Chip)
}
tmp.setOnLongClickListener {
onLongClick(it as Chip)
true
}
return tmp
}
fun showPipedInstancesDialog(context: Activity, currentInstance: String, instanceSelected: (f: String) -> Unit){
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.piped_instance))
val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null)
val editText = view.findViewById<EditText>(R.id.filename_edittext)
view.findViewById<TextInputLayout>(R.id.filename).apply {
hint = context.getString(R.string.piped_instance)
endIconMode = END_ICON_NONE
}
editText.setText(currentInstance)
editText.setSelection(editText.text.length)
builder.setView(view)
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
instanceSelected(editText.text.toString())
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
builder.setNeutralButton("?") { _: DialogInterface?, _: Int ->
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/TeamPiped/Piped/wiki/Instances"))
context.startActivity(browserIntent)
}
view.findViewById<View>(R.id.suggested).visibility = View.GONE
val dialog = builder.create()
dialog.show()
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
//handle suggestion chips
CoroutineScope(Dispatchers.IO).launch {
val chipGroup = view.findViewById<ChipGroup>(R.id.filename_suggested_chipgroup)
val chips = mutableListOf<Chip>()
val instances = PipedApiUtil(context).getPipedInstances().ifEmpty { return@launch }
instances.forEach { s ->
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
tmp.text = s
tmp.setOnClickListener {
val c = it as Chip
c.toggle()
editText.setText(c.text.toString())
editText.setSelection(c.text.length)
}
chips.add(tmp)
}
withContext(Dispatchers.Main){
view.findViewById<View>(R.id.suggested).visibility = View.VISIBLE
chips.forEach {
it.isChecked = editText.text == it.text
chipGroup!!.addView(it)
}
editText.doOnTextChanged { text, start, before, count ->
chips.forEach {
it.isChecked = editText.text == it.text
}
}
}
}
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
private fun showGeneratedCommand(context: Activity, command: String){
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.command))
val view = context.layoutInflater.inflate(R.layout.command_dialog, null)
view.findViewById<TextView>(R.id.commandText).apply {
text = command
isLongClickable = true
setTextIsSelectable(true)
enableTextHighlight()
setPadding(20, 0, 20, 0)
}
builder.setView(view)
builder.setPositiveButton(
context.getString(android.R.string.copy)
) { _: DialogInterface?, _: Int ->
copyToClipboard(command, context)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.dismiss)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
}
private fun showFullTextDialog(context: Activity, txt: String, title: String){
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(title)
val view = context.layoutInflater.inflate(R.layout.command_dialog, null)
val text = view.findViewById<TextView>(R.id.commandText)
text.text = txt
text.isLongClickable = true
text.setTextIsSelectable(true)
builder.setView(view)
builder.setPositiveButton(
context.getString(android.R.string.copy)
) { _: DialogInterface?, _: Int ->
copyToClipboard(txt, context)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.dismiss)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
}
fun getAlphaAnimator(view: View, alphaTo: Float): Animator {
return ObjectAnimator.ofFloat(view, View.ALPHA, view.alpha, alphaTo)
}
fun getScaleXAnimator(view: View, scaleTo: Float): Animator {
return ObjectAnimator.ofFloat(view, View.SCALE_X, view.scaleX, scaleTo)
}
fun getScaleYAnimator(view: View, scaleTo: Float): Animator {
return ObjectAnimator.ofFloat(view, View.SCALE_Y, view.scaleX, scaleTo)
}
fun getElevationAnimator(view: MaterialCardView, @DimenRes elevationTo: Int): Animator {
val valueFrom = view.cardElevation
val valueTo = view.context.resources.getDimensionPixelSize(elevationTo).toFloat()
return ValueAnimator.ofFloat(valueFrom, valueTo).apply {
addUpdateListener {
view.cardElevation = it.animatedValue as? Float ?: return@addUpdateListener
}
}
}
fun openMultipleFilesIntent(context: Activity, path: List<String>){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.filepathlist)
val list = bottomSheet.findViewById<LinearLayout>(R.id.filepath_list)
list?.apply {
path.forEach {path ->
val file = File(path)
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
item.apply {
findViewById<TextView>(R.id.file_name).text = file.nameWithoutExtension
findViewById<TextView>(R.id.duration).apply {
val duration = file.getMediaDuration(context)
isVisible = duration > 0
text = duration.toStringDuration(Locale.US)
}
findViewById<TextView>(R.id.filesize).text = FileUtil.convertFileSize(file.length())
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
if (!file.exists()){
isEnabled = false
alpha = 0.7f
}
isEnabled = file.exists()
setOnClickListener {
FileUtil.openFileIntent(context, path)
bottomSheet.dismiss()
}
}
list.addView(item)
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
private fun showAddEditCustomYTDLPSource(context: Activity, title: String = "", repo: String = "", created: (title: String, repo: String) -> Unit) {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.new_source))
val view = context.layoutInflater.inflate(R.layout.create_ytdlp_sources, null)
val titleTextInput = view.findViewById<TextInputLayout>(R.id.title_textinput).editText!!
val repoTextInput = view.findViewById<TextInputLayout>(R.id.repo_textinput).editText!!
titleTextInput.setText(title)
repoTextInput.setText(repo)
builder.setView(view)
builder.setPositiveButton(context.getString(R.string.add)) { _: DialogInterface?, _: Int ->
val t = titleTextInput.text.toString()
val r = repoTextInput.text.toString()
created(t, r)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
val createBtn = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
createBtn.isEnabled = titleTextInput.text.toString().isNotBlank() && repoTextInput.text.toString().isNotBlank()
titleTextInput.doAfterTextChanged {
createBtn.isEnabled = titleTextInput.text.toString().isNotBlank() && repoTextInput.text.toString().isNotBlank()
}
repoTextInput.doAfterTextChanged {
createBtn.isEnabled = titleTextInput.text.toString().isNotBlank() && repoTextInput.text.toString().isNotBlank()
}
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
repoTextInput.postDelayed({
repoTextInput.requestFocus()
imm.showSoftInput(repoTextInput, 0)
}, 300)
}
fun showYTDLSourceBottomSheet(context: Activity, preferences: SharedPreferences, selectedSource: (title: String, repo: String) -> Unit) {
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.ytdlp_sources_list)
val list = preferences.getStringSet("custom_ytdlp_sources", emptySet())!!.toMutableList()
val parentView = bottomSheet.findViewById<LinearLayout>(R.id.sourcesList)!!
bottomSheet.findViewById<View>(R.id.add)?.apply {
setOnClickListener {
showAddEditCustomYTDLPSource(context) { title, repo ->
list.add("${title}___${repo}")
preferences.edit().putStringSet("custom_ytdlp_sources", list.toSet()).apply()
bottomSheet.dismiss()
selectedSource(title, repo)
}
}
}
val defaultSourceTitles = context.getStringArray(R.array.ytdlp_source)
val defaultSourceValues = context.getStringArray(R.array.ytdlp_source_values)
val tmp = list.toMutableList()
tmp.addAll(0, defaultSourceTitles.mapIndexed { index, s -> "${s}___${defaultSourceValues[index]}" })
tmp.forEach { s ->
val title = s.split("___")[0]
val source = s.split("___")[1]
val isEditable = !defaultSourceValues.contains(source)
val child = LayoutInflater.from(context).inflate(R.layout.custom_ytdlp_source, null)
child.findViewById<MaterialCardView>(R.id.sampleCustomSource).setOnClickListener {
bottomSheet.dismiss()
selectedSource(title, source)
}
child.findViewById<TextView>(R.id.sampleTitle).apply {
text = title
}
child.findViewById<RadioButton>(R.id.sampleRadioBtn).apply {
isChecked = preferences.getString("ytdlp_source", "stable") == source
setOnClickListener {
bottomSheet.dismiss()
selectedSource(title, source)
}
}
child.findViewById<TextView>(R.id.sampleRepo).text = source
child.findViewById<View>(R.id.options).apply {
isVisible = isEditable
setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.custom_ytdlp_source_menu, popup.menu)
popup.setOnMenuItemClickListener { m ->
when (m.itemId) {
R.id.edit -> {
popup.dismiss()
showAddEditCustomYTDLPSource(context, title, source) { nt, nr ->
child.findViewById<TextView>(R.id.sampleTitle).text = nt
child.findViewById<TextView>(R.id.sampleRepo).text = nr
val index = list.indexOf(s)
list[index] = "${nt}___${nr}"
preferences.edit().putStringSet("custom_ytdlp_sources", list.toSet()).apply()
if (child.findViewById<RadioButton>(R.id.sampleRadioBtn).isChecked) {
selectedSource(nt, nr)
}
}
}
R.id.remove -> {
popup.dismiss()
showGenericDeleteDialog(context, title) {
list.remove(s)
preferences.edit()
.putStringSet("custom_ytdlp_sources", list.toSet()).apply()
if (child.findViewById<RadioButton>(R.id.sampleRadioBtn).isChecked) {
parentView.children.first().performClick()
}
parentView.removeView(child)
}
}
}
true
}
popup.show()
}
}
parentView.addView(child)
}
bottomSheet.show()
bottomSheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
}