more fixes
This commit is contained in:
parent
db55894fdc
commit
1426ceef0a
24 changed files with 735 additions and 679 deletions
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.deniscerri.ytdl.database.models
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
data class FormatRecyclerView(
|
||||||
|
var label: String? = null,
|
||||||
|
var format: Format? = null,
|
||||||
|
)
|
||||||
|
|
@ -57,7 +57,7 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
|
|
||||||
suspend fun getHomeRecommendations(){
|
suspend fun getHomeRecommendations(){
|
||||||
deleteAll()
|
deleteAll()
|
||||||
val category = sharedPreferences.getString("youtube_home_recommendations", "")
|
val category = sharedPreferences.getString("home_recommendations", "")
|
||||||
val items = when(category) {
|
val items = when(category) {
|
||||||
"newpipe" -> newPipeUtil.getTrending()
|
"newpipe" -> newPipeUtil.getTrending()
|
||||||
"yt_api" -> youtubeApiUtil.getTrending()
|
"yt_api" -> youtubeApiUtil.getTrending()
|
||||||
|
|
@ -65,6 +65,11 @@ class ResultRepository(private val resultDao: ResultDao, private val commandTemp
|
||||||
"yt_dlp_recommendations" -> ytdlpUtil.getYoutubeRecommendations()
|
"yt_dlp_recommendations" -> ytdlpUtil.getYoutubeRecommendations()
|
||||||
"yt_dlp_liked" -> ytdlpUtil.getYoutubeLikedVideos()
|
"yt_dlp_liked" -> ytdlpUtil.getYoutubeLikedVideos()
|
||||||
"yt_dlp_watch_history" -> ytdlpUtil.getYoutubeWatchHistory()
|
"yt_dlp_watch_history" -> ytdlpUtil.getYoutubeWatchHistory()
|
||||||
|
"custom" -> {
|
||||||
|
val customURL = sharedPreferences.getString("custom_home_recommendation_url", "")
|
||||||
|
if (customURL.isNullOrBlank()) arrayListOf()
|
||||||
|
else ytdlpUtil.getFromYTDL(customURL)
|
||||||
|
}
|
||||||
else -> arrayListOf()
|
else -> arrayListOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1099,8 +1099,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it.type == Type.video) {
|
if (it.type == Type.video) {
|
||||||
|
it.videoPreferences.audioFormatIDs.clear()
|
||||||
ft.audioFormats?.map { a -> a.format_id }?.let { list ->
|
ft.audioFormats?.map { a -> a.format_id }?.let { list ->
|
||||||
it.videoPreferences.audioFormatIDs.clear()
|
|
||||||
it.videoPreferences.audioFormatIDs.addAll(list)
|
it.videoPreferences.audioFormatIDs.addAll(list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1342,8 +1342,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
cancelAllDownloadsImpl()
|
cancelAllDownloadsImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelAllDownloadsImpl() {
|
private suspend fun cancelAllDownloadsImpl() {
|
||||||
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
||||||
|
val activeDownloadsList = withContext(Dispatchers.IO){
|
||||||
|
getActiveDownloads()
|
||||||
|
}
|
||||||
|
activeDownloadsList.forEach {
|
||||||
|
cancelDownloadOnly(it.id)
|
||||||
|
}
|
||||||
cancelActiveQueued()
|
cancelActiveQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
package com.deniscerri.ytdl.database.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
|
import com.deniscerri.ytdl.R
|
||||||
|
import com.deniscerri.ytdl.database.DBManager
|
||||||
|
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdl.database.models.Format
|
||||||
|
import com.deniscerri.ytdl.database.models.FormatRecyclerView
|
||||||
|
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||||
|
import com.deniscerri.ytdl.ui.downloadcard.FormatSelectionBottomSheetDialog.FormatCategory
|
||||||
|
import com.deniscerri.ytdl.ui.downloadcard.FormatSelectionBottomSheetDialog.FormatSorting
|
||||||
|
import com.deniscerri.ytdl.ui.downloadcard.FormatTuple
|
||||||
|
import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||||
|
import com.deniscerri.ytdl.util.FormatUtil
|
||||||
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.text.Normalizer.Form
|
||||||
|
|
||||||
|
class FormatViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
|
private val downloadRepository: DownloadRepository
|
||||||
|
val selectedItems = MutableStateFlow(listOf<DownloadItem>())
|
||||||
|
val selectedItemsSharedFlow = MutableSharedFlow<List<DownloadItem>>(replay = 1)
|
||||||
|
var formats : Flow<List<FormatRecyclerView>>
|
||||||
|
var showFilterBtn = MutableStateFlow(false)
|
||||||
|
var showRefreshBtn = MutableStateFlow(false)
|
||||||
|
private var canUpdate = true
|
||||||
|
var canMultiSelectAudio = MutableStateFlow(false)
|
||||||
|
var isMissingFormats = MutableStateFlow(false)
|
||||||
|
|
||||||
|
private var formatUtil = FormatUtil(application)
|
||||||
|
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
|
|
||||||
|
val genericAudioFormats = formatUtil.getGenericAudioFormats(application.resources)
|
||||||
|
val genericVideoFormats = formatUtil.getGenericVideoFormats(application.resources)
|
||||||
|
|
||||||
|
var sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
|
||||||
|
var filterBy = MutableStateFlow(FormatCategory.valueOf(sharedPreferences.getString("format_filter", "ALL")!!))
|
||||||
|
|
||||||
|
init {
|
||||||
|
downloadRepository = DownloadRepository(DBManager.getInstance(application).downloadDao)
|
||||||
|
formats = combine(listOf(selectedItemsSharedFlow, filterBy)) { f ->
|
||||||
|
val items = selectedItems.value
|
||||||
|
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
mutableListOf()
|
||||||
|
}else {
|
||||||
|
val formats = if (items.size == 1) {
|
||||||
|
items.first().allFormats
|
||||||
|
}else {
|
||||||
|
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
||||||
|
|
||||||
|
flatFormatCollection.groupingBy { it.format_id }.eachCount()
|
||||||
|
.filter { it.value == items.size }
|
||||||
|
.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
|
||||||
|
.map { it.value }.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
isMissingFormats.apply {
|
||||||
|
val vl = formats.isEmpty()
|
||||||
|
value = vl
|
||||||
|
emit(vl)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chosenFormats: List<Format>
|
||||||
|
|
||||||
|
if (items.size > 1) {
|
||||||
|
if (!isMissingFormats.value) {
|
||||||
|
chosenFormats = formats.mapTo(mutableListOf()) {it.copy()}
|
||||||
|
chosenFormats = when(items.first().type){
|
||||||
|
Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
|
else -> chosenFormats
|
||||||
|
}
|
||||||
|
chosenFormats.forEach {
|
||||||
|
it.filesize = items.map { itm -> itm.allFormats }.flatten().filter { f -> f.format_id == it.format_id }.sumOf { itt -> itt.filesize }
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
chosenFormats = formats
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
chosenFormats = formats
|
||||||
|
if(!isMissingFormats.value){
|
||||||
|
if(items.first().type == Type.audio){
|
||||||
|
chosenFormats = chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showFilterBtn.apply {
|
||||||
|
val vl = chosenFormats.isNotEmpty() || items.all { it.url.isYoutubeURL() }
|
||||||
|
value = vl
|
||||||
|
emit(vl)
|
||||||
|
}
|
||||||
|
showRefreshBtn.apply {
|
||||||
|
val vl = (isMissingFormats.value || items.isEmpty() || items.first().url.isEmpty()) && canUpdate
|
||||||
|
value = vl
|
||||||
|
emit(vl)
|
||||||
|
}
|
||||||
|
|
||||||
|
//sort
|
||||||
|
var finalFormats: List<Format> = when(sortBy){
|
||||||
|
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
||||||
|
FormatSorting.id -> chosenFormats.sortedBy { it.format_id }
|
||||||
|
FormatSorting.codec -> {
|
||||||
|
val codecOrder = application.getStringArray(R.array.video_codec_values).toMutableList()
|
||||||
|
codecOrder.removeAt(0)
|
||||||
|
chosenFormats.groupBy { format -> codecOrder.indexOfFirst { format.vcodec.matches("^(${it})(.+)?$".toRegex()) } }
|
||||||
|
|
||||||
|
.flatMap {
|
||||||
|
it.value.sortedByDescending { l -> l.filesize }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormatSorting.filesize -> chosenFormats
|
||||||
|
}
|
||||||
|
|
||||||
|
//filter category
|
||||||
|
when(filterBy.value){
|
||||||
|
FormatCategory.ALL -> {}
|
||||||
|
FormatCategory.SUGGESTED -> {
|
||||||
|
finalFormats = if (items.first().type == Type.audio){
|
||||||
|
formatUtil.sortAudioFormats(finalFormats)
|
||||||
|
}else{
|
||||||
|
val audioFormats = finalFormats.filter { it.vcodec.isBlank() || it.vcodec == "none" }
|
||||||
|
val videoFormats = finalFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }
|
||||||
|
|
||||||
|
formatUtil.sortVideoFormats(videoFormats) + formatUtil.sortAudioFormats(audioFormats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormatCategory.SMALLEST -> {
|
||||||
|
val tmpFormats = finalFormats
|
||||||
|
.asSequence()
|
||||||
|
.map { it.copy() }
|
||||||
|
.filter { it.filesize > 0 }
|
||||||
|
.onEach {
|
||||||
|
var tmp = it.format_note.lowercase()
|
||||||
|
//remove brackets
|
||||||
|
tmp = tmp.replace(" (.*)".toRegex(), "")
|
||||||
|
|
||||||
|
//formats that end like 1080P
|
||||||
|
if (tmp.endsWith("060")){
|
||||||
|
tmp = tmp.removeSuffix("60")
|
||||||
|
}
|
||||||
|
tmp = tmp.removeSuffix("p")
|
||||||
|
//formats that are written like 1920X1080
|
||||||
|
val split = tmp.split("x")
|
||||||
|
if (split.size > 1){
|
||||||
|
tmp = split[1]
|
||||||
|
}
|
||||||
|
it.format_note = tmp
|
||||||
|
}
|
||||||
|
.groupBy { it.format_note }
|
||||||
|
.map { it.value.minBy { it2 -> it2.filesize } }.toList()
|
||||||
|
finalFormats = finalFormats.filter { tmpFormats.map { it2 -> it2.format_id }.contains(it.format_id) }
|
||||||
|
}
|
||||||
|
FormatCategory.GENERIC -> {
|
||||||
|
finalFormats = listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canMultiSelectAudio.apply {
|
||||||
|
val vl = items.first().type == Type.video && finalFormats.find { it.vcodec.isBlank() || it.vcodec == "none" } != null
|
||||||
|
value = vl
|
||||||
|
emit(vl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalFormats.isEmpty()) {
|
||||||
|
finalFormats = if (items.first().type == Type.audio){
|
||||||
|
genericAudioFormats
|
||||||
|
}else{
|
||||||
|
genericVideoFormats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val results = mutableListOf<FormatRecyclerView>()
|
||||||
|
if (canMultiSelectAudio.value) {
|
||||||
|
results.add(FormatRecyclerView(label = application.getString(R.string.video)))
|
||||||
|
results.addAll(finalFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.map { FormatRecyclerView(null, it) })
|
||||||
|
results.add(FormatRecyclerView(label = application.getString(R.string.audio)))
|
||||||
|
results.addAll(finalFormats.filter { it.vcodec.isBlank() || it.vcodec == "none" }.map { FormatRecyclerView(null, it) })
|
||||||
|
}else{
|
||||||
|
results.addAll(finalFormats.map { FormatRecyclerView(null, it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItems(list: List<DownloadItem>, updateFormats: Boolean? = null) = viewModelScope.launch {
|
||||||
|
selectedItems.apply {
|
||||||
|
value = list
|
||||||
|
emit(list)
|
||||||
|
}
|
||||||
|
selectedItemsSharedFlow.emit(list)
|
||||||
|
canUpdate = updateFormats ?: canUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setItem(item: DownloadItem, updateFormats: Boolean? = null) = viewModelScope.launch {
|
||||||
|
selectedItems.apply {
|
||||||
|
value = listOf(item)
|
||||||
|
emit(listOf(item))
|
||||||
|
}
|
||||||
|
selectedItemsSharedFlow.emit(listOf(item))
|
||||||
|
canUpdate = updateFormats ?: canUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getFormatsForItemsBasedOnFormat(item: Format, audioFormats: List<Format>? = null) : MutableList<MultipleItemFormatTuple> {
|
||||||
|
val formatsToReturn = mutableListOf<MultipleItemFormatTuple>()
|
||||||
|
val f = if (genericAudioFormats.contains(item) || genericVideoFormats.contains(item)) item else null
|
||||||
|
|
||||||
|
selectedItems.value.forEach {
|
||||||
|
formatsToReturn.add(
|
||||||
|
MultipleItemFormatTuple(
|
||||||
|
it.url,
|
||||||
|
FormatTuple(
|
||||||
|
f ?: it.allFormats.firstOrNull { af -> af.format_id == item.format_id },
|
||||||
|
audioFormats?.map { sa ->
|
||||||
|
it.allFormats.first { a -> a.format_id == sa.format_id }
|
||||||
|
}?.ifEmpty { null }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatsToReturn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -124,7 +124,11 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun getHomeRecommendations() = viewModelScope.launch(Dispatchers.IO){
|
fun getHomeRecommendations() = viewModelScope.launch(Dispatchers.IO){
|
||||||
if (!sharedPreferences.getString("youtube_home_recommendations", "").isNullOrBlank()){
|
val homeRecommendations = sharedPreferences.getString("home_recommendations", "")
|
||||||
|
val customHomeRecommendations = sharedPreferences.getString("custom_home_recommendation_url", "")
|
||||||
|
val emptyCustomRecommendations = customHomeRecommendations.isNullOrBlank() && homeRecommendations == "custom"
|
||||||
|
|
||||||
|
if (!homeRecommendations.isNullOrBlank() && !emptyCustomRecommendations){
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
uiState.update {it.copy(processing = true)}
|
uiState.update {it.copy(processing = true)}
|
||||||
repository.getHomeRecommendations()
|
repository.getHomeRecommendations()
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
} else {
|
} else {
|
||||||
item.format.acodec.uppercase()
|
item.format.acodec.uppercase()
|
||||||
}
|
}
|
||||||
if (codecText == "" || codecText == "none"){
|
if (codecText == "" || codecText == "none" || codecText == "DEFAULT"){
|
||||||
codec.visibility = View.GONE
|
codec.visibility = View.GONE
|
||||||
}else{
|
}else{
|
||||||
codec.visibility = View.VISIBLE
|
codec.visibility = View.VISIBLE
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.app.Activity
|
||||||
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.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
@ -12,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.models.CookieItem
|
import com.deniscerri.ytdl.database.models.CookieItem
|
||||||
import com.deniscerri.ytdl.database.models.Format
|
import com.deniscerri.ytdl.database.models.Format
|
||||||
|
import com.deniscerri.ytdl.database.models.FormatRecyclerView
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.databinding.FormatItemBinding
|
import com.deniscerri.ytdl.databinding.FormatItemBinding
|
||||||
import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter.ViewHolder
|
import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter.ViewHolder
|
||||||
|
|
@ -19,39 +21,54 @@ import com.deniscerri.ytdl.util.Extensions.popup
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
||||||
class FormatAdapter(onItemClickListener: OnItemClickListener, activity: Activity, private val downloadType: DownloadViewModel.Type) : ListAdapter<Format?, FormatAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
class FormatAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<FormatRecyclerView?, FormatAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||||
DIFF_CALLBACK
|
DIFF_CALLBACK
|
||||||
).build()) {
|
).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
private var selectedVideoFormat: Format?
|
var selectedVideoFormat: Format? = null
|
||||||
private val selectedAudioFormats: MutableList<Format>
|
val selectedAudioFormats: MutableList<Format> = mutableListOf()
|
||||||
private val usingGrid: Boolean
|
private var canMultiSelectAudio: Boolean = false
|
||||||
|
private var formats: MutableList<FormatRecyclerView?> = mutableListOf()
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
this.selectedVideoFormat = null
|
|
||||||
this.usingGrid = false
|
|
||||||
this.selectedAudioFormats = mutableListOf()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
val item: MaterialCardView
|
val item: MaterialCardView? = itemView.findViewById(R.id.format_card_constraintLayout)
|
||||||
|
val label: Button? = itemView.findViewById(R.id.title)
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
override fun submitList(list: MutableList<FormatRecyclerView?>?) {
|
||||||
item = itemView.findViewById(R.id.format_card_constraintLayout)
|
if (list != null) {
|
||||||
|
formats = list
|
||||||
|
}
|
||||||
|
super.submitList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCanMultiSelectAudio(it: Boolean) {
|
||||||
|
canMultiSelectAudio = it
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
try {
|
||||||
|
val isLabel = formats[position]!!.label != null
|
||||||
|
return if (isLabel) 0 else 1
|
||||||
|
}catch (err: Exception) {
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return if (usingGrid){
|
return if (viewType == 0){
|
||||||
val cardView = LayoutInflater.from(parent.context)
|
val button = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.format_item_grid, parent, false)
|
.inflate(R.layout.format_type_label, parent, false)
|
||||||
|
|
||||||
ViewHolder(
|
ViewHolder(
|
||||||
cardView,
|
button,
|
||||||
onItemClickListener
|
onItemClickListener
|
||||||
)
|
)
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -67,54 +84,65 @@ class FormatAdapter(onItemClickListener: OnItemClickListener, activity: Activity
|
||||||
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position) ?: return
|
val itm = getItem(position) ?: return
|
||||||
val card = holder.item
|
val viewType = getItemViewType(position)
|
||||||
card.popup()
|
if (viewType == 0) {
|
||||||
|
val button = holder.label
|
||||||
|
button?.text = itm.label
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val item = itm.format!!
|
||||||
|
val card = holder.item!!
|
||||||
|
//card.popup()
|
||||||
UiUtil.populateFormatCard(activity, card, item)
|
UiUtil.populateFormatCard(activity, card, item)
|
||||||
|
card.isChecked = selectedVideoFormat == item || selectedAudioFormats.any { it == item }
|
||||||
|
|
||||||
card.setOnClickListener {
|
card.setOnClickListener {
|
||||||
when(downloadType) {
|
if (!canMultiSelectAudio) {
|
||||||
DownloadViewModel.Type.audio -> {
|
onItemClickListener.onItemSelect(item, null)
|
||||||
onItemClickListener.onItemClick(item)
|
}else {
|
||||||
}
|
if (item.isVideo()) {
|
||||||
DownloadViewModel.Type.video -> {
|
if (card.isChecked) {
|
||||||
if(item.isAudio()) {
|
onItemClickListener.onItemSelect(item, selectedAudioFormats)
|
||||||
if (card.isChecked) {
|
|
||||||
selectedAudioFormats.remove(item)
|
|
||||||
}else {
|
|
||||||
selectedAudioFormats.add(item)
|
|
||||||
}
|
|
||||||
}else {
|
}else {
|
||||||
if (card.isChecked) {
|
selectedVideoFormat = item
|
||||||
onItemClickListener.onItemClick(item)
|
notifyDataSetChanged()
|
||||||
}else {
|
|
||||||
selectedVideoFormat = item
|
|
||||||
card.isChecked = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}else {
|
||||||
|
if (card.isChecked) {
|
||||||
|
selectedAudioFormats.remove(item)
|
||||||
|
}else {
|
||||||
|
selectedAudioFormats.add(item)
|
||||||
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
card.setOnLongClickListener {
|
||||||
|
UiUtil.showFormatDetails(item, activity)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Format.isAudio() : Boolean {
|
private fun Format.isVideo() : Boolean {
|
||||||
return this.vcodec.isNotBlank() && this.vcodec != "none"
|
return this.vcodec.isNotBlank() && this.vcodec != "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnItemClickListener {
|
interface OnItemClickListener {
|
||||||
fun onItemClick(item: Format)
|
fun onItemSelect(item: Format, audioFormats: List<Format>?)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFF_CALLBACK: DiffUtil.ItemCallback<Format> = object : DiffUtil.ItemCallback<Format>() {
|
private val DIFF_CALLBACK: DiffUtil.ItemCallback<FormatRecyclerView> = object : DiffUtil.ItemCallback<FormatRecyclerView>() {
|
||||||
override fun areItemsTheSame(oldItem: Format, newItem: Format): Boolean {
|
override fun areItemsTheSame(oldItem: FormatRecyclerView, newItem: FormatRecyclerView): Boolean {
|
||||||
return oldItem.format_id == newItem.format_id
|
return oldItem.label == newItem.label && oldItem.format?.format_id == newItem.format?.format_id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: Format, newItem: Format): Boolean {
|
override fun areContentsTheSame(oldItem: FormatRecyclerView, newItem: FormatRecyclerView): Boolean {
|
||||||
return oldItem.format_id == newItem.format_id
|
return oldItem.label == newItem.label && oldItem.format?.format_id == newItem.format?.format_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import com.deniscerri.ytdl.database.models.Format
|
||||||
import com.deniscerri.ytdl.database.models.ResultItem
|
import com.deniscerri.ytdl.database.models.ResultItem
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.FormatViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdl.util.Extensions.applyFilenameTemplateForCuts
|
import com.deniscerri.ytdl.util.Extensions.applyFilenameTemplateForCuts
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
|
|
@ -54,6 +55,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
private lateinit var resultViewModel : ResultViewModel
|
private lateinit var resultViewModel : ResultViewModel
|
||||||
|
private lateinit var formatViewModel : FormatViewModel
|
||||||
private lateinit var saveDir : TextInputLayout
|
private lateinit var saveDir : TextInputLayout
|
||||||
private lateinit var freeSpace : TextView
|
private lateinit var freeSpace : TextView
|
||||||
private lateinit var genericAudioFormats: MutableList<Format>
|
private lateinit var genericAudioFormats: MutableList<Format>
|
||||||
|
|
@ -73,6 +75,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
|
formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java]
|
||||||
genericAudioFormats = FormatUtil(requireContext()).getGenericAudioFormats(requireContext().resources)
|
genericAudioFormats = FormatUtil(requireContext()).getGenericAudioFormats(requireContext().resources)
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
shownFields = preferences.getStringSet("modify_download_card", requireContext().getStringArray(R.array.modify_download_card_values).toSet())!!.toList()
|
shownFields = preferences.getStringSet("modify_download_card", requireContext().getStringArray(R.array.modify_download_card_values).toSet())!!.toList()
|
||||||
|
|
@ -252,7 +255,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
}
|
}
|
||||||
formatCard.setOnClickListener{
|
formatCard.setOnClickListener{
|
||||||
if (parentFragmentManager.findFragmentByTag("formatSheet") == null){
|
if (parentFragmentManager.findFragmentByTag("formatSheet") == null){
|
||||||
val bottomSheet = FormatSelectionBottomSheetDialog(listOf(downloadItem), listener, canUpdate = !nonSpecific)
|
formatViewModel.setItem(downloadItem, !nonSpecific)
|
||||||
|
val bottomSheet = FormatSelectionBottomSheetDialog(listener)
|
||||||
bottomSheet.show(parentFragmentManager, "formatSheet")
|
bottomSheet.show(parentFragmentManager, "formatSheet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import com.deniscerri.ytdl.database.models.DownloadItemConfigureMultiple
|
||||||
import com.deniscerri.ytdl.database.models.Format
|
import com.deniscerri.ytdl.database.models.Format
|
||||||
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.FormatViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdl.receiver.ShareActivity
|
import com.deniscerri.ytdl.receiver.ShareActivity
|
||||||
|
|
@ -70,6 +71,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
private lateinit var historyViewModel: HistoryViewModel
|
private lateinit var historyViewModel: HistoryViewModel
|
||||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
|
private lateinit var formatViewModel: FormatViewModel
|
||||||
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
|
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
|
||||||
private lateinit var recyclerView: RecyclerView
|
private lateinit var recyclerView: RecyclerView
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
|
|
@ -97,6 +99,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
|
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||||
|
formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java]
|
||||||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
|
|
@ -506,7 +509,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
val items = withContext(Dispatchers.IO){
|
val items = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getProcessingDownloads()
|
downloadViewModel.getProcessingDownloads()
|
||||||
}
|
}
|
||||||
val bottomSheet = FormatSelectionBottomSheetDialog(items, _multipleFormatsListener = formatListener)
|
formatViewModel.setItems(items)
|
||||||
|
val bottomSheet = FormatSelectionBottomSheetDialog( _multipleFormatsListener = formatListener)
|
||||||
bottomSheet.show(parentFragmentManager, "formatSheet")
|
bottomSheet.show(parentFragmentManager, "formatSheet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import com.deniscerri.ytdl.database.models.Format
|
||||||
import com.deniscerri.ytdl.database.models.ResultItem
|
import com.deniscerri.ytdl.database.models.ResultItem
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.FormatViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdl.util.Extensions.applyFilenameTemplateForCuts
|
import com.deniscerri.ytdl.util.Extensions.applyFilenameTemplateForCuts
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
|
|
@ -53,6 +54,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
private var fragmentView: View? = null
|
private var fragmentView: View? = null
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
|
private lateinit var formatViewModel : FormatViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
private lateinit var preferences: SharedPreferences
|
private lateinit var preferences: SharedPreferences
|
||||||
private lateinit var shownFields: List<String>
|
private lateinit var shownFields: List<String>
|
||||||
|
|
@ -77,6 +79,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
|
formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java]
|
||||||
val formatUtil = FormatUtil(requireContext())
|
val formatUtil = FormatUtil(requireContext())
|
||||||
genericVideoFormats = formatUtil.getGenericVideoFormats(requireContext().resources)
|
genericVideoFormats = formatUtil.getGenericVideoFormats(requireContext().resources)
|
||||||
genericAudioFormats = formatUtil.getGenericAudioFormats(requireContext().resources)
|
genericAudioFormats = formatUtil.getGenericAudioFormats(requireContext().resources)
|
||||||
|
|
@ -290,7 +293,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
}
|
}
|
||||||
formatCard.setOnClickListener{
|
formatCard.setOnClickListener{
|
||||||
if (parentFragmentManager.findFragmentByTag("formatSheet") == null){
|
if (parentFragmentManager.findFragmentByTag("formatSheet") == null){
|
||||||
val bottomSheet = FormatSelectionBottomSheetDialog(listOf(downloadItem), listener, canUpdate = !nonSpecific)
|
formatViewModel.setItem(downloadItem, !nonSpecific)
|
||||||
|
val bottomSheet = FormatSelectionBottomSheetDialog(listener)
|
||||||
bottomSheet.show(parentFragmentManager, "formatSheet")
|
bottomSheet.show(parentFragmentManager, "formatSheet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,19 @@ import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdl.database.models.Format
|
import com.deniscerri.ytdl.database.models.Format
|
||||||
|
import com.deniscerri.ytdl.database.models.FormatRecyclerView
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.FormatViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdl.ui.adapter.FormatAdapter
|
import com.deniscerri.ytdl.ui.adapter.FormatAdapter
|
||||||
|
import com.deniscerri.ytdl.ui.adapter.HomeAdapter
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||||
import com.deniscerri.ytdl.util.FormatUtil
|
import com.deniscerri.ytdl.util.FormatUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
|
|
@ -39,17 +45,27 @@ import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.w3c.dom.Text
|
||||||
|
|
||||||
|
|
||||||
class FormatSelectionBottomSheetDialog(
|
class FormatSelectionBottomSheetDialog(
|
||||||
private val _items: List<DownloadItem?>? = null,
|
|
||||||
private val _listener: OnFormatClickListener? = null,
|
private val _listener: OnFormatClickListener? = null,
|
||||||
private val canUpdate: Boolean = true,
|
|
||||||
private val _multipleFormatsListener: OnMultipleFormatClickListener? = null
|
private val _multipleFormatsListener: OnMultipleFormatClickListener? = null
|
||||||
) : BottomSheetDialogFragment() {
|
) : BottomSheetDialogFragment(), FormatAdapter.OnItemClickListener {
|
||||||
|
|
||||||
|
private lateinit var formatViewModel: FormatViewModel
|
||||||
|
private lateinit var recyclerView: RecyclerView
|
||||||
|
private lateinit var adapter: FormatAdapter
|
||||||
|
private lateinit var genericAudioFormats : List<Format>
|
||||||
|
private lateinit var genericVideoFormats : List<Format>
|
||||||
|
private var formats: List<FormatRecyclerView> = listOf()
|
||||||
|
|
||||||
|
private var canMultiSelectAudio: Boolean = false
|
||||||
|
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
private lateinit var formatUtil: FormatUtil
|
private lateinit var formatUtil: FormatUtil
|
||||||
|
|
@ -58,36 +74,17 @@ class FormatSelectionBottomSheetDialog(
|
||||||
private lateinit var downloadViewModel: DownloadViewModel
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private lateinit var videoFormatList : GridLayout
|
|
||||||
private lateinit var audioFormatList : GridLayout
|
|
||||||
private lateinit var okBtn : Button
|
private lateinit var okBtn : Button
|
||||||
private lateinit var refreshBtn: Button
|
private lateinit var refreshBtn: Button
|
||||||
private lateinit var videoTitle : TextView
|
|
||||||
private lateinit var audioTitle : TextView
|
|
||||||
|
|
||||||
private lateinit var chosenFormats: List<Format>
|
|
||||||
private var selectedVideo : Format? = null
|
|
||||||
private lateinit var selectedAudios : MutableList<Format>
|
|
||||||
|
|
||||||
private lateinit var sortBy : FormatSorting
|
|
||||||
private lateinit var filterBy : FormatCategory
|
|
||||||
private lateinit var filterBtn : Button
|
private lateinit var filterBtn : Button
|
||||||
|
|
||||||
private var updateFormatsJob: Job? = null
|
private var updateFormatsJob: Job? = null
|
||||||
private var isMissingFormats: Boolean = false
|
|
||||||
|
|
||||||
private lateinit var items: MutableList<DownloadItem?>
|
|
||||||
private lateinit var formats: MutableList<Format>
|
|
||||||
private lateinit var listener: OnFormatClickListener
|
private lateinit var listener: OnFormatClickListener
|
||||||
private lateinit var multipleFormatsListener: OnMultipleFormatClickListener
|
private lateinit var multipleFormatsListener: OnMultipleFormatClickListener
|
||||||
|
|
||||||
private var currentFormatSource : String? = null
|
private var currentFormatSource : String? = null
|
||||||
|
|
||||||
private lateinit var genericAudioFormats : List<Format>
|
|
||||||
private lateinit var genericVideoFormats : List<Format>
|
|
||||||
|
|
||||||
private var usingGrid: Boolean = false
|
|
||||||
|
|
||||||
enum class FormatSorting {
|
enum class FormatSorting {
|
||||||
filesize, container, codec, id
|
filesize, container, codec, id
|
||||||
}
|
}
|
||||||
|
|
@ -98,9 +95,8 @@ class FormatSelectionBottomSheetDialog(
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
formatViewModel = ViewModelProvider(requireActivity())[FormatViewModel::class.java]
|
||||||
formatUtil = FormatUtil(requireContext())
|
formatUtil = FormatUtil(requireContext())
|
||||||
chosenFormats = listOf()
|
|
||||||
selectedAudios = mutableListOf()
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
|
|
@ -113,20 +109,66 @@ class FormatSelectionBottomSheetDialog(
|
||||||
view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null)
|
view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null)
|
||||||
dialog.setContentView(view)
|
dialog.setContentView(view)
|
||||||
|
|
||||||
if (_items == null){
|
dialog.setOnShowListener {
|
||||||
this.dismiss()
|
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||||
return
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
behavior.peekHeight = displayMetrics.heightPixels / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
items = _items.toMutableList()
|
view.findViewById<TextView>(R.id.bottom_sheet_title).setOnClickListener {
|
||||||
if (items.size == 1) {
|
recyclerView.scrollTo(0,0)
|
||||||
formats = items.first()!!.allFormats
|
}
|
||||||
}else{
|
|
||||||
val flatFormatCollection = items.map { it!!.allFormats }.flatten()
|
genericAudioFormats = formatUtil.getGenericAudioFormats(resources)
|
||||||
formats = flatFormatCollection.groupingBy { it.format_id }.eachCount()
|
genericVideoFormats = formatUtil.getGenericVideoFormats(resources)
|
||||||
.filter { it.value == items.size }
|
|
||||||
.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
|
adapter =
|
||||||
.map { it.value }.toMutableList()
|
FormatAdapter(
|
||||||
|
this,
|
||||||
|
requireActivity()
|
||||||
|
)
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView)
|
||||||
|
recyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
refreshBtn = view.findViewById(R.id.format_refresh)
|
||||||
|
okBtn = view.findViewById(R.id.format_ok)
|
||||||
|
filterBtn = view.findViewById(R.id.format_filter)
|
||||||
|
val shimmers = view.findViewById<ShimmerFrameLayout>(R.id.format_list_shimmer)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
formatViewModel.formats.collectLatest {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
this@FormatSelectionBottomSheetDialog.dismiss()
|
||||||
|
}
|
||||||
|
adapter.setCanMultiSelectAudio(formatViewModel.canMultiSelectAudio.value)
|
||||||
|
formats = it
|
||||||
|
adapter.submitList(it.toMutableList())
|
||||||
|
shimmers.visibility = View.GONE
|
||||||
|
shimmers.stopShimmer()
|
||||||
|
recyclerView.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
formatViewModel.showFilterBtn.collectLatest {
|
||||||
|
filterBtn.isVisible = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
formatViewModel.showRefreshBtn.collectLatest {
|
||||||
|
refreshBtn.isVisible = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
formatViewModel.canMultiSelectAudio.collectLatest {
|
||||||
|
okBtn.isVisible = it
|
||||||
|
canMultiSelectAudio = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_listener?.apply {
|
_listener?.apply {
|
||||||
|
|
@ -137,211 +179,119 @@ class FormatSelectionBottomSheetDialog(
|
||||||
multipleFormatsListener = this
|
multipleFormatsListener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
genericAudioFormats = formatUtil.getGenericAudioFormats(requireContext().resources)
|
|
||||||
genericVideoFormats = formatUtil.getGenericVideoFormats(requireContext().resources)
|
|
||||||
|
|
||||||
sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
|
|
||||||
filterBy = FormatCategory.valueOf(sharedPreferences.getString("format_filter", "ALL")!!)
|
|
||||||
filterBtn = view.findViewById(R.id.format_filter)
|
|
||||||
usingGrid = sharedPreferences.getBoolean("format_list_grid", false)
|
|
||||||
|
|
||||||
dialog.setOnShowListener {
|
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
|
||||||
val displayMetrics = DisplayMetrics()
|
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
|
||||||
behavior.peekHeight = displayMetrics.heightPixels / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
val formatListLinearLayout = view.findViewById<LinearLayout>(R.id.format_list_linear_layout)
|
|
||||||
val shimmers = view.findViewById<ShimmerFrameLayout>(R.id.format_list_shimmer)
|
|
||||||
|
|
||||||
videoFormatList = view.findViewById(R.id.video_linear_layout)
|
|
||||||
audioFormatList = view.findViewById(R.id.audio_linear_layout)
|
|
||||||
videoTitle = view.findViewById(R.id.video_title)
|
|
||||||
audioTitle = view.findViewById(R.id.audio_title)
|
|
||||||
okBtn = view.findViewById(R.id.format_ok)
|
|
||||||
|
|
||||||
shimmers.visibility = View.GONE
|
|
||||||
isMissingFormats = formats.isEmpty() && items.any { it!!.allFormats.isEmpty() }
|
|
||||||
|
|
||||||
if (items.size > 1){
|
|
||||||
if (!isMissingFormats){
|
|
||||||
chosenFormats = formats.mapTo(mutableListOf()) {it.copy()}
|
|
||||||
chosenFormats = when(items.first()?.type){
|
|
||||||
Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
else -> chosenFormats
|
|
||||||
}
|
|
||||||
chosenFormats.forEach {
|
|
||||||
it.filesize = items.map { itm -> itm!!.allFormats }.flatten().filter { f -> f.format_id == it.format_id }.sumOf { itt -> itt.filesize }
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
chosenFormats = formats
|
|
||||||
}
|
|
||||||
addFormatsToView()
|
|
||||||
}else{
|
|
||||||
chosenFormats = formats
|
|
||||||
if(!isMissingFormats){
|
|
||||||
if(items.first()?.type == Type.audio){
|
|
||||||
chosenFormats = chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addFormatsToView()
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshBtn = view.findViewById(R.id.format_refresh)
|
|
||||||
filterBtn.isVisible = chosenFormats.isNotEmpty() || items.all { it!!.url.isYoutubeURL() }
|
|
||||||
if (!isMissingFormats || items.isEmpty() || items.first()?.url?.isEmpty() == true || !canUpdate) {
|
|
||||||
refreshBtn.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
refreshBtn.setOnClickListener {
|
refreshBtn.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val distinctItems = items.distinctBy { it!!.url }
|
val items = formatViewModel.selectedItems.value.toMutableList()
|
||||||
|
val distinctItems = items.distinctBy { it.url }
|
||||||
|
|
||||||
val itemsThatHaveFormats = distinctItems.filter { it!!.allFormats.isNotEmpty() }
|
val itemsThatHaveFormats = distinctItems.filter { it.allFormats.isNotEmpty() }
|
||||||
val itemsWithMissingFormats = distinctItems.filter { it!!.allFormats.isEmpty() }.ifEmpty { distinctItems }
|
val itemsWithMissingFormats = distinctItems.filter { it.allFormats.isEmpty() }.ifEmpty { distinctItems }
|
||||||
|
|
||||||
if (itemsWithMissingFormats.size > 10){
|
if (itemsWithMissingFormats.size > 10){
|
||||||
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
||||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||||
_multipleFormatsListener!!.onContinueOnBackground()
|
_multipleFormatsListener!!.onContinueOnBackground()
|
||||||
this@FormatSelectionBottomSheetDialog.dismiss()
|
this@FormatSelectionBottomSheetDialog.dismiss()
|
||||||
}
|
}
|
||||||
continueInBackgroundSnackBar.show()
|
continueInBackgroundSnackBar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
chosenFormats = emptyList()
|
refreshBtn.isEnabled = false
|
||||||
refreshBtn.isEnabled = false
|
refreshBtn.isVisible = true
|
||||||
refreshBtn.isVisible = true
|
okBtn.isVisible = false
|
||||||
okBtn.isVisible = false
|
okBtn.isEnabled = false
|
||||||
okBtn.isEnabled = false
|
filterBtn.isEnabled = false
|
||||||
filterBtn.isEnabled = false
|
recyclerView.isVisible = false
|
||||||
formatListLinearLayout.visibility = View.GONE
|
shimmers.isVisible = true
|
||||||
shimmers.visibility = View.VISIBLE
|
shimmers.startShimmer()
|
||||||
shimmers.startShimmer()
|
|
||||||
updateFormatsJob = launch(Dispatchers.IO) {
|
|
||||||
try{
|
|
||||||
//simple download
|
|
||||||
if (items.size == 1) {
|
|
||||||
kotlin.runCatching {
|
|
||||||
val res = resultViewModel.getFormats(items.first()!!.url, currentFormatSource)
|
|
||||||
if (!isActive) return@launch
|
|
||||||
res.filter { it.format_note != "storyboard" }
|
|
||||||
chosenFormats = if (items.first()?.type == Type.audio) {
|
|
||||||
res.filter { it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
} else {
|
|
||||||
res
|
|
||||||
}
|
|
||||||
if (chosenFormats.isEmpty()) throw Exception()
|
|
||||||
|
|
||||||
formats.clear()
|
updateFormatsJob = launch(Dispatchers.IO) {
|
||||||
formats.addAll(res)
|
try{
|
||||||
|
//simple download
|
||||||
|
if (items.size == 1) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
val res = resultViewModel.getFormats(items.first().url, currentFormatSource)
|
||||||
|
if (!isActive) return@launch
|
||||||
|
res.filter { it.format_note != "storyboard" }
|
||||||
|
val chosenFormats = if (items.first().type == Type.audio) {
|
||||||
|
res.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
|
} else {
|
||||||
|
res
|
||||||
|
}
|
||||||
|
if (chosenFormats.isEmpty()) throw Exception()
|
||||||
|
|
||||||
|
items.first().allFormats.clear()
|
||||||
|
items.first().allFormats.addAll(chosenFormats)
|
||||||
|
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
listener.onFormatsUpdated(res)
|
formatViewModel.setItem(items.first())
|
||||||
}
|
listener.onFormatsUpdated(res)
|
||||||
}.onFailure { err ->
|
}
|
||||||
withContext(Dispatchers.Main){
|
}.onFailure { err ->
|
||||||
UiUtil.handleNoResults(requireActivity(), err.message.toString(), null, false, continued = {}, closed = {}, cookieFetch = {})
|
withContext(Dispatchers.Main){
|
||||||
}
|
UiUtil.handleNoResults(requireActivity(), err.message.toString(), null, false, continued = {}, closed = {}, cookieFetch = {})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//list format filtering
|
//list format filtering
|
||||||
}else{
|
}else{
|
||||||
formats.clear()
|
var progressInt = 0
|
||||||
var progressInt = 0
|
val formatCollection = itemsThatHaveFormats.map { it.allFormats }.toMutableList()
|
||||||
val formatCollection = itemsThatHaveFormats.map { it!!.allFormats }.toMutableList()
|
|
||||||
|
|
||||||
var progress = "0/${itemsWithMissingFormats.size}"
|
var progress = "0/${itemsWithMissingFormats.size}"
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
refreshBtn.text = progress
|
refreshBtn.text = progress
|
||||||
}
|
}
|
||||||
|
|
||||||
val res = resultViewModel.getFormatsMultiple(itemsWithMissingFormats.map { it!!.url }, currentFormatSource) {
|
val res = resultViewModel.getFormatsMultiple(itemsWithMissingFormats.map { it.url }, currentFormatSource) {
|
||||||
if (!isActive) return@getFormatsMultiple
|
if (!isActive) return@getFormatsMultiple
|
||||||
|
|
||||||
if (it.unavailable) {
|
if (it.unavailable) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
multipleFormatsListener.onItemUnavailable(it.url)
|
multipleFormatsListener.onItemUnavailable(it.url)
|
||||||
items.removeAt(items.indexOfFirst { item -> item!!.url == it.url })
|
items.removeAt(items.indexOfFirst { item -> item.url == it.url })
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
Snackbar.make(view, it.unavailableMessage, Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(view, it.unavailableMessage, Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
multipleFormatsListener.onFormatUpdated(it.url, it.formats)
|
multipleFormatsListener.onFormatUpdated(it.url, it.formats)
|
||||||
items.filter { item -> item!!.url == it.url }.forEach { d ->
|
items.filter { item -> item.url == it.url }.forEach { d ->
|
||||||
d?.allFormats?.clear()
|
d.allFormats.clear()
|
||||||
d?.allFormats?.addAll(it.formats)
|
d.allFormats.addAll(it.formats)
|
||||||
}
|
}
|
||||||
progressInt++
|
progressInt++
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
progress = "${progressInt}/${itemsWithMissingFormats.size}"
|
progress = "${progressInt}/${itemsWithMissingFormats.size}"
|
||||||
refreshBtn.text = progress
|
refreshBtn.text = progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
formatViewModel.setItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
formatCollection.addAll(res)
|
withContext(Dispatchers.Main){
|
||||||
|
filterBtn.isEnabled = true
|
||||||
if (!isActive) return@launch
|
okBtn.isEnabled = true
|
||||||
|
}
|
||||||
val flatFormatCollection = formatCollection.flatten()
|
}catch (e: Exception){
|
||||||
val commonFormats =
|
withContext(Dispatchers.Main) {
|
||||||
flatFormatCollection.groupingBy { it.format_id }.eachCount()
|
refreshBtn.isEnabled = true
|
||||||
.filter { it.value == distinctItems.size }
|
filterBtn.isEnabled = true
|
||||||
.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }
|
okBtn.isEnabled = true
|
||||||
.map { it.value }
|
refreshBtn.text = getString(R.string.update)
|
||||||
formats.addAll(commonFormats)
|
recyclerView.isVisible = true
|
||||||
|
shimmers.visibility = View.GONE
|
||||||
chosenFormats = commonFormats.filter { it.filesize != 0L }
|
shimmers.stopShimmer()
|
||||||
.mapTo(mutableListOf()) { it.copy() }
|
e.printStackTrace()
|
||||||
chosenFormats = when (items.first()?.type) {
|
Toast.makeText(context, getString(R.string.error_updating_formats), Toast.LENGTH_SHORT).show()
|
||||||
Type.audio -> chosenFormats.filter {
|
}
|
||||||
it.vcodec.isBlank() || it.vcodec == "none"
|
}
|
||||||
}
|
}
|
||||||
|
updateFormatsJob?.start()
|
||||||
else -> chosenFormats
|
}
|
||||||
}
|
|
||||||
if (chosenFormats.isEmpty()) throw Exception()
|
|
||||||
chosenFormats.forEach {
|
|
||||||
it.filesize =
|
|
||||||
flatFormatCollection.filter { f -> f.format_id == it.format_id }
|
|
||||||
.sumOf { itt -> itt.filesize }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isMissingFormats = formats.isEmpty()
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
shimmers.visibility = View.GONE
|
|
||||||
shimmers.stopShimmer()
|
|
||||||
addFormatsToView()
|
|
||||||
refreshBtn.isVisible = isMissingFormats
|
|
||||||
refreshBtn.isEnabled = isMissingFormats
|
|
||||||
filterBtn.isEnabled = true
|
|
||||||
okBtn.isEnabled = true
|
|
||||||
formatListLinearLayout.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}catch (e: Exception){
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
refreshBtn.isEnabled = true
|
|
||||||
filterBtn.isEnabled = true
|
|
||||||
okBtn.isEnabled = true
|
|
||||||
refreshBtn.text = getString(R.string.update)
|
|
||||||
formatListLinearLayout.visibility = View.VISIBLE
|
|
||||||
shimmers.visibility = View.GONE
|
|
||||||
shimmers.stopShimmer()
|
|
||||||
|
|
||||||
e.printStackTrace()
|
|
||||||
Toast.makeText(context, getString(R.string.error_updating_formats), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateFormatsJob?.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
okBtn.setOnClickListener {
|
okBtn.setOnClickListener {
|
||||||
|
|
@ -364,6 +314,7 @@ class FormatSelectionBottomSheetDialog(
|
||||||
filterSheet.setContentView(R.layout.format_category_sheet)
|
filterSheet.setContentView(R.layout.format_category_sheet)
|
||||||
|
|
||||||
//format filter
|
//format filter
|
||||||
|
val isMissingFormats = formatViewModel.isMissingFormats.value
|
||||||
filterSheet.findViewById<LinearLayout>(R.id.format_filter_linear)?.isVisible = !isMissingFormats
|
filterSheet.findViewById<LinearLayout>(R.id.format_filter_linear)?.isVisible = !isMissingFormats
|
||||||
if (!isMissingFormats) {
|
if (!isMissingFormats) {
|
||||||
val all = filterSheet.findViewById<TextView>(R.id.all)
|
val all = filterSheet.findViewById<TextView>(R.id.all)
|
||||||
|
|
@ -373,7 +324,7 @@ class FormatSelectionBottomSheetDialog(
|
||||||
|
|
||||||
val filterOptions = listOf(all!!, suggested!!,smallest!!, generic!!)
|
val filterOptions = listOf(all!!, suggested!!,smallest!!, generic!!)
|
||||||
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
when(filterBy) {
|
when(formatViewModel.filterBy.value) {
|
||||||
FormatCategory.ALL -> all.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
FormatCategory.ALL -> all.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
||||||
FormatCategory.SUGGESTED -> {
|
FormatCategory.SUGGESTED -> {
|
||||||
suggested.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
suggested.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
||||||
|
|
@ -388,37 +339,34 @@ class FormatSelectionBottomSheetDialog(
|
||||||
|
|
||||||
all.setOnClickListener {
|
all.setOnClickListener {
|
||||||
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
filterBy = FormatCategory.ALL
|
|
||||||
all.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
all.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
||||||
addFormatsToView()
|
|
||||||
filterSheet.dismiss()
|
filterSheet.dismiss()
|
||||||
|
formatViewModel.filterBy.value = FormatCategory.ALL
|
||||||
}
|
}
|
||||||
suggested.setOnClickListener {
|
suggested.setOnClickListener {
|
||||||
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
filterBy = FormatCategory.SUGGESTED
|
|
||||||
suggested.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
suggested.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
||||||
addFormatsToView()
|
|
||||||
filterSheet.dismiss()
|
filterSheet.dismiss()
|
||||||
|
formatViewModel.filterBy.value = FormatCategory.SUGGESTED
|
||||||
}
|
}
|
||||||
smallest.setOnClickListener {
|
smallest.setOnClickListener {
|
||||||
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
filterBy = FormatCategory.SMALLEST
|
|
||||||
smallest.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
smallest.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
||||||
addFormatsToView()
|
|
||||||
filterSheet.dismiss()
|
filterSheet.dismiss()
|
||||||
|
formatViewModel.filterBy.value = FormatCategory.SMALLEST
|
||||||
}
|
}
|
||||||
generic.setOnClickListener {
|
generic.setOnClickListener {
|
||||||
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
|
||||||
filterBy = FormatCategory.GENERIC
|
|
||||||
generic.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
generic.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
||||||
addFormatsToView()
|
|
||||||
filterSheet.dismiss()
|
filterSheet.dismiss()
|
||||||
|
formatViewModel.filterBy.value = FormatCategory.GENERIC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//format source
|
//format source
|
||||||
val formatSourceLinear = filterSheet.findViewById<LinearLayout>(R.id.format_source_linear)!!
|
val formatSourceLinear = filterSheet.findViewById<LinearLayout>(R.id.format_source_linear)!!
|
||||||
val canSwitch = items.all { it!!.url.isYoutubeURL() }
|
val items = formatViewModel.selectedItems.value
|
||||||
|
val canSwitch = items.all { it.url.isYoutubeURL() }
|
||||||
formatSourceLinear.isVisible = canSwitch
|
formatSourceLinear.isVisible = canSwitch
|
||||||
if (canSwitch) {
|
if (canSwitch) {
|
||||||
val formatSourceOptions = mutableListOf<TextView>()
|
val formatSourceOptions = mutableListOf<TextView>()
|
||||||
|
|
@ -435,6 +383,7 @@ class FormatSelectionBottomSheetDialog(
|
||||||
txt.tag = tag
|
txt.tag = tag
|
||||||
txt.setOnClickListener {
|
txt.setOnClickListener {
|
||||||
currentFormatSource = it.tag.toString()
|
currentFormatSource = it.tag.toString()
|
||||||
|
formatViewModel.filterBy.value = FormatCategory.ALL
|
||||||
refreshBtn.performClick()
|
refreshBtn.performClick()
|
||||||
filterSheet.dismiss()
|
filterSheet.dismiss()
|
||||||
}
|
}
|
||||||
|
|
@ -447,33 +396,6 @@ class FormatSelectionBottomSheetDialog(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//format layout
|
|
||||||
val listLayout = filterSheet.findViewById<TextView>(R.id.layout_list)!!
|
|
||||||
val gridLayout = filterSheet.findViewById<TextView>(R.id.layout_grid)!!
|
|
||||||
|
|
||||||
if (usingGrid) {
|
|
||||||
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
|
|
||||||
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
|
||||||
}else{
|
|
||||||
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
|
|
||||||
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
|
||||||
}
|
|
||||||
|
|
||||||
listLayout.setOnClickListener {
|
|
||||||
usingGrid = false
|
|
||||||
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
|
|
||||||
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
|
||||||
addFormatsToView()
|
|
||||||
filterSheet.dismiss()
|
|
||||||
}
|
|
||||||
gridLayout.setOnClickListener {
|
|
||||||
usingGrid = true
|
|
||||||
listLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty, 0,0,0)
|
|
||||||
gridLayout.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
|
|
||||||
addFormatsToView()
|
|
||||||
filterSheet.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
val displayMetrics = DisplayMetrics()
|
val displayMetrics = DisplayMetrics()
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
filterSheet.behavior.peekHeight = displayMetrics.heightPixels
|
filterSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||||
|
|
@ -482,240 +404,18 @@ class FormatSelectionBottomSheetDialog(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnFormats(){
|
private fun returnFormats(){
|
||||||
if (items.size == 1){
|
if (_listener != null){
|
||||||
//simple video format selection
|
//simple video format selection
|
||||||
listener.onFormatClick(FormatTuple(selectedVideo, selectedAudios.ifEmpty { listOf(downloadViewModel.getFormat(chosenFormats, Type.audio)) }))
|
listener.onFormatClick(FormatTuple(adapter.selectedVideoFormat, adapter.selectedAudioFormats.ifEmpty {
|
||||||
|
listOf(downloadViewModel.getFormat(formats.filter { it.label == null }.map { it.format!! }, Type.audio))
|
||||||
|
}))
|
||||||
}else{
|
}else{
|
||||||
//playlist format selection
|
val res = formatViewModel.getFormatsForItemsBasedOnFormat(adapter.selectedVideoFormat!!, adapter.selectedAudioFormats)
|
||||||
val formatsToReturn = mutableListOf<MultipleItemFormatTuple>()
|
multipleFormatsListener.onFormatClick(res)
|
||||||
items.forEach {
|
|
||||||
formatsToReturn.add(
|
|
||||||
MultipleItemFormatTuple(
|
|
||||||
it!!.url,
|
|
||||||
FormatTuple(
|
|
||||||
it.allFormats.firstOrNull { f -> f.format_id == selectedVideo?.format_id },
|
|
||||||
selectedAudios.map { sa ->
|
|
||||||
it.allFormats.first { a -> a.format_id == sa.format_id }
|
|
||||||
}.ifEmpty { null }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
multipleFormatsListener.onFormatClick(formatsToReturn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addFormatsToView(){
|
|
||||||
//sort
|
|
||||||
var finalFormats: List<Format> = when(sortBy){
|
|
||||||
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
|
||||||
FormatSorting.id -> chosenFormats.sortedBy { it.format_id }
|
|
||||||
FormatSorting.codec -> {
|
|
||||||
val codecOrder = resources.getStringArray(R.array.video_codec_values).toMutableList()
|
|
||||||
codecOrder.removeAt(0)
|
|
||||||
chosenFormats.groupBy { format -> codecOrder.indexOfFirst { format.vcodec.matches("^(${it})(.+)?$".toRegex()) } }
|
|
||||||
|
|
||||||
.flatMap {
|
|
||||||
it.value.sortedByDescending { l -> l.filesize }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FormatSorting.filesize -> chosenFormats
|
|
||||||
}
|
|
||||||
|
|
||||||
val formatSorter = FormatUtil(requireContext())
|
|
||||||
|
|
||||||
//filter category
|
|
||||||
when(filterBy){
|
|
||||||
FormatCategory.ALL -> {}
|
|
||||||
FormatCategory.SUGGESTED -> {
|
|
||||||
finalFormats = if (items.first()?.type == Type.audio){
|
|
||||||
formatSorter.sortAudioFormats(finalFormats)
|
|
||||||
}else{
|
|
||||||
val audioFormats = finalFormats.filter { it.vcodec.isBlank() || it.vcodec == "none" }
|
|
||||||
val videoFormats = finalFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }
|
|
||||||
|
|
||||||
formatSorter.sortVideoFormats(videoFormats) + formatSorter.sortAudioFormats(audioFormats)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FormatCategory.SMALLEST -> {
|
|
||||||
val tmpFormats = finalFormats
|
|
||||||
.asSequence()
|
|
||||||
.map { it.copy() }
|
|
||||||
.filter { it.filesize > 0 }
|
|
||||||
.onEach {
|
|
||||||
var tmp = it.format_note
|
|
||||||
//formats that end like 1080P
|
|
||||||
if (tmp.endsWith("060")){
|
|
||||||
tmp = tmp.removeSuffix("60")
|
|
||||||
}
|
|
||||||
tmp = tmp.removeSuffix("p")
|
|
||||||
//formats that are written like 1920X1080
|
|
||||||
val split = tmp.split("x")
|
|
||||||
if (split.size > 1){
|
|
||||||
tmp = split[1]
|
|
||||||
}
|
|
||||||
it.format_note = tmp
|
|
||||||
}
|
|
||||||
.groupBy { it.format_note }
|
|
||||||
.map { it.value.minBy { it2 -> it2.filesize } }.toList()
|
|
||||||
finalFormats = finalFormats.filter { tmpFormats.map { it2 -> it2.format_id }.contains(it.format_id) }
|
|
||||||
}
|
|
||||||
FormatCategory.GENERIC -> {
|
|
||||||
finalFormats = listOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val canMultiSelectAudio = items.first()?.type == Type.video && finalFormats.find { it.vcodec.isBlank() || it.vcodec == "none" } != null
|
|
||||||
|
|
||||||
if (!canMultiSelectAudio) {
|
|
||||||
audioFormatList.visibility = View.GONE
|
|
||||||
videoTitle.visibility = View.GONE
|
|
||||||
audioTitle.visibility = View.GONE
|
|
||||||
okBtn.visibility = View.GONE
|
|
||||||
}else{
|
|
||||||
if (finalFormats.count { it.vcodec.isBlank() || it.vcodec == "none" } == 0){
|
|
||||||
audioFormatList.visibility = View.GONE
|
|
||||||
audioTitle.visibility = View.GONE
|
|
||||||
videoTitle.visibility = View.GONE
|
|
||||||
okBtn.visibility = View.GONE
|
|
||||||
}else{
|
|
||||||
audioFormatList.visibility = View.VISIBLE
|
|
||||||
audioTitle.visibility = View.VISIBLE
|
|
||||||
videoTitle.visibility = View.VISIBLE
|
|
||||||
okBtn.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
videoFormatList.removeAllViews()
|
|
||||||
audioFormatList.removeAllViews()
|
|
||||||
|
|
||||||
if (usingGrid) {
|
|
||||||
videoFormatList.columnCount = 2
|
|
||||||
audioFormatList.columnCount = 2
|
|
||||||
}else{
|
|
||||||
videoFormatList.columnCount = 1
|
|
||||||
audioFormatList.columnCount = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalFormats.isEmpty()){
|
|
||||||
finalFormats = if (items.first()?.type == Type.audio){
|
|
||||||
genericAudioFormats
|
|
||||||
}else{
|
|
||||||
genericVideoFormats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0.. finalFormats.lastIndex){
|
|
||||||
val format = finalFormats[i]
|
|
||||||
lateinit var formatItem: View
|
|
||||||
|
|
||||||
if (usingGrid){
|
|
||||||
formatItem = LayoutInflater.from(context).inflate(R.layout.format_item_grid, null)
|
|
||||||
formatItem.layoutParams = GridLayout.LayoutParams(
|
|
||||||
GridLayout.spec(GridLayout.UNDEFINED, 1f),
|
|
||||||
GridLayout.spec(GridLayout.UNDEFINED, 1f)).apply {
|
|
||||||
width = 0
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
|
||||||
formatItem.layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
1.0f
|
|
||||||
)
|
|
||||||
}
|
|
||||||
formatItem.tag = "${format.format_id}${format.format_note}"
|
|
||||||
UiUtil.populateFormatCard(requireContext(), formatItem as MaterialCardView, format, null)
|
|
||||||
if (selectedVideo == format) formatItem.isChecked = true
|
|
||||||
if (selectedAudios.any { it == format }) formatItem.isChecked = true
|
|
||||||
formatItem.setOnClickListener{ clickedformat ->
|
|
||||||
//if the context is behind a video or playlist, allow the ability to multiselect audio formats
|
|
||||||
if (canMultiSelectAudio){
|
|
||||||
val clickedCard = (clickedformat as MaterialCardView)
|
|
||||||
if (format.vcodec.isNotBlank() && format.vcodec != "none") {
|
|
||||||
if (clickedCard.isChecked) {
|
|
||||||
returnFormats()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
|
|
||||||
selectedVideo = format
|
|
||||||
clickedCard.isChecked = true
|
|
||||||
}else{
|
|
||||||
if(selectedAudios.contains(format)) {
|
|
||||||
selectedAudios.remove(format)
|
|
||||||
} else {
|
|
||||||
selectedAudios.add(format)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
audioFormatList.forEach { (it as MaterialCardView).isChecked = false }
|
|
||||||
audioFormatList.forEach {
|
|
||||||
(it as MaterialCardView).isChecked = selectedAudios.map { a -> "${a.format_id}${a.format_note}" }.contains(it.tag)
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if (items.size == 1){
|
|
||||||
listener.onFormatClick(FormatTuple(format, null))
|
|
||||||
}else{
|
|
||||||
val formatsToReturn = mutableListOf<MultipleItemFormatTuple>()
|
|
||||||
val f = if (genericAudioFormats.contains(format) || genericVideoFormats.contains(format)) format else null
|
|
||||||
items.forEach {
|
|
||||||
formatsToReturn.add(
|
|
||||||
MultipleItemFormatTuple(
|
|
||||||
it!!.url,
|
|
||||||
FormatTuple(
|
|
||||||
f ?: it.allFormats.firstOrNull { af -> af.format_id == format.format_id },
|
|
||||||
null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
multipleFormatsListener.onFormatClick(formatsToReturn)
|
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formatItem.setOnLongClickListener {
|
|
||||||
UiUtil.showFormatDetails(format, requireActivity())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canMultiSelectAudio){
|
|
||||||
if (format.vcodec.isNotBlank() && format.vcodec != "none") videoFormatList.addView(formatItem)
|
|
||||||
else audioFormatList.addView(formatItem)
|
|
||||||
}else{
|
|
||||||
videoFormatList.addView(formatItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items.first()?.type == Type.video){
|
|
||||||
selectedVideo = null
|
|
||||||
run breaking@{
|
|
||||||
videoFormatList.children.forEach {
|
|
||||||
val card = it as MaterialCardView
|
|
||||||
if (card.isChecked){
|
|
||||||
selectedVideo = finalFormats.first { format -> "${format.format_id}${format.format_note}" == card.tag }
|
|
||||||
return@breaking
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
selectedAudios = mutableListOf()
|
|
||||||
run breaking@{
|
|
||||||
audioFormatList.children.forEach {
|
|
||||||
val card = it as MaterialCardView
|
|
||||||
if (card.isChecked){
|
|
||||||
selectedAudios.add(finalFormats.first { format -> "${format.format_id}${format.format_note}" == card.tag })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCancel(dialog: DialogInterface) {
|
override fun onCancel(dialog: DialogInterface) {
|
||||||
super.onCancel(dialog)
|
super.onCancel(dialog)
|
||||||
cleanUp()
|
cleanUp()
|
||||||
|
|
@ -733,6 +433,16 @@ class FormatSelectionBottomSheetDialog(
|
||||||
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("formatSheet")!!).commit()
|
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("formatSheet")!!).commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onItemSelect(item: Format, audioFormats: List<Format>?) {
|
||||||
|
if (_listener != null) {
|
||||||
|
listener.onFormatClick(FormatTuple(item, audioFormats))
|
||||||
|
}else{
|
||||||
|
val formatsToReturn = formatViewModel.getFormatsForItemsBasedOnFormat(item)
|
||||||
|
multipleFormatsListener.onFormatClick(formatsToReturn)
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnFormatClickListener{
|
interface OnFormatClickListener{
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<ListPreference>("youtube_home_recommendations")?.apply {
|
findPreference<ListPreference>("home_recommendations")?.apply {
|
||||||
val s = getString(R.string.video_recommendations_summary)
|
val s = getString(R.string.video_recommendations_summary)
|
||||||
summary = if (value.isNullOrBlank()) {
|
summary = if (value.isNullOrBlank()) {
|
||||||
s
|
s
|
||||||
|
|
@ -288,13 +288,20 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
|
|
||||||
if (newValue == "yt_api") {
|
if (newValue == "yt_api") {
|
||||||
findPreference<EditTextPreference>("api_key")?.isVisible = true
|
findPreference<EditTextPreference>("api_key")?.isVisible = true
|
||||||
|
}else if (newValue == "custom") {
|
||||||
|
findPreference<EditTextPreference>("custom_home_recommendation_url")?.isVisible = true
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findPreference<EditTextPreference>("custom_home_recommendation_url")?.apply {
|
||||||
|
title = "[${getString(R.string.video_recommendations)}] ${getString(R.string.custom)}"
|
||||||
|
isVisible = preferences.getString("home_recommendations", "") == "custom"
|
||||||
|
}
|
||||||
|
|
||||||
findPreference<EditTextPreference>("api_key")?.apply {
|
findPreference<EditTextPreference>("api_key")?.apply {
|
||||||
isVisible = preferences.getString("youtube_home_recommendations", "") == "yt_api"
|
isVisible = preferences.getString("home_recommendations", "") == "yt_api"
|
||||||
val s = getString(R.string.api_key_summary)
|
val s = getString(R.string.api_key_summary)
|
||||||
summary = if (text.isNullOrBlank()) {
|
summary = if (text.isNullOrBlank()) {
|
||||||
s
|
s
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,45 @@
|
||||||
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens
|
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context.INPUT_METHOD_SERVICE
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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.view.Window
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
|
import com.deniscerri.ytdl.database.models.CookieItem
|
||||||
import com.deniscerri.ytdl.database.models.YoutubeGeneratePoTokenItem
|
import com.deniscerri.ytdl.database.models.YoutubeGeneratePoTokenItem
|
||||||
import com.deniscerri.ytdl.database.models.YoutubePoTokenItem
|
import com.deniscerri.ytdl.database.models.YoutubePoTokenItem
|
||||||
|
import com.deniscerri.ytdl.ui.more.WebViewActivity
|
||||||
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
||||||
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.PoTokenWebViewLoginActivity
|
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.PoTokenWebViewLoginActivity
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
|
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.button.MaterialButton
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.materialswitch.MaterialSwitch
|
import com.google.android.material.materialswitch.MaterialSwitch
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class GenerateYoutubePoTokensFragment : Fragment() {
|
class GenerateYoutubePoTokensFragment : Fragment() {
|
||||||
private lateinit var settingsActivity: SettingsActivity
|
private lateinit var settingsActivity: SettingsActivity
|
||||||
|
|
@ -31,6 +47,8 @@ class GenerateYoutubePoTokensFragment : Fragment() {
|
||||||
private lateinit var configuration : MutableList<YoutubeGeneratePoTokenItem>
|
private lateinit var configuration : MutableList<YoutubeGeneratePoTokenItem>
|
||||||
private lateinit var workManager : WorkManager
|
private lateinit var workManager : WorkManager
|
||||||
|
|
||||||
|
private lateinit var webPoTokenResultLauncher : ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -119,7 +137,7 @@ class GenerateYoutubePoTokensFragment : Fragment() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
val webPoTokenResultLauncher = registerForActivityResult(
|
webPoTokenResultLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
|
@ -137,13 +155,11 @@ class GenerateYoutubePoTokensFragment : Fragment() {
|
||||||
configuration.add(conf)
|
configuration.add(conf)
|
||||||
setValues(conf)
|
setValues(conf)
|
||||||
preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply()
|
preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply()
|
||||||
}else {
|
|
||||||
Snackbar.make(requireView(), R.string.network_error, Snackbar.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
regenerate.setOnClickListener {
|
regenerate.setOnClickListener {
|
||||||
webPoTokenResultLauncher.launch(Intent(requireContext(), PoTokenWebViewLoginActivity::class.java))
|
showBottomSheet()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch.setOnClickListener {
|
switch.setOnClickListener {
|
||||||
|
|
@ -168,4 +184,44 @@ class GenerateYoutubePoTokensFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showBottomSheet(){
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val layout = BottomSheetDialog(requireContext())
|
||||||
|
layout.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
layout.setContentView(R.layout.generate_po_token_url_bottom_sheet)
|
||||||
|
|
||||||
|
val editText = layout.findViewById<EditText>(R.id.url_edittext)!!
|
||||||
|
val text = preferences.getString("genenerate_youtube_po_token_preferred_url", "https://youtube.com/account")
|
||||||
|
editText.setText(text)
|
||||||
|
editText.setSelection(editText.text.length)
|
||||||
|
|
||||||
|
val regenerateBtn = layout.findViewById<MaterialButton>(R.id.getPoTokenBtn)!!
|
||||||
|
|
||||||
|
editText.doOnTextChanged { text, start, before, count ->
|
||||||
|
regenerateBtn.isEnabled = editText.text.toString().isYoutubeURL()
|
||||||
|
}
|
||||||
|
|
||||||
|
regenerateBtn.setOnClickListener {
|
||||||
|
val intent = Intent(requireContext(), PoTokenWebViewLoginActivity::class.java)
|
||||||
|
intent.putExtra("url", editText.text.toString())
|
||||||
|
webPoTokenResultLauncher.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val imm = requireActivity().getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
editText.postDelayed({
|
||||||
|
editText.requestFocus()
|
||||||
|
imm.showSoftInput(editText, 0)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
layout.show()
|
||||||
|
layout.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
layout.window!!.setLayout(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +55,8 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.webview_activity)
|
setContentView(R.layout.webview_activity)
|
||||||
|
|
||||||
|
val url = intent.getStringExtra("url")!!
|
||||||
|
|
||||||
cookiesViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
cookiesViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
||||||
|
|
@ -77,6 +79,7 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
cookieManager = CookieManager.getInstance()
|
cookieManager = CookieManager.getInstance()
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(this@PoTokenWebViewLoginActivity)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this@PoTokenWebViewLoginActivity)
|
||||||
|
preferences.edit().putString("genenerate_youtube_po_token_preferred_url", url).apply()
|
||||||
|
|
||||||
webViewClient = object : AccompanistWebViewClient() {
|
webViewClient = object : AccompanistWebViewClient() {
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
|
@ -113,13 +116,13 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
|
|
||||||
//update cookies
|
//update cookies
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val url = "Po Token Generated Cookies"
|
val cookieURL = "Po Token Generated Cookies"
|
||||||
cookiesViewModel.getCookiesFromDB(url).getOrNull()?.let {
|
cookiesViewModel.getCookiesFromDB(cookieURL).getOrNull()?.let {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
cookiesViewModel.insert(
|
cookiesViewModel.insert(
|
||||||
CookieItem(
|
CookieItem(
|
||||||
0,
|
0,
|
||||||
url,
|
cookieURL,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -137,8 +140,6 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
webView.clearCache(true)
|
webView.clearCache(true)
|
||||||
// ensures that the WebView isn't doing anything when destroying it
|
// ensures that the WebView isn't doing anything when destroying it
|
||||||
webView.loadUrl("about:blank")
|
webView.loadUrl("about:blank")
|
||||||
|
|
@ -150,7 +151,7 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
webViewCompose.apply {
|
webViewCompose.apply {
|
||||||
setContent { WebViewView() }
|
setContent { WebViewView(url) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +159,7 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled", "JavascriptInterface")
|
@SuppressLint("SetJavaScriptEnabled", "JavascriptInterface")
|
||||||
@Composable
|
@Composable
|
||||||
fun WebViewView() {
|
fun WebViewView(url: String) {
|
||||||
val webViewChromeClient = remember {
|
val webViewChromeClient = remember {
|
||||||
object : AccompanistWebChromeClient() {
|
object : AccompanistWebChromeClient() {
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +167,7 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
|
||||||
|
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues ->
|
Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues ->
|
||||||
WebView(
|
WebView(
|
||||||
state = rememberWebViewState("https://youtube.com/account"), client = webViewClient, chromeClient = webViewChromeClient,
|
state = rememberWebViewState(url), client = webViewClient, chromeClient = webViewChromeClient,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ object UiUtil {
|
||||||
if (chosenFormat.tbr.isNullOrBlank() || (chosenFormat.vcodec.isNotBlank() && chosenFormat.vcodec != "none")) {
|
if (chosenFormat.tbr.isNullOrBlank() || (chosenFormat.vcodec.isNotBlank() && chosenFormat.vcodec != "none")) {
|
||||||
isVisible = false
|
isVisible = false
|
||||||
}else{
|
}else{
|
||||||
|
isVisible = true
|
||||||
text = chosenFormat.tbr
|
text = chosenFormat.tbr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="5dp"
|
||||||
android:background="@drawable/rounded_corner"
|
android:background="@drawable/rounded_corner"
|
||||||
android:backgroundTint="?attr/colorSecondary"
|
android:backgroundTint="?attr/colorPrimaryInverse"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
|
|
||||||
|
|
@ -102,54 +102,4 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/format_layout_linear"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="20dp"
|
|
||||||
android:layout_marginBottom="10dp"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/layout"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/layout_list"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:paddingVertical="10dp"
|
|
||||||
android:paddingHorizontal="20dp"
|
|
||||||
android:drawablePadding="30dp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="15sp"
|
|
||||||
android:text="@string/list"
|
|
||||||
app:drawableStartCompat="@drawable/ic_check" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/layout_grid"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:paddingVertical="10dp"
|
|
||||||
android:paddingHorizontal="20dp"
|
|
||||||
android:drawablePadding="30dp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="15sp"
|
|
||||||
android:text="@string/grid"
|
|
||||||
app:drawableStartCompat="@drawable/empty" />
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
@ -103,81 +104,18 @@
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:scrollbars="none">
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/format_item" />
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/format_list_linear_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/video_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="20dp"
|
|
||||||
android:clickable="false"
|
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
|
||||||
android:gravity="start"
|
|
||||||
app:cornerRadius="0dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:paddingVertical="20dp"
|
|
||||||
android:text="@string/video" />
|
|
||||||
|
|
||||||
<GridLayout
|
|
||||||
android:id="@+id/video_linear_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:columnCount="1"
|
|
||||||
android:alignmentMode="alignBounds"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/audio_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="20dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:clickable="false"
|
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
|
||||||
android:gravity="start"
|
|
||||||
app:cornerRadius="0dp"
|
|
||||||
android:paddingVertical="20dp"
|
|
||||||
android:text="@string/audio" />
|
|
||||||
|
|
||||||
<GridLayout
|
|
||||||
android:id="@+id/audio_linear_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:columnCount="1"
|
|
||||||
android:alignmentMode="alignBounds"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
||||||
|
|
||||||
<com.facebook.shimmer.ShimmerFrameLayout
|
<com.facebook.shimmer.ShimmerFrameLayout
|
||||||
android:id="@+id/format_list_shimmer"
|
android:id="@+id/format_list_shimmer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:paddingTop="10dp">
|
android:paddingTop="10dp">
|
||||||
|
|
|
||||||
15
app/src/main/res/layout/format_type_label.xml
Normal file
15
app/src/main/res/layout/format_type_label.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Button android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:clickable="false"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||||
|
android:gravity="start"
|
||||||
|
app:cornerRadius="0dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingVertical="20dp"
|
||||||
|
android:text="@string/video"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" />
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:hint="@string/url"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/scr">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/url_edittext"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:maxLines="2000" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/getPoTokenBtn"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autoLink="all"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:text="@string/regenerate"
|
||||||
|
app:icon="@drawable/baseline_stars_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/redownload"
|
android:id="@+id/redownload"
|
||||||
android:icon="@drawable/baseline_share_24"
|
android:icon="@drawable/baseline_download_24"
|
||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:title="@string/redownload" />
|
android:title="@string/redownload" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1151,6 +1151,7 @@
|
||||||
<item>@string/ytdlp_recommnedations</item>
|
<item>@string/ytdlp_recommnedations</item>
|
||||||
<item>@string/ytdlp_liked</item>
|
<item>@string/ytdlp_liked</item>
|
||||||
<item>@string/ytdlp_watch_history</item>
|
<item>@string/ytdlp_watch_history</item>
|
||||||
|
<item>@string/custom</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="video_recommnedations_values">
|
<array name="video_recommnedations_values">
|
||||||
|
|
@ -1161,6 +1162,7 @@
|
||||||
<item>yt_dlp_recommendations</item>
|
<item>yt_dlp_recommendations</item>
|
||||||
<item>yt_dlp_liked</item>
|
<item>yt_dlp_liked</item>
|
||||||
<item>yt_dlp_watch_history</item>
|
<item>yt_dlp_watch_history</item>
|
||||||
|
<item>custom</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<!--below is for preferred audio language, not related to app's language-->
|
<!--below is for preferred audio language, not related to app's language-->
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,7 @@
|
||||||
<string name="subtitle_languages">Subtitle languages</string>
|
<string name="subtitle_languages">Subtitle languages</string>
|
||||||
<string name="format_source">Formats Source</string>
|
<string name="format_source">Formats Source</string>
|
||||||
<string name="video_recommendations">Video Recommendations</string>
|
<string name="video_recommendations">Video Recommendations</string>
|
||||||
<string name="video_recommendations_summary">Get recommended YouTube videos on the home screen</string>
|
<string name="video_recommendations_summary">Get recommended videos on the home screen</string>
|
||||||
<string name="preferred_search_engine">Preferred Search Engine</string>
|
<string name="preferred_search_engine">Preferred Search Engine</string>
|
||||||
<string name="preferred_search_engine_summary">The search engine to use for in-app searches</string>
|
<string name="preferred_search_engine_summary">The search engine to use for in-app searches</string>
|
||||||
<string name="format_filtering_hint">All items must be of the same type to use this option</string>
|
<string name="format_filtering_hint">All items must be of the same type to use this option</string>
|
||||||
|
|
@ -475,4 +475,5 @@
|
||||||
<string name="generate_potokens">Generate PO Tokens</string>
|
<string name="generate_potokens">Generate PO Tokens</string>
|
||||||
<string name="regenerate">Re-generate</string>
|
<string name="regenerate">Re-generate</string>
|
||||||
<string name="generate_potokens_warning">* By enabling this, you need to disable cookies</string>
|
<string name="generate_potokens_warning">* By enabling this, you need to disable cookies</string>
|
||||||
|
<string name="custom">Custom</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -71,13 +71,18 @@
|
||||||
<PreferenceCategory android:title="YouTube">
|
<PreferenceCategory android:title="YouTube">
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:icon="@drawable/baseline_recommend_24"
|
android:icon="@drawable/baseline_recommend_24"
|
||||||
android:key="youtube_home_recommendations"
|
android:key="home_recommendations"
|
||||||
android:defaultValue="newpipe"
|
android:defaultValue="newpipe"
|
||||||
android:entries="@array/video_recommendations"
|
android:entries="@array/video_recommendations"
|
||||||
android:entryValues="@array/video_recommnedations_values"
|
android:entryValues="@array/video_recommnedations_values"
|
||||||
app:summary="@string/video_recommendations_summary"
|
app:summary="@string/video_recommendations_summary"
|
||||||
app:title="@string/video_recommendations" />
|
app:title="@string/video_recommendations" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:icon="@drawable/baseline_recommend_24"
|
||||||
|
app:key="custom_home_recommendation_url"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/ic_key"
|
android:icon="@drawable/ic_key"
|
||||||
app:key="api_key"
|
app:key="api_key"
|
||||||
|
|
@ -120,7 +125,7 @@
|
||||||
android:entryValues="@array/countries_values"
|
android:entryValues="@array/countries_values"
|
||||||
app:icon="@drawable/ic_language"
|
app:icon="@drawable/ic_language"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:dependency="youtube_home_recommendations"
|
app:dependency="home_recommendations"
|
||||||
app:key="locale"
|
app:key="locale"
|
||||||
app:title="@string/preferred_locale" />
|
app:title="@string/preferred_locale" />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue