1.7.9
This commit is contained in:
parent
6ba8d65afd
commit
d57ab39e82
26 changed files with 378 additions and 199 deletions
|
|
@ -10,7 +10,7 @@ plugins {
|
||||||
def properties = new Properties()
|
def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 7
|
def versionMinor = 7
|
||||||
def versionPatch = 8
|
def versionPatch = 9
|
||||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||||
def isBeta = false
|
def isBeta = false
|
||||||
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ interface DownloadDao {
|
||||||
""")
|
""")
|
||||||
fun getProcessingDownloadTypes() : List<String>
|
fun getProcessingDownloadTypes() : List<String>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT DISTINCT container from downloads where status = 'Processing'
|
||||||
|
""")
|
||||||
|
fun getProcessingDownloadContainers() : List<String>
|
||||||
|
|
||||||
|
|
||||||
@Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)")
|
@Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)")
|
||||||
suspend fun updateItemsToProcessing(ids: List<Long>)
|
suspend fun updateItemsToProcessing(ids: List<Long>)
|
||||||
|
|
@ -60,6 +65,9 @@ interface DownloadDao {
|
||||||
@Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'")
|
@Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'")
|
||||||
suspend fun updateProcessingDownloadPath(path: String)
|
suspend fun updateProcessingDownloadPath(path: String)
|
||||||
|
|
||||||
|
@Query("UPDATE downloads set container=:cont WHERE status ='Processing'")
|
||||||
|
suspend fun updateProcessingContainer(cont: String)
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||||
fun getActiveDownloadsList() : List<DownloadItem>
|
fun getActiveDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdl.database.models.LogItem
|
import com.deniscerri.ytdl.database.models.LogItem
|
||||||
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
@ -41,10 +40,15 @@ interface LogDao {
|
||||||
suspend fun updateLogContent(l: String, id: Long)
|
suspend fun updateLogContent(l: String, id: Long)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun updateLog(line: String, id: Long) {
|
suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) {
|
||||||
val l = getByID(id) ?: return
|
val l = getByID(id) ?: return
|
||||||
val log = l.content ?: ""
|
var log = l.content ?: ""
|
||||||
updateLogContent(log.appendLineToLog(line), id)
|
if (resetLog) {
|
||||||
|
log = line.lines().joinToString("\n")
|
||||||
|
}else{
|
||||||
|
log += "\n${line}"
|
||||||
|
}
|
||||||
|
updateLogContent(log, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import androidx.room.Insert
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import com.deniscerri.ytdl.database.models.TerminalItem
|
import com.deniscerri.ytdl.database.models.TerminalItem
|
||||||
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
@ -29,10 +28,15 @@ interface TerminalDao {
|
||||||
@Query("SELECT * FROM terminalDownloads WHERE id=:id LIMIT 1")
|
@Query("SELECT * FROM terminalDownloads WHERE id=:id LIMIT 1")
|
||||||
fun getTerminalById(id: Long) : TerminalItem?
|
fun getTerminalById(id: Long) : TerminalItem?
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun updateLog(line: String, id: Long){
|
suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){
|
||||||
val t = getTerminalById(id) ?: return
|
val t = getTerminalById(id) ?: return
|
||||||
val log = t.log ?: ""
|
var log = t.log ?: ""
|
||||||
updateTerminalLog(log.appendLineToLog(line), id)
|
if (resetLog) {
|
||||||
|
log = line.lines().joinToString("\n")
|
||||||
|
}else{
|
||||||
|
log += "\n${line}"
|
||||||
|
}
|
||||||
|
updateTerminalLog(log, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ data class DownloadItemConfigureMultiple(
|
||||||
var title: String,
|
var title: String,
|
||||||
var thumb: String,
|
var thumb: String,
|
||||||
var duration: String,
|
var duration: String,
|
||||||
|
var container: String,
|
||||||
var format: Format,
|
var format: Format,
|
||||||
var allFormats: MutableList<Format>,
|
var allFormats: MutableList<Format>,
|
||||||
var audioPreferences: AudioPreferences,
|
var audioPreferences: AudioPreferences,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdl.database.repository
|
||||||
|
|
||||||
import com.deniscerri.ytdl.database.dao.LogDao
|
import com.deniscerri.ytdl.database.dao.LogDao
|
||||||
import com.deniscerri.ytdl.database.models.LogItem
|
import com.deniscerri.ytdl.database.models.LogItem
|
||||||
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class LogRepository(private val logDao: LogDao) {
|
class LogRepository(private val logDao: LogDao) {
|
||||||
|
|
@ -38,9 +37,9 @@ class LogRepository(private val logDao: LogDao) {
|
||||||
return logDao.getByID(id)
|
return logDao.getByID(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun update(line: String, id: Long){
|
suspend fun update(line: String, id: Long, resetLog: Boolean = false){
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
logDao.updateLog(line, id)
|
logDao.updateLog(line, id, resetLog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1015,6 +1015,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun updateProcessingContainer(cont: String) {
|
||||||
|
var container = ""
|
||||||
|
if (cont != resources.getString(R.string.defaultValue)) {
|
||||||
|
container = cont
|
||||||
|
}
|
||||||
|
dao.updateProcessingContainer(container)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateProcessingDownloadPath(path: String){
|
suspend fun updateProcessingDownloadPath(path: String){
|
||||||
dao.updateProcessingDownloadPath(path)
|
dao.updateProcessingDownloadPath(path)
|
||||||
}
|
}
|
||||||
|
|
@ -1111,6 +1119,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
return Pair(types.size == 1, Type.valueOf(types.first()))
|
return Pair(types.size == 1, Type.valueOf(types.first()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkIfAllProcessingItemsHaveSameContainer() : Pair<Boolean, String> {
|
||||||
|
val containers = dao.getProcessingDownloadContainers()
|
||||||
|
return Pair(containers.size == 1, containers.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun updateItemsWithIdsToProcessingStatus(ids: List<Long>) {
|
suspend fun updateItemsWithIdsToProcessingStatus(ids: List<Long>) {
|
||||||
repository.deleteProcessing()
|
repository.deleteProcessing()
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,14 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
return repository.getItemByURL(url)
|
return repository.getItemByURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllByURL(url: String) : List<ResultItem> {
|
||||||
|
return repository.getAllByURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getByID(id: Long) : ResultItem? {
|
||||||
|
return repository.getItemByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
fun addSearchQueryToHistory(query: String) = viewModelScope.launch(Dispatchers.IO) {
|
fun addSearchQueryToHistory(query: String) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
val allQueries = searchHistoryRepository.getAll()
|
val allQueries = searchHistoryRepository.getAll()
|
||||||
if (allQueries.none { it.query == query }){
|
if (allQueries.none { it.query == query }){
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdl.MainActivity
|
import com.deniscerri.ytdl.MainActivity
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
|
import com.deniscerri.ytdl.database.models.ResultItem
|
||||||
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||||
|
|
@ -185,13 +186,18 @@ class ShareActivity : BaseActivity() {
|
||||||
val command = intent.getStringExtra("COMMAND") ?: ""
|
val command = intent.getStringExtra("COMMAND") ?: ""
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var result = withContext(Dispatchers.IO){
|
val result: ResultItem
|
||||||
resultViewModel.getItemByURL(inputQuery)
|
val existingResults = withContext(Dispatchers.IO){
|
||||||
|
resultViewModel.getAllByURL(inputQuery)
|
||||||
}
|
}
|
||||||
if (result == null) {
|
|
||||||
|
if (existingResults.isEmpty() || existingResults.size > 1) {
|
||||||
resultViewModel.deleteAll()
|
resultViewModel.deleteAll()
|
||||||
result = downloadViewModel.createEmptyResultItem(inputQuery)
|
result = downloadViewModel.createEmptyResultItem(inputQuery)
|
||||||
|
}else{
|
||||||
|
result = existingResults.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadType = DownloadViewModel.Type.valueOf(type ?: downloadViewModel.getDownloadType(url = result.url).toString())
|
val downloadType = DownloadViewModel.Type.valueOf(type ?: downloadViewModel.getDownloadType(url = result.url).toString())
|
||||||
if (sharedPreferences.getBoolean("download_card", true) && !background){
|
if (sharedPreferences.getBoolean("download_card", true) && !background){
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import com.deniscerri.ytdl.ui.adapter.HomeAdapter
|
||||||
import com.deniscerri.ytdl.ui.adapter.SearchSuggestionsAdapter
|
import com.deniscerri.ytdl.ui.adapter.SearchSuggestionsAdapter
|
||||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdl.util.InfoUtil
|
import com.deniscerri.ytdl.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
import com.deniscerri.ytdl.util.ThemeUtil
|
import com.deniscerri.ytdl.util.ThemeUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.facebook.shimmer.ShimmerFrameLayout
|
import com.facebook.shimmer.ShimmerFrameLayout
|
||||||
|
|
@ -84,6 +85,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
private var clipboardFab: ExtendedFloatingActionButton? = null
|
private var clipboardFab: ExtendedFloatingActionButton? = null
|
||||||
private var homeFabs: LinearLayout? = null
|
private var homeFabs: LinearLayout? = null
|
||||||
private var infoUtil: InfoUtil? = null
|
private var infoUtil: InfoUtil? = null
|
||||||
|
private var notificationUtil: NotificationUtil? = null
|
||||||
private var downloadQueue: ArrayList<ResultItem>? = null
|
private var downloadQueue: ArrayList<ResultItem>? = null
|
||||||
|
|
||||||
private lateinit var resultViewModel : ResultViewModel
|
private lateinit var resultViewModel : ResultViewModel
|
||||||
|
|
@ -121,6 +123,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
mainActivity = activity as MainActivity?
|
mainActivity = activity as MainActivity?
|
||||||
quickLaunchSheet = false
|
quickLaunchSheet = false
|
||||||
infoUtil = InfoUtil(requireContext())
|
infoUtil = InfoUtil(requireContext())
|
||||||
|
notificationUtil = NotificationUtil(requireContext())
|
||||||
selectedObjects = arrayListOf()
|
selectedObjects = arrayListOf()
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
@ -306,6 +309,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
|
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
|
||||||
|
notificationUtil?.cancelDownloadNotification(NotificationUtil.FORMAT_UPDATING_FINISHED_NOTIFICATION_ID)
|
||||||
arguments?.remove("showDownloadsWithUpdatedFormats")
|
arguments?.remove("showDownloadsWithUpdatedFormats")
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val ids = arguments?.getLongArray("downloadIds") ?: return@launch
|
val ids = arguments?.getLongArray("downloadIds") ?: return@launch
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
codec.text = codecText
|
codec.text = codecText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val container = card.findViewById<TextView>(R.id.container)
|
||||||
|
container.isVisible = item.container.isNotBlank()
|
||||||
|
container.text = item.container.uppercase()
|
||||||
|
|
||||||
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
||||||
val fileSizeReadable = if(item.type != DownloadViewModel.Type.video){
|
val fileSizeReadable = if(item.type != DownloadViewModel.Type.video){
|
||||||
FileUtil.convertFileSize(item.format.filesize)
|
FileUtil.convertFileSize(item.format.filesize)
|
||||||
|
|
@ -185,6 +189,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
override fun areContentsTheSame(oldItem: DownloadItemConfigureMultiple, newItem: DownloadItemConfigureMultiple): Boolean {
|
override fun areContentsTheSame(oldItem: DownloadItemConfigureMultiple, newItem: DownloadItemConfigureMultiple): Boolean {
|
||||||
return oldItem.title == newItem.title &&
|
return oldItem.title == newItem.title &&
|
||||||
oldItem.type == newItem.type &&
|
oldItem.type == newItem.type &&
|
||||||
|
oldItem.container == newItem.container &&
|
||||||
oldItem.videoPreferences == newItem.videoPreferences &&
|
oldItem.videoPreferences == newItem.videoPreferences &&
|
||||||
oldItem.format == newItem.format &&
|
oldItem.format == newItem.format &&
|
||||||
oldItem.incognito == newItem.incognito
|
oldItem.incognito == newItem.incognito
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import java.util.*
|
||||||
class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, PlaylistAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, PlaylistAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||||
DIFF_CALLBACK
|
DIFF_CALLBACK
|
||||||
).build()) {
|
).build()) {
|
||||||
private val checkedItems: ArrayList<String>
|
private val checkedItems: ArrayList<Long>
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
@ -70,9 +70,9 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
|
|
||||||
// CHECKBOX ----------------------------------
|
// CHECKBOX ----------------------------------
|
||||||
val check = card.findViewById<CheckBox>(R.id.checkBox)
|
val check = card.findViewById<CheckBox>(R.id.checkBox)
|
||||||
check.isChecked = checkedItems.contains(item.url)
|
check.isChecked = checkedItems.contains(item.id)
|
||||||
check.setOnClickListener {
|
check.setOnClickListener {
|
||||||
checkCard(check.isChecked, item.url)
|
checkCard(check.isChecked, item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
card.setOnClickListener {
|
card.setOnClickListener {
|
||||||
|
|
@ -80,25 +80,25 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkCard(isChecked: Boolean, itemURL: String) {
|
private fun checkCard(isChecked: Boolean, id: Long) {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
checkedItems.add(itemURL)
|
checkedItems.add(id)
|
||||||
} else {
|
} else {
|
||||||
checkedItems.remove(itemURL)
|
checkedItems.remove(id)
|
||||||
}
|
}
|
||||||
onItemClickListener.onCardSelect(itemURL, isChecked, checkedItems)
|
onItemClickListener.onCardSelect(id, isChecked, checkedItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnItemClickListener {
|
interface OnItemClickListener {
|
||||||
fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>)
|
fun onCardSelect(itemID: Long, isChecked: Boolean, checkedItems: List<Long>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun clearCheckeditems() {
|
fun clearCheckeditems() {
|
||||||
for (i in 0 until itemCount){
|
for (i in 0 until itemCount){
|
||||||
val item = getItem(i)
|
val item = getItem(i)
|
||||||
if (checkedItems.find { it == item?.url } != null){
|
if (checkedItems.find { it == item?.id } != null){
|
||||||
checkedItems.remove(item?.url)
|
checkedItems.remove(item?.id)
|
||||||
notifyItemChanged(i)
|
notifyItemChanged(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,15 +109,15 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
checkedItems.clear()
|
checkedItems.clear()
|
||||||
for (i in 0 until itemCount){
|
for (i in 0 until itemCount){
|
||||||
val item = getItem(i)
|
val item = getItem(i)
|
||||||
checkedItems.add(item!!.url)
|
checkedItems.add(item!!.id)
|
||||||
notifyItemChanged(i)
|
notifyItemChanged(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invertSelected(items: List<ResultItem?>?){
|
fun invertSelected(items: List<ResultItem?>?){
|
||||||
val invertedList = mutableListOf<String>()
|
val invertedList = mutableListOf<Long>()
|
||||||
items?.forEach {
|
items?.forEach {
|
||||||
if (!checkedItems.contains(it!!.url)) invertedList.add(it.url)
|
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
|
||||||
}
|
}
|
||||||
checkedItems.clear()
|
checkedItems.clear()
|
||||||
checkedItems.addAll(invertedList)
|
checkedItems.addAll(invertedList)
|
||||||
|
|
@ -128,19 +128,19 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
checkedItems.clear()
|
checkedItems.clear()
|
||||||
if (start == end ){
|
if (start == end ){
|
||||||
val item = getItem(start)
|
val item = getItem(start)
|
||||||
checkedItems.add(item!!.url)
|
checkedItems.add(item!!.id)
|
||||||
notifyItemChanged(start)
|
notifyItemChanged(start)
|
||||||
}else{
|
}else{
|
||||||
for (i in start..end){
|
for (i in start..end){
|
||||||
val item = getItem(i)
|
val item = getItem(i)
|
||||||
checkedItems.add(item!!.url)
|
checkedItems.add(item!!.id)
|
||||||
notifyItemChanged(i)
|
notifyItemChanged(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCheckedItems() : List<String>{
|
fun getCheckedItems() : List<Long>{
|
||||||
return checkedItems
|
return checkedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ 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.PopupMenu
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.get
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -90,6 +90,9 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
private lateinit var currentDownloadIDs: List<Long>
|
private lateinit var currentDownloadIDs: List<Long>
|
||||||
private var processingItemsCount : Int = 0
|
private var processingItemsCount : Int = 0
|
||||||
|
|
||||||
|
private lateinit var containerBtn : MenuItem
|
||||||
|
private lateinit var containerTextView: TextView
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
|
|
@ -145,10 +148,16 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
scheduleBtn = view.findViewById(R.id.bottomsheet_schedule_button)
|
||||||
downloadBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_download_button)
|
downloadBtn = view.findViewById(R.id.bottomsheet_download_button)
|
||||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||||
|
|
||||||
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
||||||
|
val formatBtn = bottomAppBar.menu.findItem(R.id.format)
|
||||||
|
val moreBtn = bottomAppBar.menu.findItem(R.id.more)
|
||||||
|
containerBtn = bottomAppBar.menu.findItem(R.id.container)
|
||||||
|
containerTextView = containerBtn.actionView as TextView
|
||||||
|
val incognitoBtn = bottomAppBar.menu.findItem(R.id.incognito)
|
||||||
|
|
||||||
filesize = view.findViewById(R.id.filesize)
|
filesize = view.findViewById(R.id.filesize)
|
||||||
count = view.findViewById(R.id.count)
|
count = view.findViewById(R.id.count)
|
||||||
|
|
@ -167,20 +176,28 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.processingDownloads.collectLatest { items ->
|
downloadViewModel.processingDownloads.collectLatest { items ->
|
||||||
processingItemsCount = items.size
|
processingItemsCount = items.size
|
||||||
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
|
count.text = "$processingItemsCount ${getString(R.string.selected)}"
|
||||||
listAdapter.submitList(items)
|
listAdapter.submitList(items)
|
||||||
|
|
||||||
updateFileSize(items)
|
updateFileSize(items)
|
||||||
|
|
||||||
if (items.isNotEmpty()){
|
if (items.isNotEmpty()){
|
||||||
if (items.all { it2 -> it2.type == items[0].type }) {
|
if (items.all { it2 -> it2.type == items[0].type }) {
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
formatBtn.icon?.alpha = 255
|
||||||
if (items[0].type != DownloadViewModel.Type.command) {
|
if (items[0].type != DownloadViewModel.Type.command) {
|
||||||
bottomAppBar.menu[4].icon?.alpha = 255
|
moreBtn.icon?.alpha = 255
|
||||||
}
|
}
|
||||||
|
|
||||||
|
containerBtn.isVisible = items.first().type != DownloadViewModel.Type.command
|
||||||
} else {
|
} else {
|
||||||
bottomAppBar.menu[1].icon?.alpha = 30
|
formatBtn.icon?.alpha = 30
|
||||||
bottomAppBar.menu[4].icon?.alpha = 30
|
moreBtn.icon?.alpha = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.all { it.container == items[0].container }) {
|
||||||
|
setContainerText(items[0].container)
|
||||||
|
}else{
|
||||||
|
setContainerText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
val type = items.first().type
|
val type = items.first().type
|
||||||
|
|
@ -191,10 +208,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
DownloadViewModel.Type.video -> {
|
DownloadViewModel.Type.video -> {
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||||
|
|
||||||
}
|
}
|
||||||
DownloadViewModel.Type.command -> {
|
DownloadViewModel.Type.command -> {
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||||
|
setContainerText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
|
|
@ -303,7 +320,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel.areAllProcessingIncognito()
|
downloadViewModel.areAllProcessingIncognito()
|
||||||
}
|
}
|
||||||
|
|
||||||
bottomAppBar.menu.children.first { it.itemId == R.id.incognito }.icon!!.apply {
|
incognitoBtn.icon!!.apply {
|
||||||
alpha = if (allIncognito){
|
alpha = if (allIncognito){
|
||||||
255
|
255
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -336,7 +353,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
R.id.incognito -> {
|
R.id.incognito -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (m.icon!!.alpha == 255) {
|
if (m.icon!!.alpha == 255) {
|
||||||
bottomAppBar.menu[3].isEnabled = false
|
incognitoBtn.isEnabled = false
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
downloadViewModel.updateProcessingIncognito(false)
|
downloadViewModel.updateProcessingIncognito(false)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
|
|
@ -344,18 +361,18 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
m.isEnabled = true
|
m.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bottomAppBar.menu[3].icon?.alpha = 30
|
incognitoBtn.icon?.alpha = 30
|
||||||
bottomAppBar.menu[3].isEnabled = true
|
incognitoBtn.isEnabled = true
|
||||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.disabled)}", Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.disabled)}", Toast.LENGTH_SHORT).show()
|
||||||
}else{
|
}else{
|
||||||
bottomAppBar.menu[3].isEnabled = false
|
incognitoBtn.isEnabled = false
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
downloadViewModel.updateProcessingIncognito(true)
|
downloadViewModel.updateProcessingIncognito(true)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bottomAppBar.menu[3].icon?.alpha = 255
|
incognitoBtn.icon?.alpha = 255
|
||||||
bottomAppBar.menu[3].isEnabled = true
|
incognitoBtn.isEnabled = true
|
||||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.ok)}", Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.ok)}", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -393,10 +410,20 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
|
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
formatBtn.icon?.alpha = 255
|
||||||
bottomAppBar.menu[4].icon?.alpha = 255
|
moreBtn.icon?.alpha = 255
|
||||||
bottomSheet.cancel()
|
bottomSheet.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (!res.first) {
|
||||||
|
setContainerText("")
|
||||||
|
}else{
|
||||||
|
setContainerText(res.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,10 +432,19 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
|
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
formatBtn.icon?.alpha = 255
|
||||||
bottomAppBar.menu[4].icon?.alpha = 255
|
moreBtn.icon?.alpha = 255
|
||||||
bottomSheet.cancel()
|
bottomSheet.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer()
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (!res.first) {
|
||||||
|
setContainerText("")
|
||||||
|
}else{
|
||||||
|
setContainerText(res.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -417,10 +453,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
|
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
formatBtn.icon?.alpha = 255
|
||||||
bottomAppBar.menu[4].icon?.alpha = 30
|
moreBtn.icon?.alpha = 30
|
||||||
bottomSheet.cancel()
|
bottomSheet.cancel()
|
||||||
|
containerBtn.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -676,6 +714,41 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
containerTextView.setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val res = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||||
|
}
|
||||||
|
if (!res.first){
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
||||||
|
}else{
|
||||||
|
val popup = PopupMenu(activity, containerTextView)
|
||||||
|
when(res.second) {
|
||||||
|
DownloadViewModel.Type.audio -> resources.getStringArray(R.array.audio_containers)
|
||||||
|
//video
|
||||||
|
else -> resources.getStringArray(R.array.video_containers)
|
||||||
|
}.forEach {
|
||||||
|
popup.menu.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener { mm ->
|
||||||
|
val container = mm.title
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.updateProcessingContainer(container.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setContainerText(container.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleLoading(loading: Boolean){
|
private fun toggleLoading(loading: Boolean){
|
||||||
|
|
@ -964,5 +1037,21 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
private fun setContainerText(cont: String?) {
|
||||||
|
if (cont == null) {
|
||||||
|
containerBtn.isVisible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cont == getString(R.string.defaultValue) || cont.isEmpty()) {
|
||||||
|
containerTextView.text = ".ext"
|
||||||
|
return
|
||||||
|
}else{
|
||||||
|
containerTextView.text = cont
|
||||||
|
}
|
||||||
|
containerBtn.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
private lateinit var selectBetween: MenuItem
|
private lateinit var selectBetween: MenuItem
|
||||||
|
|
||||||
private lateinit var resultItemIDs: List<Long>
|
private lateinit var resultItemIDs: List<Long>
|
||||||
private lateinit var itemURLs: List<String>
|
|
||||||
private var items = listOf<ResultItem>()
|
private var items = listOf<ResultItem>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -179,9 +178,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
ok.isEnabled = false
|
ok.isEnabled = false
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val checkedItems = listAdapter.getCheckedItems()
|
val checkedItems = listAdapter.getCheckedItems()
|
||||||
val checkedResultItems = items.filter { item -> checkedItems.contains(item.url) }
|
val checkedResultItems = items.filter { item -> checkedItems.contains(item.id) }
|
||||||
if (checkedResultItems.size == 1){
|
if (checkedResultItems.size == 1){
|
||||||
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0].url)!!
|
val resultItem = resultViewModel.getByID(checkedResultItems[0].id)!!
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadBottomSheetDialog, bundleOf(
|
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadBottomSheetDialog, bundleOf(
|
||||||
Pair("result", resultItem),
|
Pair("result", resultItem),
|
||||||
|
|
@ -223,8 +222,8 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
if(selectedItems.size != 2){
|
if(selectedItems.size != 2){
|
||||||
m.isVisible = false
|
m.isVisible = false
|
||||||
}else{
|
}else{
|
||||||
val item2 = itemURLs.indexOf(selectedItems.last())
|
val item2 = resultItemIDs.indexOf(selectedItems.last())
|
||||||
val item1 = itemURLs.indexOf(selectedItems.first())
|
val item1 = resultItemIDs.indexOf(selectedItems.first())
|
||||||
if(item1 > item2) listAdapter.checkRange(item2, item1)
|
if(item1 > item2) listAdapter.checkRange(item2, item1)
|
||||||
else listAdapter.checkRange(item1, item2)
|
else listAdapter.checkRange(item1, item2)
|
||||||
count.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
count.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||||
|
|
@ -249,7 +248,7 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
toTextInput.isEnabled = !isLoading
|
toTextInput.isEnabled = !isLoading
|
||||||
|
|
||||||
items = it
|
items = it
|
||||||
itemURLs = items.map { itm -> itm.url }
|
resultItemIDs = items.map { itm -> itm.id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +283,7 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>) {
|
override fun onCardSelect(itemID: Long, isChecked: Boolean, checkedItems: List<Long>) {
|
||||||
if (checkedItems.size == items.size){
|
if (checkedItems.size == items.size){
|
||||||
count.text = "(${listAdapter.getCheckedItems().size}) ${resources.getString(R.string.all_items_selected)} "
|
count.text = "(${listAdapter.getCheckedItems().size}) ${resources.getString(R.string.all_items_selected)} "
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -302,8 +301,8 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
val size = checkedItems.size
|
val size = checkedItems.size
|
||||||
if(size != 2) false
|
if(size != 2) false
|
||||||
else {
|
else {
|
||||||
val item1 = itemURLs.indexOf(checkedItems.first())
|
val item1 = resultItemIDs.indexOf(checkedItems.first())
|
||||||
val item2 = itemURLs.indexOf(checkedItems.last())
|
val item2 = resultItemIDs.indexOf(checkedItems.last())
|
||||||
|
|
||||||
(item1-item2).absoluteValue > 1
|
(item1-item2).absoluteValue > 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,12 +167,17 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val activeQueuedDownloadsCount = withContext(Dispatchers.IO) {
|
val activeCount = withContext(Dispatchers.IO) {
|
||||||
downloadViewModel.getActiveQueuedDownloadsCount()
|
downloadViewModel.getActiveDownloadsCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val queuedCount = withContext(Dispatchers.IO) {
|
||||||
|
downloadViewModel.getQueuedDownloadsCount()
|
||||||
|
}
|
||||||
|
|
||||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||||
pause.isVisible = activeQueuedDownloadsCount > 0 && !pausedDownloads
|
pause.isVisible = (queuedCount > 0 || activeCount > 0) && !pausedDownloads
|
||||||
resume.isVisible = activeQueuedDownloadsCount > 0 && pausedDownloads
|
resume.isVisible = (queuedCount > 0 || activeCount > 0) && pausedDownloads
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdl.MainActivity
|
import com.deniscerri.ytdl.MainActivity
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||||
|
|
@ -22,11 +23,14 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
||||||
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
|
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
|
||||||
import com.deniscerri.ytdl.util.NavbarUtil
|
import com.deniscerri.ytdl.util.NavbarUtil
|
||||||
|
import com.deniscerri.ytdl.util.NotificationUtil
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class MoreFragment : Fragment() {
|
class MoreFragment : Fragment() {
|
||||||
|
|
@ -125,28 +129,40 @@ class MoreFragment : Fragment() {
|
||||||
doNotShowAgain = compoundButton.isChecked
|
doNotShowAgain = compoundButton.isChecked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val workManager = WorkManager.getInstance(requireContext())
|
||||||
|
val notificationUtil = NotificationUtil(requireContext())
|
||||||
|
|
||||||
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
|
terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
|
||||||
runBlocking {
|
lifecycleScope.launch {
|
||||||
val job : Job = lifecycleScope.launch(Dispatchers.IO) {
|
val activeDownloads = withContext(Dispatchers.IO){
|
||||||
val activeDownloads = downloadViewModel.getActiveDownloads().toMutableList()
|
downloadViewModel.getActiveDownloadsCount()
|
||||||
activeDownloads.map { it.status = DownloadRepository.Status.Queued.toString() }
|
|
||||||
activeDownloads.forEach {
|
|
||||||
downloadViewModel.updateDownload(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
runBlocking {
|
if (activeDownloads > 0) {
|
||||||
job.join()
|
workManager.cancelAllWorkByTag("download")
|
||||||
if (doNotShowAgain){
|
val activeDownloadsList = withContext(Dispatchers.IO){
|
||||||
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false)
|
downloadViewModel.getActiveDownloads()
|
||||||
mainSharedPreferencesEditor.commit()
|
|
||||||
}
|
}
|
||||||
mainActivity.finishAndRemoveTask()
|
|
||||||
mainActivity.finishAffinity()
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
activeDownloadsList.forEach {
|
||||||
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
|
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
downloadViewModel.updateDownload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainSharedPreferencesEditor.putBoolean("paused_downloads", true).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doNotShowAgain){
|
||||||
|
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false).apply()
|
||||||
|
}
|
||||||
|
mainSharedPreferencesEditor.commit()
|
||||||
|
mainActivity.finishAndRemoveTask()
|
||||||
|
mainActivity.finishAffinity()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
terminateDialog.show()
|
terminateDialog.show()
|
||||||
}else{
|
}else{
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,12 @@ import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdl.MainActivity
|
import com.deniscerri.ytdl.MainActivity
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.viewmodel.LogViewModel
|
import com.deniscerri.ytdl.database.viewmodel.LogViewModel
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
||||||
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
|
import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
@ -104,8 +106,14 @@ class DownloadLogFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content.isFocusable = true
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
content.enableTextHighlight()
|
content.isFocusable = true
|
||||||
|
content.enableTextHighlight()
|
||||||
|
}
|
||||||
|
|
||||||
|
contentScrollView.enableFastScroll()
|
||||||
|
|
||||||
|
|
||||||
val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
|
val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
|
||||||
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
|
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
when(m.itemId){
|
when(m.itemId){
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import android.view.ViewOutlineProvider
|
||||||
import android.view.animation.Interpolator
|
import android.view.animation.Interpolator
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Px
|
import androidx.annotation.Px
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|
@ -139,6 +140,15 @@ object Extensions {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ScrollView.enableFastScroll() {
|
||||||
|
val drawable = ShapeDrawable(OvalShape())
|
||||||
|
drawable.paint.color = context.getColor(android.R.color.transparent)
|
||||||
|
|
||||||
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
|
.setTrackDrawable(drawable)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
fun File.getMediaDuration(context: Context): Int {
|
fun File.getMediaDuration(context: Context): Int {
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
if (!exists()) return 0
|
if (!exists()) return 0
|
||||||
|
|
@ -251,20 +261,20 @@ object Extensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.appendLineToLog(line: String): String {
|
// fun String.appendLineToLog(line: String): String {
|
||||||
val lines = this.split("\n")
|
// val lines = this.split("\n")
|
||||||
if (!lines.takeLast(3).contains(line)){
|
// if (!lines.takeLast(3).contains(line)){
|
||||||
//clean dublicate progress + add newline
|
// //clean dublicate progress + add newline
|
||||||
var newLine = line
|
// var newLine = line
|
||||||
if (newLine.contains("[download")) {
|
// if (newLine.contains("[download")) {
|
||||||
newLine = "[download]" + line.split("[download]").last()
|
// newLine = "[download]" + line.split("[download]").last()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
// return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return this
|
// return this
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
|
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
|
||||||
if(!hideThumb){
|
if(!hideThumb){
|
||||||
|
|
|
||||||
|
|
@ -97,11 +97,15 @@ class DownloadWorker(
|
||||||
|
|
||||||
val running = ArrayList(runningYTDLInstances)
|
val running = ArrayList(runningYTDLInstances)
|
||||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
if (items.isEmpty() && running.isEmpty()) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
if (items.isEmpty() && running.isEmpty()) {
|
||||||
|
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
|
||||||
if (useScheduler){
|
if (useScheduler){
|
||||||
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
|
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
|
||||||
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||||
|
return@collect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
||||||
|
|
@ -138,15 +142,16 @@ class DownloadWorker(
|
||||||
|
|
||||||
|
|
||||||
val commandString = infoUtil.parseYTDLRequestString(request)
|
val commandString = infoUtil.parseYTDLRequestString(request)
|
||||||
val logString = StringBuilder("\n ${commandString}\n\n")
|
val initialLogDetails = "Downloading:\n" +
|
||||||
|
"Title: ${downloadItem.title}\n" +
|
||||||
|
"URL: ${downloadItem.url}\n" +
|
||||||
|
"Type: ${downloadItem.type}\n" +
|
||||||
|
"Command: \n $commandString \n\n"
|
||||||
|
val logString = StringBuilder(initialLogDetails)
|
||||||
val logItem = LogItem(
|
val logItem = LogItem(
|
||||||
0,
|
0,
|
||||||
downloadItem.title.ifBlank { downloadItem.url },
|
downloadItem.title.ifBlank { downloadItem.url },
|
||||||
"Downloading:\n" +
|
logString.toString(),
|
||||||
"Title: ${downloadItem.title}\n" +
|
|
||||||
"URL: ${downloadItem.url}\n" +
|
|
||||||
"Type: ${downloadItem.type}\n" +
|
|
||||||
"Command: $logString",
|
|
||||||
downloadItem.format,
|
downloadItem.format,
|
||||||
downloadItem.type,
|
downloadItem.type,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
@ -182,7 +187,7 @@ class DownloadWorker(
|
||||||
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
||||||
dao.updateWithoutUpsert(this)
|
dao.updateWithoutUpsert(this)
|
||||||
}
|
}
|
||||||
val wasQuickDownloaded = resultDao.getCountInt() == 0
|
//val wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||||
runBlocking {
|
runBlocking {
|
||||||
var finalPaths : MutableList<String>?
|
var finalPaths : MutableList<String>?
|
||||||
|
|
||||||
|
|
@ -304,7 +309,7 @@ class DownloadWorker(
|
||||||
dao.delete(downloadItem.id)
|
dao.delete(downloadItem.id)
|
||||||
|
|
||||||
if (logDownloads){
|
if (logDownloads){
|
||||||
logRepo.update(it.out, logItem.id)
|
logRepo.update(initialLogDetails + "\n" + it.out, logItem.id, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,12 @@ class TerminalDownloadWorker(
|
||||||
|
|
||||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||||
|
|
||||||
|
val initialLogDetails = "Terminal Task\n" +
|
||||||
|
"Command: \n ${command}\n\n"
|
||||||
val logItem = LogItem(
|
val logItem = LogItem(
|
||||||
0,
|
0,
|
||||||
"Terminal Task",
|
"Terminal Task",
|
||||||
"Terminal Task\n" +
|
initialLogDetails,
|
||||||
"Command: \n ${command}\n\n",
|
|
||||||
Format(),
|
Format(),
|
||||||
DownloadViewModel.Type.command,
|
DownloadViewModel.Type.command,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
@ -139,8 +140,7 @@ class TerminalDownloadWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (logDownloads) logRepo.update(initialLogDetails + "\n" + it.out, logItem.id, true)
|
||||||
if (logDownloads) logRepo.update(it.out, logItem.id)
|
|
||||||
dao.updateLog(it.out, itemId.toLong())
|
dao.updateLog(it.out, itemId.toLong())
|
||||||
notificationUtil.cancelDownloadNotification(itemId)
|
notificationUtil.cancelDownloadNotification(itemId)
|
||||||
delay(1000)
|
delay(1000)
|
||||||
|
|
|
||||||
12
app/src/main/res/layout/bottom_app_bar_text.xml
Normal file
12
app/src/main/res/layout/bottom_app_bar_text.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:id="@+id/bottom_app_bar_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:minHeight="0dp"
|
||||||
|
android:minWidth="0dp"
|
||||||
|
android:textColor="?android:colorAccent"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
|
@ -187,6 +187,27 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/container"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
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:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/codec"
|
android:id="@+id/codec"
|
||||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ScrollView
|
<me.zhanghai.android.fastscroll.FastScrollScrollView
|
||||||
android:id="@+id/content_scrollview"
|
android:id="@+id/content_scrollview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</me.zhanghai.android.fastscroll.FastScrollScrollView>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@
|
||||||
android:icon="@drawable/baseline_folder_24"
|
android:icon="@drawable/baseline_folder_24"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:title="@string/container"
|
||||||
|
app:actionLayout="@layout/bottom_app_bar_text"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/incognito"
|
android:id="@+id/incognito"
|
||||||
android:title="@string/incognito"
|
android:title="@string/incognito"
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
android:defaultValue=""
|
android:defaultValue=""
|
||||||
android:entries="@array/cleanup_leftover_downloads"
|
android:entries="@array/cleanup_leftover_downloads"
|
||||||
android:entryValues="@array/cleanup_leftover_downloads_values"
|
android:entryValues="@array/cleanup_leftover_downloads_values"
|
||||||
android:icon="@drawable/baseline_delete_24"
|
android:icon="@drawable/ic_folder_delete"
|
||||||
app:key="cleanup_leftover_downloads"
|
app:key="cleanup_leftover_downloads"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:title="@string/cleanup_leftover_downloads" />
|
app:title="@string/cleanup_leftover_downloads" />
|
||||||
|
|
|
||||||
|
|
@ -1,87 +1,43 @@
|
||||||
TEMPORARY just to save changelog
|
# What's Changed
|
||||||
|
|
||||||
List is empty.
|
- Fix App crashing with exception "com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.checkIfAllProcessingItemsHaveSameType(Unknown Source:21)..."
|
||||||
|
- Added option in download settings to use AlarmManager instead of WorkManager for scheduled downloads to improve accuracy
|
||||||
kotlin.collections.CollectionsKt.first(SourceFile:2)
|
- When updating formats for multiple items, now the app remembers what items don't need new formats and skips them to save time and data
|
||||||
com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.checkIfAllProcessingItemsHaveSameType(Unknown Source:21)
|
- Moved format source from the bottom of the format list to the filter bottom sheet
|
||||||
com.deniscerri.ytdl.ui.downloadcard.DownloadMultipleBottomSheetDialog$setupDialog$9$3$res$1.invokeSuspend(SourceFile:18)
|
- Added ability for the app to update the result items formats when you update the download item formats if there are any results with the same url
|
||||||
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:9)
|
- Moved "In Queue" screen back in its separate tab due to recyclerview performance issues
|
||||||
kotlinx.coroutines.DispatchedTask.run(Unknown Source:96)
|
- Format updating in multiple download card now applies updates immediately on each item
|
||||||
androidx.work.Worker$2.run(SourceFile:39)
|
- Deleted saved downloads after a format update in the background after the user decided to open for review
|
||||||
kotlinx.coroutines.scheduling.TaskImpl.run(Unknown Source:2)
|
- App now shows the actual predicted filesize along with the audio size when downloading videos in the multiple download card. Also in single download mode when the audio size is known but the video size is unknown the app wont show the audio size only, it wont show anything
|
||||||
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(SourceFile:96)
|
- Made snackbar show up above the copy log button
|
||||||
|
- Fixed App crashing when updating fragments in the download card while in the Share Activity
|
||||||
|
- Added a setting to use the video url instead of the playlist url to download. The app used playlist url and then indexes to get the video to take advantage of playlist metadata. Some users have been using libretube playlists that cant be recognised properly by yt-dlp. Turn this option if this affects you
|
||||||
add option in download settings to use alarm manager instead of workmanager for scheduled tasks
|
- Added Write URI Permission when opening files
|
||||||
|
- Fixed App still updating formats even though the user closed the format bottom sheet
|
||||||
fix audio format importance
|
- Fixed App keeping old video titles in the download card #526
|
||||||
|
- Refactored code on how to fetch player url and chapters from yt-dlp in the cut bottom sheet
|
||||||
When updating formats for multiple items, now the app remembers what items dont need new formats and skips them
|
- Added ability to change the theme accent without restarting the app. If you change light/dark mode the app is supposed to restart because the icon activity changes aswell
|
||||||
|
- Prevented AVI and FLV containers from embedding thumbnails
|
||||||
Move format source from the bottom of the format list to the filter bottom sheet
|
- Fixed Subs Language preference not being saved in the download card
|
||||||
|
- Added player_client=default,mediaconnect,android extractor args when data fetching and downloading to use more formats
|
||||||
Added ability for the app to update the result items formats when you update the download item formats if there are any results with the same url
|
- Turned some metadata into digits
|
||||||
|
|
||||||
moved queue screen back in its separate tab due to performance issues
|
|
||||||
|
|
||||||
format updating in the multiple download card now applies updates immediately on each item
|
|
||||||
|
|
||||||
Update formats in background review. Also prevent from showing up every time it updates. Also delete saved after opening items n the multiple download card lol wtf. Also the notification. Change the title of notification to
|
|
||||||
|
|
||||||
Can that other one under the Copy log be fixed, too?snackbar
|
|
||||||
|
|
||||||
Bulk download shows size without audio fixed
|
|
||||||
|
|
||||||
When selecting audio format add generic last
|
|
||||||
|
|
||||||
Update ui crashes app from share activity
|
|
||||||
|
|
||||||
add option to use video url instead of playlist url to avoid piped private playlist issue
|
|
||||||
|
|
||||||
add write uri permission when opening files
|
|
||||||
|
|
||||||
fix app still updating formats even though the user closed the format bottom sheet
|
|
||||||
|
|
||||||
[BUG] Video title doesn't change in the download card #526
|
|
||||||
|
|
||||||
fixed app fetching player urls and chapters from yt-dlp
|
|
||||||
|
|
||||||
added ability to change theme without restarting the app. If the icon changes then the app will restart
|
|
||||||
|
|
||||||
prevent avi and flv containers from embedding thumbnails
|
|
||||||
|
|
||||||
Fixed Subs language preference isn't saved in the download card
|
|
||||||
|
|
||||||
add player_client=default,mediaconnect,android extractor args when data fetching and downloading to show more formats
|
|
||||||
|
|
||||||
Turned some metadata into digits
|
|
||||||
-> --parse-metadata "%(track_number,playlist_index)d:(?P<track_number>\d+)" --parse-metadata "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\d+)"
|
-> --parse-metadata "%(track_number,playlist_index)d:(?P<track_number>\d+)" --parse-metadata "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:(?P<meta_date>\d+)"
|
||||||
|
|
||||||
Removed NA from meta album artist --parse-metadata "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)"
|
- Removed NA from meta album artist --parse-metadata "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)"
|
||||||
|
- Added --recode-video toggle in the settings to use instead of --merge-output-format
|
||||||
Add --recode-video toggle in the settings to use instead of --merge-output-format
|
- Also added that preference as a button in the adjust video in the download card
|
||||||
|
- Adjusted the "Adjust video" section in the download card and bundled some common items together to save space
|
||||||
adjusted the "Adjust video" section in the download card and put all subtitle items together
|
- Fixed app not selecting an audio format to show in the format view in the download card after updating formats
|
||||||
|
- Fixed app showing when the download will start up when you queue up a scheduled download
|
||||||
Fixed app not selecting an audio format to show in the format view in the download card after updating formats
|
- Made home recyclerview bottom padding bigger to avoid the FABs
|
||||||
|
- Fixed Issue #529
|
||||||
download_rescheduled_to shows up twice, one in original language and one in english
|
- App now remembers the scroll position in the download history tab
|
||||||
|
- Made piped api not fetch data when fed mix playlists
|
||||||
It's very difficult to tap on video icon for the video at the bottom
|
- Turned the incognito toast to a snackbar in the download card
|
||||||
|
- Fixed duplicate dialog crashing app when in share activity
|
||||||
#529
|
- Fixed issue of app not showing audio formats of preferred language at the top when selecting the suggested filter
|
||||||
|
- Added a feature to delete cancelled downloads, errored downloads and download cache older than a certain period. You can select daily, weekly, monthly.
|
||||||
https://www.instagram.com/reel/C9zTRlONU6G/?igsh=MWJ1djBhYjdscG5wMw== chooses smallest format
|
- Added ability to change the container in the multiple download card
|
||||||
|
- Fixed App issues with download logs
|
||||||
Limit audio id length
|
- Added preferred language in the format sorter in yt-dlp
|
||||||
|
- More other small changes i forgor what i fixed
|
||||||
Remember scroll position in history tab
|
|
||||||
|
|
||||||
Make piped not fetch mix playlists
|
|
||||||
|
|
||||||
turn incognito toast to snackbar
|
|
||||||
|
|
||||||
duplicate dialog crash app when in share activity because the activity closes too early
|
|
||||||
|
|
||||||
4. In videos with multiple languages, previously, when I entered the suggested tab, all audios in my preferred language were displayed at the top, with the lowest quality. However, this no longer happens, and now I need to search among multiple languages (again, I'm not entirely sure about this, but it's challenging to compare with a previous version since I no longer have access to older app versions)
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue