stuff
Add multiselect audio in format card. use piped for format updating and single video fetching, the rest invidious Make search suggestions as a preference cuz of privacy reasons Selecting the videos in the download queue causes the app to be force closed Removed ip whois network request update download item data when you schedule a quick downloaded item
This commit is contained in:
parent
35f8f939e8
commit
aadac439f3
28 changed files with 630 additions and 211 deletions
|
|
@ -198,7 +198,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
|
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
|
||||||
return oldItem.url == newItem.url && oldItem.format.format_id == newItem.format.format_id
|
return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||||
fun getDownloadById(id: Long) : DownloadItem
|
fun getDownloadById(id: Long) : DownloadItem
|
||||||
|
|
||||||
|
@Query("SELECT id FROM downloads ORDER BY id DESC LIMIT 1")
|
||||||
|
fun getLastDownloadId(): Long
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insert(item: DownloadItem) : Long
|
suspend fun insert(item: DownloadItem) : Long
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
|
||||||
data class Format(
|
data class Format(
|
||||||
@SerializedName(value = "format_id", alternate = ["itag"])
|
@SerializedName(value = "format_id", alternate = ["itag"])
|
||||||
var format_id: String = "",
|
var format_id: String = "",
|
||||||
@SerializedName(value = "ext", alternate = ["container"])
|
@SerializedName(value = "ext", alternate = ["container", "format"])
|
||||||
var container: String = "",
|
var container: String = "",
|
||||||
@SerializedName(value = "vcodec")
|
@SerializedName(value = "vcodec")
|
||||||
var vcodec: String = "",
|
var vcodec: String = "",
|
||||||
|
|
@ -15,7 +15,7 @@ data class Format(
|
||||||
var encoding: String = "",
|
var encoding: String = "",
|
||||||
@SerializedName(value = "filesize", alternate = ["clen", "filesize_approx"])
|
@SerializedName(value = "filesize", alternate = ["clen", "filesize_approx"])
|
||||||
var filesize: Long = 0,
|
var filesize: Long = 0,
|
||||||
@SerializedName(value = "format_note", alternate = ["resolution", "audioQuality"])
|
@SerializedName(value = "format_note", alternate = ["resolution", "audioQuality", "quality"])
|
||||||
var format_note: String = "",
|
var format_note: String = "",
|
||||||
@SerializedName(value = "fps")
|
@SerializedName(value = "fps")
|
||||||
var fps: String? = "",
|
var fps: String? = "",
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@ data class VideoPreferences (
|
||||||
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
||||||
var writeSubs: Boolean = false,
|
var writeSubs: Boolean = false,
|
||||||
var subsLanguages: String = "en.*,.*-orig",
|
var subsLanguages: String = "en.*,.*-orig",
|
||||||
|
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||||
var removeAudio: Boolean = false
|
var removeAudio: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.cancelQueued()
|
downloadDao.cancelQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLastDownloadId() : Long {
|
||||||
|
return downloadDao.getLastDownloadId()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long {
|
fun checkIfReDownloadingErroredOrCancelled(item: DownloadItem) : Long {
|
||||||
val converters = Converters()
|
val converters = Converters()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
|
@ -28,9 +29,12 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
@ -39,6 +43,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
private val repository : DownloadRepository
|
private val repository : DownloadRepository
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
private val commandTemplateDao: CommandTemplateDao
|
private val commandTemplateDao: CommandTemplateDao
|
||||||
|
private val infoUtil : InfoUtil
|
||||||
val allDownloads : LiveData<List<DownloadItem>>
|
val allDownloads : LiveData<List<DownloadItem>>
|
||||||
val queuedDownloads : LiveData<List<DownloadItem>>
|
val queuedDownloads : LiveData<List<DownloadItem>>
|
||||||
val activeDownloads : LiveData<List<DownloadItem>>
|
val activeDownloads : LiveData<List<DownloadItem>>
|
||||||
|
|
@ -62,6 +67,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
repository = DownloadRepository(dao)
|
repository = DownloadRepository(dao)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
|
infoUtil = InfoUtil(application)
|
||||||
|
|
||||||
allDownloads = repository.allDownloads.asLiveData()
|
allDownloads = repository.allDownloads.asLiveData()
|
||||||
queuedDownloads = repository.queuedDownloads.asLiveData()
|
queuedDownloads = repository.queuedDownloads.asLiveData()
|
||||||
|
|
@ -74,9 +80,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString()
|
formatIDPreference = sharedPreferences.getString("format_id", "").toString()
|
||||||
|
|
||||||
val videoFormat = getApplication<App>().resources.getStringArray(R.array.video_formats)
|
val videoFormat = App.instance.resources.getStringArray(R.array.video_formats)
|
||||||
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||||
if (videoContainer == "Default") videoContainer = application.getString(R.string.defaultValue)
|
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
|
||||||
|
|
||||||
defaultVideoFormats = mutableListOf()
|
defaultVideoFormats = mutableListOf()
|
||||||
videoFormat.forEach {
|
videoFormat.forEach {
|
||||||
|
|
@ -95,7 +101,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
bestVideoFormat = defaultVideoFormats.last()
|
bestVideoFormat = defaultVideoFormats.last()
|
||||||
|
|
||||||
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||||
if (audioContainer == "Default") audioContainer = application.getString(R.string.defaultValue)
|
if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue)
|
||||||
bestAudioFormat = Format(
|
bestAudioFormat = Format(
|
||||||
getApplication<App>().resources.getString(R.string.best_quality),
|
getApplication<App>().resources.getString(R.string.best_quality),
|
||||||
audioContainer!!,
|
audioContainer!!,
|
||||||
|
|
@ -112,7 +118,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
|
fun updateDownload(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
|
||||||
repository.update(item);
|
repository.update(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getItemByID(id: Long) : DownloadItem {
|
fun getItemByID(id: Long) : DownloadItem {
|
||||||
|
|
@ -317,20 +323,20 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGenericAudioFormats() : MutableList<Format>{
|
fun getGenericAudioFormats() : MutableList<Format>{
|
||||||
val audioFormats = application.resources.getStringArray(R.array.audio_formats)
|
val audioFormats = App.instance.resources.getStringArray(R.array.audio_formats)
|
||||||
val formats = mutableListOf<Format>()
|
val formats = mutableListOf<Format>()
|
||||||
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
||||||
if (containerPreference == "Default") containerPreference = application.getString(R.string.defaultValue)
|
if (containerPreference == "Default") containerPreference = App.instance.getString(R.string.defaultValue)
|
||||||
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGenericVideoFormats() : MutableList<Format>{
|
fun getGenericVideoFormats() : MutableList<Format>{
|
||||||
val videoFormats = application.resources.getStringArray(R.array.video_formats)
|
val videoFormats = App.instance.resources.getStringArray(R.array.video_formats)
|
||||||
val formats = mutableListOf<Format>()
|
val formats = mutableListOf<Format>()
|
||||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||||
if (containerPreference == "Default") containerPreference = application.getString(R.string.defaultValue)
|
if (containerPreference == "Default") containerPreference = application.getString(R.string.defaultValue)
|
||||||
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
videoFormats.forEach { formats.add(Format(it, containerPreference!!,application.getString(R.string.defaultValue),"", "",0, it)) }
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,14 +391,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
return repository.getActiveAndQueuedDownloads()
|
return repository.getActiveAndQueuedDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun queueDownloads(items: List<DownloadItem>) {
|
suspend fun queueDownloads(items: List<DownloadItem>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val context = getApplication<App>().applicationContext
|
val context = App.instance
|
||||||
val activeAndQueuedDownloads = withContext(Dispatchers.IO){
|
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||||
repository.getActiveAndQueuedDownloads()
|
|
||||||
}
|
|
||||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
|
var lastDownloadId = repository.getLastDownloadId()
|
||||||
items.forEach {
|
items.forEach {
|
||||||
|
lastDownloadId++
|
||||||
it.status = DownloadRepository.Status.Queued.toString()
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
if (activeAndQueuedDownloads.firstOrNull{d ->
|
if (activeAndQueuedDownloads.firstOrNull{d ->
|
||||||
d.id = 0
|
d.id = 0
|
||||||
|
|
@ -401,12 +407,32 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
} != null) {
|
} != null) {
|
||||||
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
||||||
}else{
|
}else{
|
||||||
it.id = withContext(Dispatchers.IO){
|
it.id = lastDownloadId
|
||||||
repository.insert(it)
|
val insert = async {repository.insert(it)}
|
||||||
}
|
val id = insert.await()
|
||||||
|
it.id = id
|
||||||
queuedItems.add(it)
|
queuedItems.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if the download was quick downloaded and are incomplete
|
||||||
|
if (queuedItems.count() == 1){
|
||||||
|
val item = queuedItems.first()
|
||||||
|
if (item.title.isEmpty() || item.author.isEmpty() || item.thumb.isEmpty()){
|
||||||
|
runCatching {
|
||||||
|
val info = infoUtil.getMissingInfo(item.url)
|
||||||
|
if (item.title.isEmpty()) item.title = info?.title.toString()
|
||||||
|
if (item.author.isEmpty()) item.author = info?.author.toString()
|
||||||
|
item.duration = info?.duration.toString()
|
||||||
|
item.website = info?.website.toString()
|
||||||
|
if (item.thumb.isEmpty()) item.thumb = info?.thumb.toString()
|
||||||
|
repository.update(item)
|
||||||
|
}.onFailure { err ->
|
||||||
|
Log.e("error", err.message.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
queuedItems.forEach {
|
queuedItems.forEach {
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
var delay = if (it.downloadStartTime != 0L){
|
var delay = if (it.downloadStartTime != 0L){
|
||||||
|
|
|
||||||
|
|
@ -376,13 +376,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}else{
|
}else{
|
||||||
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
|
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
|
||||||
}
|
}
|
||||||
val suggestions = withContext(Dispatchers.IO){
|
val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){
|
||||||
if (it!!.isEmpty()) {
|
withContext(Dispatchers.IO){
|
||||||
resultViewModel.getSearchHistory().map { it.query }
|
if (it!!.isEmpty()) {
|
||||||
}else{
|
resultViewModel.getSearchHistory().map { it.query }
|
||||||
infoUtil!!.getSearchSuggestions(it.toString())
|
}else{
|
||||||
|
infoUtil!!.getSearchSuggestions(it.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else emptyList()
|
||||||
|
|
||||||
if (it!!.isEmpty()){
|
if (it!!.isEmpty()){
|
||||||
for (i in suggestions.indices) {
|
for (i in suggestions.indices) {
|
||||||
|
|
@ -445,8 +447,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}
|
}
|
||||||
searchSuggestionsLinearLayout!!.visibility = VISIBLE
|
searchSuggestionsLinearLayout!!.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
@ -141,13 +142,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
|
|
||||||
if (formats.isEmpty()) formats = downloadViewModel.getGenericAudioFormats()
|
if (formats.isEmpty()) formats = downloadViewModel.getGenericAudioFormats()
|
||||||
|
|
||||||
val formatCard = view.findViewById<ConstraintLayout>(R.id.format_card_constraintLayout)
|
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||||
val chosenFormat = downloadItem.format
|
val chosenFormat = downloadItem.format
|
||||||
uiUtil.populateFormatCard(formatCard, chosenFormat)
|
uiUtil.populateFormatCard(formatCard, chosenFormat, null)
|
||||||
val listener = object : OnFormatClickListener {
|
val listener = object : OnFormatClickListener {
|
||||||
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) {
|
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) {
|
||||||
downloadItem.format = item.first()
|
downloadItem.format = item.first()
|
||||||
uiUtil.populateFormatCard(formatCard, item.first())
|
uiUtil.populateFormatCard(formatCard, item.first(), null)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
resultItem.formats.removeAll(formats.toSet())
|
resultItem.formats.removeAll(formats.toSet())
|
||||||
|
|
@ -156,6 +157,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formats = allFormats.first().toMutableList()
|
formats = allFormats.first().toMutableList()
|
||||||
|
downloadItem.format.container = container.editText?.text.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formatCard.setOnClickListener{
|
formatCard.setOnClickListener{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -28,6 +29,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
|
@ -47,6 +49,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
private lateinit var commandTemplateDao : CommandTemplateDao
|
private lateinit var commandTemplateDao : CommandTemplateDao
|
||||||
private lateinit var uiUtil: UiUtil
|
private lateinit var uiUtil: UiUtil
|
||||||
|
private lateinit var infoUtil: InfoUtil
|
||||||
|
|
||||||
private lateinit var downloadAudioFragment: DownloadAudioFragment
|
private lateinit var downloadAudioFragment: DownloadAudioFragment
|
||||||
private lateinit var downloadVideoFragment: DownloadVideoFragment
|
private lateinit var downloadVideoFragment: DownloadVideoFragment
|
||||||
|
|
@ -58,6 +61,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
||||||
uiUtil = UiUtil(FileUtil())
|
uiUtil = UiUtil(FileUtil())
|
||||||
|
infoUtil = InfoUtil(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
@ -133,8 +134,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
))
|
))
|
||||||
|
|
||||||
var formats = mutableListOf<Format>()
|
var formats = mutableListOf<Format>()
|
||||||
formats.addAll(resultItem.formats.filter { !it.format_note.contains("audio", ignoreCase = true) })
|
formats.addAll(resultItem.formats)
|
||||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { !it.format_note.contains("audio", ignoreCase = true) })
|
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats)
|
||||||
|
|
||||||
val containers = requireContext().resources.getStringArray(R.array.video_containers)
|
val containers = requireContext().resources.getStringArray(R.array.video_containers)
|
||||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||||
|
|
@ -145,13 +146,14 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
|
|
||||||
if (formats.isEmpty()) formats = downloadViewModel.getGenericVideoFormats()
|
if (formats.isEmpty()) formats = downloadViewModel.getGenericVideoFormats()
|
||||||
|
|
||||||
val formatCard = view.findViewById<ConstraintLayout>(R.id.format_card_constraintLayout)
|
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||||
|
|
||||||
val chosenFormat = downloadItem.format
|
val chosenFormat = downloadItem.format
|
||||||
uiUtil.populateFormatCard(formatCard, chosenFormat)
|
uiUtil.populateFormatCard(formatCard, chosenFormat, null)
|
||||||
val listener = object : OnFormatClickListener {
|
val listener = object : OnFormatClickListener {
|
||||||
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) {
|
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) {
|
||||||
downloadItem.format = item.first()
|
downloadItem.format = item.first()
|
||||||
|
downloadItem.videoPreferences.audioFormatIDs.addAll(item.drop(1).map { it.format_id })
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
resultItem.formats.removeAll(formats.toSet())
|
resultItem.formats.removeAll(formats.toSet())
|
||||||
|
|
@ -160,7 +162,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formats = allFormats.first().toMutableList()
|
formats = allFormats.first().toMutableList()
|
||||||
uiUtil.populateFormatCard(formatCard, item.first())
|
uiUtil.populateFormatCard(formatCard, item.first(), item.drop(1).map { it.format_note })
|
||||||
|
downloadItem.format.container = container.editText?.text.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formatCard.setOnClickListener{
|
formatCard.setOnClickListener{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
|
@ -22,6 +22,8 @@ import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.facebook.shimmer.ShimmerFrameLayout
|
import com.facebook.shimmer.ShimmerFrameLayout
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -36,6 +38,14 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private lateinit var formatCollection: MutableList<List<Format>>
|
private lateinit var formatCollection: MutableList<List<Format>>
|
||||||
private lateinit var chosenFormats: List<Format>
|
private lateinit var chosenFormats: List<Format>
|
||||||
|
private lateinit var selectedVideo : Format
|
||||||
|
private lateinit var selectedAudios : MutableList<Format>
|
||||||
|
|
||||||
|
private lateinit var videoFormatList : LinearLayout
|
||||||
|
private lateinit var audioFormatList : LinearLayout
|
||||||
|
private lateinit var okBtn : Button
|
||||||
|
private lateinit var videoTitle : TextView
|
||||||
|
private lateinit var audioTitle : TextView
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -44,6 +54,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
infoUtil = InfoUtil(requireActivity().applicationContext)
|
infoUtil = InfoUtil(requireActivity().applicationContext)
|
||||||
formatCollection = mutableListOf()
|
formatCollection = mutableListOf()
|
||||||
chosenFormats = listOf()
|
chosenFormats = listOf()
|
||||||
|
selectedAudios = mutableListOf()
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,8 +72,15 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
behavior.peekHeight = displayMetrics.heightPixels / 2
|
behavior.peekHeight = displayMetrics.heightPixels / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
val linearLayout = view.findViewById<LinearLayout>(R.id.format_list_linear_layout)
|
val formatListLinearLayout = view.findViewById<LinearLayout>(R.id.format_list_linear_layout)
|
||||||
val shimmers = view.findViewById<ShimmerFrameLayout>(R.id.format_list_shimmer)
|
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
|
shimmers.visibility = View.GONE
|
||||||
val hasGenericFormats = when(items.first()!!.type){
|
val hasGenericFormats = when(items.first()!!.type){
|
||||||
Type.audio -> formats.first().size == resources.getStringArray(R.array.audio_formats).size
|
Type.audio -> formats.first().size == resources.getStringArray(R.array.audio_formats).size
|
||||||
|
|
@ -87,16 +105,15 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
}else{
|
}else{
|
||||||
chosenFormats = formats.flatten()
|
chosenFormats = formats.flatten()
|
||||||
}
|
}
|
||||||
addFormatsToView(linearLayout)
|
addFormatsToView()
|
||||||
}else{
|
}else{
|
||||||
chosenFormats = formats.flatten()
|
chosenFormats = formats.flatten()
|
||||||
if(!hasGenericFormats){
|
if(!hasGenericFormats){
|
||||||
chosenFormats = when(items.first()?.type){
|
if(items.first()?.type == Type.audio){
|
||||||
Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
chosenFormats = chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
else -> chosenFormats.filter { !it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addFormatsToView(linearLayout)
|
addFormatsToView()
|
||||||
}
|
}
|
||||||
|
|
||||||
val refreshBtn = view.findViewById<Button>(R.id.format_refresh)
|
val refreshBtn = view.findViewById<Button>(R.id.format_refresh)
|
||||||
|
|
@ -107,7 +124,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
refreshBtn.isEnabled = false
|
refreshBtn.isEnabled = false
|
||||||
linearLayout.visibility = View.GONE
|
formatListLinearLayout.visibility = View.GONE
|
||||||
shimmers.visibility = View.VISIBLE
|
shimmers.visibility = View.VISIBLE
|
||||||
shimmers.startShimmer()
|
shimmers.startShimmer()
|
||||||
|
|
||||||
|
|
@ -117,9 +134,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
infoUtil.getFormats(items.first()!!.url)
|
infoUtil.getFormats(items.first()!!.url)
|
||||||
}
|
}
|
||||||
chosenFormats = res.formats.filter { it.filesize != 0L }
|
chosenFormats = res.formats.filter { it.filesize != 0L }
|
||||||
chosenFormats = when(items.first()?.type){
|
if(items.first()?.type == Type.audio){
|
||||||
Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
chosenFormats = chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
else -> chosenFormats.filter { !it.format_note.contains("audio", ignoreCase = true) }
|
|
||||||
}
|
}
|
||||||
if (chosenFormats.isEmpty()) throw Exception()
|
if (chosenFormats.isEmpty()) throw Exception()
|
||||||
//playlist format filtering
|
//playlist format filtering
|
||||||
|
|
@ -150,18 +166,16 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
.sumOf { itt -> itt.filesize }
|
.sumOf { itt -> itt.filesize }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addFormatsToView(linearLayout)
|
|
||||||
refreshBtn.visibility = View.GONE
|
|
||||||
|
|
||||||
linearLayout.visibility = View.VISIBLE
|
|
||||||
shimmers.visibility = View.GONE
|
shimmers.visibility = View.GONE
|
||||||
shimmers.stopShimmer()
|
shimmers.stopShimmer()
|
||||||
|
addFormatsToView()
|
||||||
|
refreshBtn.visibility = View.GONE
|
||||||
|
formatListLinearLayout.visibility = View.VISIBLE
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
runCatching {
|
runCatching {
|
||||||
refreshBtn.isEnabled = true
|
refreshBtn.isEnabled = true
|
||||||
refreshBtn.text = getString(R.string.update_formats)
|
refreshBtn.text = getString(R.string.update_formats)
|
||||||
linearLayout.visibility = View.VISIBLE
|
formatListLinearLayout.visibility = View.VISIBLE
|
||||||
shimmers.visibility = View.GONE
|
shimmers.visibility = View.GONE
|
||||||
shimmers.stopShimmer()
|
shimmers.stopShimmer()
|
||||||
|
|
||||||
|
|
@ -171,38 +185,96 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
okBtn.setOnClickListener {
|
||||||
|
val selectedFormats = mutableListOf<Format>()
|
||||||
|
selectedFormats.add(selectedVideo)
|
||||||
|
selectedFormats.addAll(selectedAudios)
|
||||||
|
listener.onFormatClick(List(items.size){chosenFormats}, selectedFormats)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("update_formats", false) && refreshBtn.isVisible && items.size == 1){
|
if (sharedPreferences.getBoolean("update_formats", false) && refreshBtn.isVisible && items.size == 1){
|
||||||
refreshBtn.performClick()
|
refreshBtn.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun addFormatsToView(linearLayout: LinearLayout){
|
private fun addFormatsToView(){
|
||||||
linearLayout.removeAllViews()
|
val isSingleAndVideo = items.first()?.type == Type.video && items.count() == 1
|
||||||
|
videoFormatList.removeAllViews()
|
||||||
|
audioFormatList.removeAllViews()
|
||||||
|
|
||||||
|
if (!isSingleAndVideo) {
|
||||||
|
audioFormatList.visibility = View.GONE
|
||||||
|
videoTitle.visibility = View.GONE
|
||||||
|
audioTitle.visibility = View.GONE
|
||||||
|
okBtn.visibility = View.GONE
|
||||||
|
}else{
|
||||||
|
if (chosenFormats.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (i in chosenFormats.lastIndex downTo 0){
|
for (i in chosenFormats.lastIndex downTo 0){
|
||||||
val format = chosenFormats[i]
|
val format = chosenFormats[i]
|
||||||
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
||||||
uiUtil.populateFormatCard(formatItem as ConstraintLayout, format)
|
formatItem.tag = "${format.format_id}${format.format_note}"
|
||||||
formatItem.setOnClickListener{_ ->
|
uiUtil.populateFormatCard(formatItem as MaterialCardView, format, null)
|
||||||
if (items.size == 1){
|
formatItem.setOnClickListener{ clickedformat ->
|
||||||
listener.onFormatClick(List(items.size){chosenFormats}, listOf(format))
|
//if the context is behind a single download card and its a video, allow the ability to multiselect audio formats
|
||||||
}else{
|
if (isSingleAndVideo){
|
||||||
val selectedFormats = mutableListOf<Format>()
|
val clickedCard = (clickedformat as MaterialCardView)
|
||||||
formatCollection.forEach {
|
if (format.vcodec.isNotBlank() && format.vcodec != "none") {
|
||||||
selectedFormats.add(it.first{ f -> f.format_id == format.format_id})
|
videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
|
||||||
}
|
selectedVideo = format
|
||||||
if (selectedFormats.isEmpty()) {
|
clickedCard.isChecked = true
|
||||||
items.forEach {
|
}else{
|
||||||
selectedFormats.add(format)
|
if(selectedAudios.contains(format)) {
|
||||||
|
selectedAudios.remove(format)
|
||||||
|
} else {
|
||||||
|
selectedAudios.add(format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.onFormatClick(formatCollection, selectedFormats)
|
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(List(items.size){chosenFormats}, listOf(format))
|
||||||
|
}else{
|
||||||
|
val selectedFormats = mutableListOf<Format>()
|
||||||
|
formatCollection.forEach {
|
||||||
|
selectedFormats.add(it.first{ f -> f.format_id == format.format_id})
|
||||||
|
}
|
||||||
|
if (selectedFormats.isEmpty()) {
|
||||||
|
items.forEach {
|
||||||
|
selectedFormats.add(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listener.onFormatClick(formatCollection, selectedFormats)
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
formatItem.setOnLongClickListener {
|
formatItem.setOnLongClickListener {
|
||||||
uiUtil.showFormatDetails(format, requireActivity())
|
uiUtil.showFormatDetails(format, requireActivity())
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
linearLayout.addView(formatItem)
|
|
||||||
|
if (isSingleAndVideo){
|
||||||
|
if (format.vcodec.isNotBlank() && format.vcodec != "none") videoFormatList.addView(formatItem)
|
||||||
|
else audioFormatList.addView(formatItem)
|
||||||
|
}else{
|
||||||
|
videoFormatList.addView(formatItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,16 @@ import android.os.Bundle
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -55,6 +59,8 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
private lateinit var queuedRecyclerView : RecyclerView
|
private lateinit var queuedRecyclerView : RecyclerView
|
||||||
private lateinit var queuedDownloads : GenericDownloadAdapter
|
private lateinit var queuedDownloads : GenericDownloadAdapter
|
||||||
private lateinit var notificationUtil: NotificationUtil
|
private lateinit var notificationUtil: NotificationUtil
|
||||||
|
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||||
|
private var actionMode : ActionMode? = null
|
||||||
private lateinit var items : MutableList<DownloadItem>
|
private lateinit var items : MutableList<DownloadItem>
|
||||||
private lateinit var fileUtil: FileUtil
|
private lateinit var fileUtil: FileUtil
|
||||||
private lateinit var uiUtil: UiUtil
|
private lateinit var uiUtil: UiUtil
|
||||||
|
|
@ -70,6 +76,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
notificationUtil = NotificationUtil(requireContext())
|
notificationUtil = NotificationUtil(requireContext())
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
items = mutableListOf()
|
items = mutableListOf()
|
||||||
|
selectedObjects = arrayListOf()
|
||||||
fileUtil = FileUtil()
|
fileUtil = FileUtil()
|
||||||
uiUtil = UiUtil(fileUtil)
|
uiUtil = UiUtil(fileUtil)
|
||||||
return fragmentView
|
return fragmentView
|
||||||
|
|
@ -226,7 +233,23 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||||
TODO("Not yet implemented")
|
val item = items.find { it.id == itemID }
|
||||||
|
if (isChecked) {
|
||||||
|
selectedObjects!!.add(item!!)
|
||||||
|
if (actionMode == null){
|
||||||
|
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
|
||||||
|
}else{
|
||||||
|
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedObjects!!.remove(item)
|
||||||
|
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
if (selectedObjects!!.isEmpty()){
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeItem(itemID: Long){
|
private fun removeItem(itemID: Long){
|
||||||
|
|
@ -251,6 +274,76 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
deleteDialog.show()
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
mode!!.menuInflater.inflate(R.menu.queued_menu_context, menu)
|
||||||
|
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(
|
||||||
|
mode: ActionMode?,
|
||||||
|
menu: Menu?
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(
|
||||||
|
mode: ActionMode?,
|
||||||
|
item: MenuItem?
|
||||||
|
): Boolean {
|
||||||
|
return when (item!!.itemId) {
|
||||||
|
R.id.delete_results -> {
|
||||||
|
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||||
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
|
for (obj in selectedObjects!!){
|
||||||
|
val id = obj.id.toInt()
|
||||||
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
|
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
|
downloadViewModel.deleteDownload(obj)
|
||||||
|
}
|
||||||
|
clearCheckedItems()
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.select_all -> {
|
||||||
|
queuedDownloads.checkAll(items)
|
||||||
|
selectedObjects?.clear()
|
||||||
|
items.forEach { selectedObjects?.add(it) }
|
||||||
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.invert_selected -> {
|
||||||
|
queuedDownloads.invertSelected(items)
|
||||||
|
val invertedList = arrayListOf<DownloadItem>()
|
||||||
|
items.forEach {
|
||||||
|
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||||
|
}
|
||||||
|
selectedObjects?.clear()
|
||||||
|
selectedObjects?.addAll(invertedList)
|
||||||
|
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
actionMode = null
|
||||||
|
clearCheckedItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearCheckedItems(){
|
||||||
|
queuedDownloads.clearCheckeditems()
|
||||||
|
selectedObjects?.clear()
|
||||||
|
}
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
private lateinit var downloadViewModel: DownloadViewModel
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
private lateinit var cookieViewModel: CookieViewModel
|
private lateinit var cookieViewModel: CookieViewModel
|
||||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||||
|
|
||||||
|
private var version: Preference? = null
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||||
val navController = findNavController()
|
val navController = findNavController()
|
||||||
|
|
@ -192,6 +195,24 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
appRestoreResultLauncher.launch(intent)
|
appRestoreResultLauncher.launch(intent)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version = findPreference("version")
|
||||||
|
version!!.summary = BuildConfig.VERSION_NAME
|
||||||
|
|
||||||
|
version!!.onPreferenceClickListener =
|
||||||
|
Preference.OnPreferenceClickListener {
|
||||||
|
lifecycleScope.launch{
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
updateUtil!!.updateApp{ msg ->
|
||||||
|
lifecycleScope.launch(Dispatchers.Main){
|
||||||
|
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun backupSettings(preferences: SharedPreferences) : JsonArray {
|
private fun backupSettings(preferences: SharedPreferences) : JsonArray {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
||||||
override val title: Int = R.string.updating
|
override val title: Int = R.string.updating
|
||||||
private var updateYTDL: Preference? = null
|
private var updateYTDL: Preference? = null
|
||||||
private var ytdlVersion: Preference? = null
|
private var ytdlVersion: Preference? = null
|
||||||
private var version: Preference? = null
|
|
||||||
private var updateUtil: UpdateUtil? = null
|
private var updateUtil: UpdateUtil? = null
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,8 +29,6 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
||||||
val editor = preferences.edit()
|
val editor = preferences.edit()
|
||||||
updateYTDL = findPreference("update_ytdl")
|
updateYTDL = findPreference("update_ytdl")
|
||||||
ytdlVersion = findPreference("ytdl-version")
|
ytdlVersion = findPreference("ytdl-version")
|
||||||
version = findPreference("version")
|
|
||||||
version!!.summary = BuildConfig.VERSION_NAME
|
|
||||||
|
|
||||||
YoutubeDL.getInstance().version(context)?.let {
|
YoutubeDL.getInstance().version(context)?.let {
|
||||||
editor.putString("ytdl-version", it)
|
editor.putString("ytdl-version", it)
|
||||||
|
|
@ -69,20 +66,6 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
version!!.onPreferenceClickListener =
|
|
||||||
Preference.OnPreferenceClickListener {
|
|
||||||
lifecycleScope.launch{
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
updateUtil!!.updateApp{ msg ->
|
|
||||||
lifecycleScope.launch(Dispatchers.Main){
|
|
||||||
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,26 @@ import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.google.common.net.HttpHeaders.USER_AGENT
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
|
||||||
class InfoUtil(private val context: Context) {
|
class InfoUtil(private val context: Context) {
|
||||||
private var items: ArrayList<ResultItem?>
|
private var items: ArrayList<ResultItem?>
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
|
@ -42,16 +48,10 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
private fun init(){
|
private fun init(){
|
||||||
if (countryCODE.isEmpty()){
|
if (countryCODE.isEmpty()){
|
||||||
val country = genericRequest("https://ipwho.is/")
|
countryCODE = "US"
|
||||||
countryCODE = try {
|
|
||||||
country.getString("country_code")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
"US"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!useInvidous){
|
if (!useInvidous){
|
||||||
val res = genericRequest(invidousURL + "stats")
|
val res = genericRequest(invidousURL + "stats")
|
||||||
Log.e("Assa", res.toString())
|
|
||||||
useInvidous = res.length() != 0
|
useInvidous = res.length() != 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,14 +235,10 @@ class InfoUtil(private val context: Context) {
|
||||||
try {
|
try {
|
||||||
init()
|
init()
|
||||||
if (key!!.isEmpty()) {
|
if (key!!.isEmpty()) {
|
||||||
return if (useInvidous) {
|
val res = genericRequest(pipedURL + "streams/" + id)
|
||||||
val res = genericRequest(invidousURL + "videos/" + id)
|
return if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideoFromPipedJSON(
|
||||||
if (res.length() == 0) getFromYTDL("https://www.youtube.com/watch?v=$id")[0] else createVideoFromInvidiousJSON(
|
res, id
|
||||||
res
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
getFromYTDL("https://www.youtube.com/watch?v=$id")[0]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//short data
|
//short data
|
||||||
|
|
@ -345,6 +341,82 @@ class InfoUtil(private val context: Context) {
|
||||||
return video
|
return video
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createVideoFromPipedJSON(obj: JSONObject, id: String): ResultItem? {
|
||||||
|
var video: ResultItem? = null
|
||||||
|
try {
|
||||||
|
val title = obj.getString("title").toString()
|
||||||
|
title.replace("'s", "'")
|
||||||
|
title.replace("&", "&")
|
||||||
|
val author = obj.getString("uploader").toString()
|
||||||
|
author.replace("'s", "'")
|
||||||
|
author.replace("&", "&")
|
||||||
|
|
||||||
|
if (author.isBlank()) throw Exception()
|
||||||
|
|
||||||
|
val duration = formatIntegerDuration(obj.getInt("duration"), Locale.getDefault())
|
||||||
|
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
||||||
|
val url = "https://www.youtube.com/watch?v=$id"
|
||||||
|
val formats : ArrayList<Format> = ArrayList()
|
||||||
|
if (obj.has("audioStreams")){
|
||||||
|
val formatsInJSON = obj.getJSONArray("audioStreams")
|
||||||
|
for (f in 0 until formatsInJSON.length()){
|
||||||
|
val format = formatsInJSON.getJSONObject(f)
|
||||||
|
if (format.getInt("bitrate") == 0) continue
|
||||||
|
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
|
try{
|
||||||
|
formatObj.acodec = format.getString("codec")
|
||||||
|
val streamURL = format.getString("url")
|
||||||
|
formatObj.format_id = streamURL.substringAfter("itag=").substringBefore("&")
|
||||||
|
formatObj.filesize = (format.getInt("bitrate") / 8 * obj.getInt("duration")).toLong()
|
||||||
|
formatObj.asr = format.getString("quality")
|
||||||
|
if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){
|
||||||
|
formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note
|
||||||
|
}else{
|
||||||
|
formatObj.format_note = formatObj.format_note + " Audio"
|
||||||
|
}
|
||||||
|
|
||||||
|
}catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
formats.add(formatObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.has("videoStreams")){
|
||||||
|
val formatsInJSON = obj.getJSONArray("videoStreams")
|
||||||
|
for (f in 0 until formatsInJSON.length()){
|
||||||
|
val format = formatsInJSON.getJSONObject(f)
|
||||||
|
if (format.getInt("bitrate") == 0) continue
|
||||||
|
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
|
try{
|
||||||
|
formatObj.vcodec = format.getString("codec")
|
||||||
|
val streamURL = format.getString("url")
|
||||||
|
formatObj.format_id = streamURL.substringAfter("itag=").substringBefore("&")
|
||||||
|
formatObj.filesize = (format.getInt("bitrate") / 8 * obj.getInt("duration")).toLong()
|
||||||
|
}catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
formats.add(formatObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formats.sortBy { it.filesize }
|
||||||
|
|
||||||
|
video = ResultItem(0,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
author,
|
||||||
|
duration,
|
||||||
|
thumb,
|
||||||
|
"youtube",
|
||||||
|
"",
|
||||||
|
formats
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
}
|
||||||
|
return video
|
||||||
|
}
|
||||||
|
|
||||||
private fun genericRequest(url: String): JSONObject {
|
private fun genericRequest(url: String): JSONObject {
|
||||||
Log.e(TAG, url)
|
Log.e(TAG, url)
|
||||||
val reader: BufferedReader
|
val reader: BufferedReader
|
||||||
|
|
@ -507,12 +579,12 @@ class InfoUtil(private val context: Context) {
|
||||||
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
||||||
val m = p.matcher(url)
|
val m = p.matcher(url)
|
||||||
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
||||||
return if(m.find() && useInvidous && formatSource == "invidious"){
|
return if(m.find() && formatSource == "piped"){
|
||||||
val id = getIDFromYoutubeURL(url)
|
val id = getIDFromYoutubeURL(url)
|
||||||
val res = genericRequest(invidousURL + "videos/" + id)
|
val res = genericRequest(pipedURL + "streams/" + id)
|
||||||
if (res.length() == 0) getFromYTDL(url)[0]!!
|
if (res.length() == 0) getFromYTDL(url)[0]!!
|
||||||
else createVideoFromInvidiousJSON(
|
else createVideoFromPipedJSON(
|
||||||
res
|
res, id
|
||||||
)!!
|
)!!
|
||||||
}else{
|
}else{
|
||||||
getFromYTDL(url)[0]!!
|
getFromYTDL(url)[0]!!
|
||||||
|
|
@ -583,8 +655,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}else{
|
}else{
|
||||||
urls.forEach {
|
urls.forEach {
|
||||||
val id = getIDFromYoutubeURL(it)
|
val id = getIDFromYoutubeURL(it)
|
||||||
val res = genericRequest(invidousURL + "videos/" + id)
|
val res = genericRequest(pipedURL + "streams/" + id)
|
||||||
val vid = createVideoFromInvidiousJSON(res)
|
val vid = createVideoFromPipedJSON(res, id)
|
||||||
progress(vid!!.formats)
|
progress(vid!!.formats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -881,6 +953,7 @@ class InfoUtil(private val context: Context) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "API MANAGER"
|
private const val TAG = "API MANAGER"
|
||||||
private const val invidousURL = "https://invidious.nerdvpn.de/api/v1/"
|
private const val invidousURL = "https://invidious.nerdvpn.de/api/v1/"
|
||||||
|
private const val pipedURL = "https://pipedapi.kavin.rocks/"
|
||||||
private var countryCODE: String = ""
|
private var countryCODE: String = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.util
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
|
|
@ -30,6 +31,7 @@ import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.datepicker.CalendarConstraints
|
import com.google.android.material.datepicker.CalendarConstraints
|
||||||
|
|
@ -44,9 +46,15 @@ import java.io.File
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
class UiUtil(private val fileUtil: FileUtil) {
|
class UiUtil(private val fileUtil: FileUtil) {
|
||||||
fun populateFormatCard(formatCard : ConstraintLayout, chosenFormat: Format){
|
@SuppressLint("SetTextI18n")
|
||||||
|
fun populateFormatCard(formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<String>?){
|
||||||
formatCard.findViewById<TextView>(R.id.container).text = chosenFormat.container.uppercase()
|
formatCard.findViewById<TextView>(R.id.container).text = chosenFormat.container.uppercase()
|
||||||
formatCard.findViewById<TextView>(R.id.format_note).text = chosenFormat.format_note.uppercase()
|
if (audioFormats.isNullOrEmpty()){
|
||||||
|
formatCard.findViewById<TextView>(R.id.format_note).text = chosenFormat.format_note.uppercase()
|
||||||
|
}else{
|
||||||
|
val title = "${chosenFormat.format_note.uppercase()} + [${audioFormats.joinToString("/")}]"
|
||||||
|
formatCard.findViewById<TextView>(R.id.format_note).text = title
|
||||||
|
}
|
||||||
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
||||||
val codec =
|
val codec =
|
||||||
if (chosenFormat.encoding != "") {
|
if (chosenFormat.encoding != "") {
|
||||||
|
|
|
||||||
|
|
@ -221,10 +221,13 @@ class DownloadWorker(
|
||||||
request.addOption("--embed-chapters")
|
request.addOption("--embed-chapters")
|
||||||
}
|
}
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
request.addOption("--embed-subs", "")
|
request.addOption("--embed-subs")
|
||||||
|
request.addOption("--sub-langs", "en.*,.*-orig")
|
||||||
}
|
}
|
||||||
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
|
val defaultFormats = context.resources.getStringArray(R.array.video_formats)
|
||||||
|
|
||||||
|
if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()) request.addOption("--audio-multistreams")
|
||||||
|
|
||||||
var videoFormatID = downloadItem.format.format_id
|
var videoFormatID = downloadItem.format.format_id
|
||||||
Log.e(TAG, videoFormatID)
|
Log.e(TAG, videoFormatID)
|
||||||
var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best"
|
var formatArgument = if (downloadItem.videoPreferences.removeAudio) "bestvideo" else "bestvideo+bestaudio/best"
|
||||||
|
|
@ -232,13 +235,19 @@ class DownloadWorker(
|
||||||
if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo"
|
if (videoFormatID == context.resources.getString(R.string.best_quality) || videoFormatID == "best") videoFormatID = "bestvideo"
|
||||||
else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst"
|
else if (videoFormatID == context.resources.getString(R.string.worst_quality) || videoFormatID == "worst") videoFormatID = "worst"
|
||||||
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
|
else if (defaultFormats.contains(videoFormatID)) videoFormatID = "bestvideo[height<="+videoFormatID.substring(0, videoFormatID.length -1)+"]"
|
||||||
if (!downloadItem.videoPreferences.removeAudio) formatArgument = "$videoFormatID+bestaudio/best/$videoFormatID"
|
|
||||||
|
formatArgument = if (downloadItem.videoPreferences.audioFormatIDs.isNotEmpty()){
|
||||||
|
val audioIds = downloadItem.videoPreferences.audioFormatIDs.joinToString("+")
|
||||||
|
"$videoFormatID+$audioIds/best/$videoFormatID"
|
||||||
|
}else{
|
||||||
|
"$videoFormatID+bestaudio/best/$videoFormatID"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.e(TAG, formatArgument)
|
Log.e(TAG, formatArgument)
|
||||||
request.addOption("-f", formatArgument)
|
request.addOption("-f", formatArgument)
|
||||||
val format = downloadItem.format.container
|
val format = downloadItem.format.container
|
||||||
if(format.isNotEmpty() && format != "Default" && format != context.getString(R.string.defaultValue)){
|
if(format.isNotEmpty() && format != "Default" && format != context.getString(R.string.defaultValue)){
|
||||||
request.addOption("--merge-output-format", format)
|
request.addOption("--merge-output-format", format.lowercase())
|
||||||
if (format != "webm") {
|
if (format != "webm") {
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||||
if (embedThumb) {
|
if (embedThumb) {
|
||||||
|
|
@ -324,6 +333,9 @@ class DownloadWorker(
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao, false, notificationUtil)
|
||||||
|
|
||||||
//put download in history
|
//put download in history
|
||||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||||
if (!incognito) {
|
if (!incognito) {
|
||||||
|
|
@ -342,7 +354,7 @@ class DownloadWorker(
|
||||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
if (updateDownloadItem(downloadItem, infoUtil, dao, resultDao, false, notificationUtil)){
|
if (wasQuickDownloaded){
|
||||||
runCatching {
|
runCatching {
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false))
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|
|
||||||
5
app/src/main/res/drawable/ic_search_accent.xml
Normal file
5
app/src/main/res/drawable/ic_search_accent.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -187,7 +187,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="10dp"
|
android:layout_marginEnd="10dp"
|
||||||
app:backgroundTint="?attr/colorSurface"
|
app:backgroundTint="?attr/colorSurface"
|
||||||
app:cornerRadius="10dp"
|
app:cornerRadius="15dp"
|
||||||
app:icon="@drawable/ic_cancel"
|
app:icon="@drawable/ic_cancel"
|
||||||
app:iconSize="30dp"
|
app:iconSize="30dp"
|
||||||
app:iconTint="?android:textColorPrimary"
|
app:iconTint="?android:textColorPrimary"
|
||||||
|
|
@ -199,7 +199,7 @@
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/output"
|
android:id="@+id/output"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="50dp"
|
android:layout_height="40dp"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
|
|
|
||||||
|
|
@ -1,113 +1,136 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
<com.google.android.material.card.MaterialCardView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/format_card_constraintLayout"
|
android:id="@+id/format_card_constraintLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:clickable="true"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingVertical="5dp"
|
|
||||||
android:paddingHorizontal="10dp"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:focusable="true">
|
android:checkable="true"
|
||||||
|
android:clickable="true"
|
||||||
<TextView
|
android:focusable="true"
|
||||||
android:id="@+id/container"
|
android:backgroundTint="@android:color/transparent"
|
||||||
android:layout_width="60dp"
|
app:checkedIcon="@null"
|
||||||
android:background="@drawable/rounded_corner"
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||||
android:backgroundTint="?attr/colorPrimaryInverse"
|
app:strokeWidth="0dp"
|
||||||
android:textColor="@color/white"
|
app:cardPreventCornerOverlap="true"
|
||||||
android:clickable="false"
|
android:layout_height="wrap_content">
|
||||||
android:layout_height="55dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:minWidth="30dp"
|
|
||||||
app:cornerRadius="10dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:paddingVertical="5dp"
|
||||||
android:orientation="vertical"
|
android:paddingHorizontal="10dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_height="wrap_content">
|
||||||
app:layout_constraintStart_toEndOf="@+id/container"
|
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/format_note"
|
android:id="@+id/container"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="60dp"
|
||||||
android:layout_height="wrap_content"
|
android:background="@drawable/rounded_corner"
|
||||||
android:layout_marginStart="10dp"
|
android:backgroundTint="?attr/colorPrimaryInverse"
|
||||||
android:textSize="20sp"
|
android:textColor="@color/white"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_height="55dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:orientation="vertical"
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/format_note"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/container"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/codec"
|
android:id="@+id/format_note"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
android:maxLines="2"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLength="17"
|
android:textSize="20sp"
|
||||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="5dp"
|
|
||||||
android:background="@drawable/rounded_corner"
|
|
||||||
android:backgroundTint="?attr/colorSecondary"
|
|
||||||
android:clickable="false"
|
|
||||||
android:gravity="center"
|
|
||||||
android:minWidth="30dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:paddingHorizontal="5dp"
|
|
||||||
app:cornerRadius="10dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/format_note"
|
||||||
|
app:layout_constraintVertical_bias="1.0">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/codec"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorSecondary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLength="17"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/file_size"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorSecondary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:gravity="center"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/file_size"
|
android:id="@+id/format_id"
|
||||||
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:layout_marginEnd="5dp"
|
|
||||||
android:background="@drawable/rounded_corner"
|
|
||||||
android:backgroundTint="?attr/colorSecondary"
|
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:gravity="center"
|
android:gravity="bottom|end"
|
||||||
|
android:maxLength="10"
|
||||||
android:minWidth="30dp"
|
android:minWidth="30dp"
|
||||||
android:textStyle="bold"
|
|
||||||
android:paddingHorizontal="5dp"
|
|
||||||
app:cornerRadius="10dp"
|
app:cornerRadius="10dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/linearLayout"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/format_note" />
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/format_id"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="55dp"
|
|
||||||
android:clickable="false"
|
|
||||||
android:gravity="bottom|end"
|
|
||||||
android:maxLength="10"
|
|
||||||
android:minWidth="30dp"
|
|
||||||
app:cornerRadius="10dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
@ -3,13 +3,14 @@
|
||||||
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"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="20dp"
|
android:paddingBottom="10dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent">
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
|
@ -40,6 +41,18 @@
|
||||||
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/format_ok"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autoLink="all"
|
||||||
|
android:text="@string/ok"
|
||||||
|
app:icon="@drawable/ic_check"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/format_refresh"
|
android:id="@+id/format_refresh"
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||||
|
|
@ -57,9 +70,9 @@
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:scrollbars="none"
|
android:layout_height="wrap_content"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:scrollbars="none">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/format_list_linear_layout"
|
android:id="@+id/format_list_linear_layout"
|
||||||
|
|
@ -67,6 +80,46 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/video_linear_layout_main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/video_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:text="@string/video" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/video_linear_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/audio_linear_layout_main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/audio_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:text="@string/audio" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/audio_linear_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
@ -75,10 +128,10 @@
|
||||||
<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:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp">
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -101,5 +154,4 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
22
app/src/main/res/menu/queued_menu_context.xml
Normal file
22
app/src/main/res/menu/queued_menu_context.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".MainActivity" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/delete_results"
|
||||||
|
android:title="@string/remove_results"
|
||||||
|
android:icon="@drawable/baseline_delete_24"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/select_all"
|
||||||
|
android:title="@string/select_all"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/invert_selected"
|
||||||
|
android:title="@string/invert_selected"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
|
@ -133,12 +133,12 @@
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="formats_source">
|
<array name="formats_source">
|
||||||
<item>Invidious API [Youtube]</item>
|
<item>PIPED [Youtube]</item>
|
||||||
<item>yt-dlp</item>
|
<item>yt-dlp</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="formats_source_values">
|
<array name="formats_source_values">
|
||||||
<item>invidious</item>
|
<item>piped</item>
|
||||||
<item>yt-dlp</item>
|
<item>yt-dlp</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -262,4 +262,7 @@
|
||||||
<string name="backup_restore">Backup And Restore</string>
|
<string name="backup_restore">Backup And Restore</string>
|
||||||
<string name="socks5_proxy_summary">Use the specified HTTP/HTTPS/SOCKS proxy</string>
|
<string name="socks5_proxy_summary">Use the specified HTTP/HTTPS/SOCKS proxy</string>
|
||||||
<string name="socks5_proxy">Socks5 Proxy URL</string>
|
<string name="socks5_proxy">Socks5 Proxy URL</string>
|
||||||
|
<string name="search_suggestions">Search Suggestions</string>
|
||||||
|
<string name="search_suggestions_summary">Get search suggestions from google</string>
|
||||||
|
<string name="preferred_format_id_summary">Selec the format with this ID in the download card</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -64,6 +64,14 @@
|
||||||
app:summary="@string/preferred_search_engine_summary"
|
app:summary="@string/preferred_search_engine_summary"
|
||||||
app:title="@string/preferred_search_engine" />
|
app:title="@string/preferred_search_engine" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_search_accent"
|
||||||
|
android:key="search_suggestions"
|
||||||
|
app:summary="@string/search_suggestions_summary"
|
||||||
|
app:title="@string/search_suggestions" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue=""
|
android:defaultValue=""
|
||||||
android:entries="@array/countries"
|
android:entries="@array/countries"
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,6 @@
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="format_id"
|
app:key="format_id"
|
||||||
app:defaultValue=""
|
app:defaultValue=""
|
||||||
app:useSimpleSummaryProvider="true"
|
app:summary="@string/preferred_format_id_summary"
|
||||||
app:title="@string/preferred_format_id" />
|
app:title="@string/preferred_format_id" />
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -80,6 +80,11 @@
|
||||||
android:data="https://github.com/deniscerri/ytdlnis" />
|
android:data="https://github.com/deniscerri/ytdlnis" />
|
||||||
</Preference>
|
</Preference>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:icon="@drawable/ic_info"
|
||||||
|
app:key="version"
|
||||||
|
app:title="@string/version"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,4 @@
|
||||||
android:key="update_app"
|
android:key="update_app"
|
||||||
app:summary="@string/update_app_summary"
|
app:summary="@string/update_app_summary"
|
||||||
app:title="@string/update_app" />
|
app:title="@string/update_app" />
|
||||||
<Preference
|
|
||||||
app:icon="@drawable/ic_info"
|
|
||||||
app:key="version"
|
|
||||||
app:title="@string/version"/>
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
Loading…
Reference in a new issue