This commit is contained in:
zaednasr 2024-07-28 23:33:06 +02:00
parent 6ba8d65afd
commit d57ab39e82
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
26 changed files with 378 additions and 199 deletions

View file

@ -10,7 +10,7 @@ plugins {
def properties = new Properties()
def versionMajor = 1
def versionMinor = 7
def versionPatch = 8
def versionPatch = 9
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def isBeta = false
def versionExt = isBeta ? ".${versionBuild}-beta" : ""

View file

@ -45,6 +45,11 @@ interface DownloadDao {
""")
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)")
suspend fun updateItemsToProcessing(ids: List<Long>)
@ -60,6 +65,9 @@ interface DownloadDao {
@Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'")
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'")
fun getActiveDownloadsList() : List<DownloadItem>

View file

@ -7,7 +7,6 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@ -41,10 +40,15 @@ interface LogDao {
suspend fun updateLogContent(l: String, id: Long)
@Transaction
suspend fun updateLog(line: String, id: Long) {
suspend fun updateLog(line: String, id: Long, resetLog: Boolean = false) {
val l = getByID(id) ?: return
val log = l.content ?: ""
updateLogContent(log.appendLineToLog(line), id)
var log = l.content ?: ""
if (resetLog) {
log = line.lines().joinToString("\n")
}else{
log += "\n${line}"
}
updateLogContent(log, id)
}
@Update(onConflict = OnConflictStrategy.REPLACE)

View file

@ -5,7 +5,6 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import com.deniscerri.ytdl.database.models.TerminalItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
@Dao
@ -29,10 +28,15 @@ interface TerminalDao {
@Query("SELECT * FROM terminalDownloads WHERE id=:id LIMIT 1")
fun getTerminalById(id: Long) : TerminalItem?
@Transaction
suspend fun updateLog(line: String, id: Long){
suspend fun updateLog(line: String, id: Long, resetLog : Boolean = false){
val t = getTerminalById(id) ?: return
val log = t.log ?: ""
updateTerminalLog(log.appendLineToLog(line), id)
var log = t.log ?: ""
if (resetLog) {
log = line.lines().joinToString("\n")
}else{
log += "\n${line}"
}
updateTerminalLog(log, id)
}
@Insert

View file

@ -13,6 +13,7 @@ data class DownloadItemConfigureMultiple(
var title: String,
var thumb: String,
var duration: String,
var container: String,
var format: Format,
var allFormats: MutableList<Format>,
var audioPreferences: AudioPreferences,

View file

@ -2,7 +2,6 @@ package com.deniscerri.ytdl.database.repository
import com.deniscerri.ytdl.database.dao.LogDao
import com.deniscerri.ytdl.database.models.LogItem
import com.deniscerri.ytdl.util.Extensions.appendLineToLog
import kotlinx.coroutines.flow.Flow
class LogRepository(private val logDao: LogDao) {
@ -38,9 +37,9 @@ class LogRepository(private val logDao: LogDao) {
return logDao.getByID(id)
}
suspend fun update(line: String, id: Long){
suspend fun update(line: String, id: Long, resetLog: Boolean = false){
kotlin.runCatching {
logDao.updateLog(line, id)
logDao.updateLog(line, id, resetLog)
}
}

View file

@ -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){
dao.updateProcessingDownloadPath(path)
}
@ -1111,6 +1119,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
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>) {
repository.deleteProcessing()

View file

@ -175,6 +175,14 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
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) {
val allQueries = searchHistoryRepository.getAll()
if (allQueries.none { it.query == query }){

View file

@ -33,6 +33,7 @@ import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.MainActivity
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.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
@ -185,13 +186,18 @@ class ShareActivity : BaseActivity() {
val command = intent.getStringExtra("COMMAND") ?: ""
lifecycleScope.launch {
var result = withContext(Dispatchers.IO){
resultViewModel.getItemByURL(inputQuery)
val result: ResultItem
val existingResults = withContext(Dispatchers.IO){
resultViewModel.getAllByURL(inputQuery)
}
if (result == null) {
if (existingResults.isEmpty() || existingResults.size > 1) {
resultViewModel.deleteAll()
result = downloadViewModel.createEmptyResultItem(inputQuery)
}else{
result = existingResults.first()
}
val downloadType = DownloadViewModel.Type.valueOf(type ?: downloadViewModel.getDownloadType(url = result.url).toString())
if (sharedPreferences.getBoolean("download_card", true) && !background){
val bundle = Bundle()

View file

@ -45,6 +45,7 @@ import com.deniscerri.ytdl.ui.adapter.HomeAdapter
import com.deniscerri.ytdl.ui.adapter.SearchSuggestionsAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.InfoUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.UiUtil
import com.facebook.shimmer.ShimmerFrameLayout
@ -84,6 +85,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
private var clipboardFab: ExtendedFloatingActionButton? = null
private var homeFabs: LinearLayout? = null
private var infoUtil: InfoUtil? = null
private var notificationUtil: NotificationUtil? = null
private var downloadQueue: ArrayList<ResultItem>? = null
private lateinit var resultViewModel : ResultViewModel
@ -121,6 +123,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
mainActivity = activity as MainActivity?
quickLaunchSheet = false
infoUtil = InfoUtil(requireContext())
notificationUtil = NotificationUtil(requireContext())
selectedObjects = arrayListOf()
return fragmentView
}
@ -306,6 +309,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
}
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
notificationUtil?.cancelDownloadNotification(NotificationUtil.FORMAT_UPDATING_FINISHED_NOTIFICATION_ID)
arguments?.remove("showDownloadsWithUpdatedFormats")
CoroutineScope(Dispatchers.IO).launch {
val ids = arguments?.getLongArray("downloadIds") ?: return@launch

View file

@ -112,6 +112,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
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 fileSizeReadable = if(item.type != DownloadViewModel.Type.video){
FileUtil.convertFileSize(item.format.filesize)
@ -185,6 +189,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
override fun areContentsTheSame(oldItem: DownloadItemConfigureMultiple, newItem: DownloadItemConfigureMultiple): Boolean {
return oldItem.title == newItem.title &&
oldItem.type == newItem.type &&
oldItem.container == newItem.container &&
oldItem.videoPreferences == newItem.videoPreferences &&
oldItem.format == newItem.format &&
oldItem.incognito == newItem.incognito

View file

@ -27,7 +27,7 @@ import java.util.*
class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, PlaylistAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val checkedItems: ArrayList<String>
private val checkedItems: ArrayList<Long>
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences: SharedPreferences
@ -70,9 +70,9 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
// CHECKBOX ----------------------------------
val check = card.findViewById<CheckBox>(R.id.checkBox)
check.isChecked = checkedItems.contains(item.url)
check.isChecked = checkedItems.contains(item.id)
check.setOnClickListener {
checkCard(check.isChecked, item.url)
checkCard(check.isChecked, item.id)
}
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) {
checkedItems.add(itemURL)
checkedItems.add(id)
} else {
checkedItems.remove(itemURL)
checkedItems.remove(id)
}
onItemClickListener.onCardSelect(itemURL, isChecked, checkedItems)
onItemClickListener.onCardSelect(id, isChecked, checkedItems)
}
interface OnItemClickListener {
fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>)
fun onCardSelect(itemID: Long, isChecked: Boolean, checkedItems: List<Long>)
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckeditems() {
for (i in 0 until itemCount){
val item = getItem(i)
if (checkedItems.find { it == item?.url } != null){
checkedItems.remove(item?.url)
if (checkedItems.find { it == item?.id } != null){
checkedItems.remove(item?.id)
notifyItemChanged(i)
}
}
@ -109,15 +109,15 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
checkedItems.clear()
for (i in 0 until itemCount){
val item = getItem(i)
checkedItems.add(item!!.url)
checkedItems.add(item!!.id)
notifyItemChanged(i)
}
}
fun invertSelected(items: List<ResultItem?>?){
val invertedList = mutableListOf<String>()
val invertedList = mutableListOf<Long>()
items?.forEach {
if (!checkedItems.contains(it!!.url)) invertedList.add(it.url)
if (!checkedItems.contains(it!!.id)) invertedList.add(it.id)
}
checkedItems.clear()
checkedItems.addAll(invertedList)
@ -128,19 +128,19 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
checkedItems.clear()
if (start == end ){
val item = getItem(start)
checkedItems.add(item!!.url)
checkedItems.add(item!!.id)
notifyItemChanged(start)
}else{
for (i in start..end){
val item = getItem(i)
checkedItems.add(item!!.url)
checkedItems.add(item!!.id)
notifyItemChanged(i)
}
}
}
fun getCheckedItems() : List<String>{
fun getCheckedItems() : List<Long>{
return checkedItems
}

View file

@ -16,12 +16,12 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -90,6 +90,9 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var currentDownloadIDs: List<Long>
private var processingItemsCount : Int = 0
private lateinit var containerBtn : MenuItem
private lateinit var containerTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -145,10 +148,16 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
itemTouchHelper.attachToRecyclerView(recyclerView)
}
scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
downloadBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_download_button)
scheduleBtn = view.findViewById(R.id.bottomsheet_schedule_button)
downloadBtn = view.findViewById(R.id.bottomsheet_download_button)
bottomAppBar = view.findViewById(R.id.bottomAppBar)
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)
count = view.findViewById(R.id.count)
@ -167,20 +176,28 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch {
downloadViewModel.processingDownloads.collectLatest { items ->
processingItemsCount = items.size
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
count.text = "$processingItemsCount ${getString(R.string.selected)}"
listAdapter.submitList(items)
updateFileSize(items)
if (items.isNotEmpty()){
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) {
bottomAppBar.menu[4].icon?.alpha = 255
moreBtn.icon?.alpha = 255
}
containerBtn.isVisible = items.first().type != DownloadViewModel.Type.command
} else {
bottomAppBar.menu[1].icon?.alpha = 30
bottomAppBar.menu[4].icon?.alpha = 30
formatBtn.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
@ -191,10 +208,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
}
DownloadViewModel.Type.video -> {
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
}
DownloadViewModel.Type.command -> {
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
setContainerText("")
}
else -> {}
@ -303,7 +320,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
downloadViewModel.areAllProcessingIncognito()
}
bottomAppBar.menu.children.first { it.itemId == R.id.incognito }.icon!!.apply {
incognitoBtn.icon!!.apply {
alpha = if (allIncognito){
255
}else{
@ -336,7 +353,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.incognito -> {
lifecycleScope.launch {
if (m.icon!!.alpha == 255) {
bottomAppBar.menu[3].isEnabled = false
incognitoBtn.isEnabled = false
withContext(Dispatchers.IO) {
downloadViewModel.updateProcessingIncognito(false)
withContext(Dispatchers.Main){
@ -344,18 +361,18 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
m.isEnabled = true
}
}
bottomAppBar.menu[3].icon?.alpha = 30
bottomAppBar.menu[3].isEnabled = true
incognitoBtn.icon?.alpha = 30
incognitoBtn.isEnabled = true
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.disabled)}", Toast.LENGTH_SHORT).show()
}else{
bottomAppBar.menu[3].isEnabled = false
incognitoBtn.isEnabled = false
withContext(Dispatchers.IO) {
downloadViewModel.updateProcessingIncognito(true)
withContext(Dispatchers.Main){
}
}
bottomAppBar.menu[3].icon?.alpha = 255
bottomAppBar.menu[3].isEnabled = true
incognitoBtn.icon?.alpha = 255
incognitoBtn.isEnabled = true
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)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[4].icon?.alpha = 255
formatBtn.icon?.alpha = 255
moreBtn.icon?.alpha = 255
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)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[4].icon?.alpha = 255
formatBtn.icon?.alpha = 255
moreBtn.icon?.alpha = 255
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)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
bottomAppBar.menu[4].icon?.alpha = 30
formatBtn.icon?.alpha = 255
moreBtn.icon?.alpha = 30
bottomSheet.cancel()
containerBtn.isVisible = false
}
}
}
@ -676,6 +714,41 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
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){
@ -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
}
}

View file

@ -55,7 +55,6 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
private lateinit var selectBetween: MenuItem
private lateinit var resultItemIDs: List<Long>
private lateinit var itemURLs: List<String>
private var items = listOf<ResultItem>()
override fun onCreate(savedInstanceState: Bundle?) {
@ -179,9 +178,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
ok.isEnabled = false
lifecycleScope.launch(Dispatchers.IO) {
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){
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0].url)!!
val resultItem = resultViewModel.getByID(checkedResultItems[0].id)!!
withContext(Dispatchers.Main){
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadBottomSheetDialog, bundleOf(
Pair("result", resultItem),
@ -223,8 +222,8 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
if(selectedItems.size != 2){
m.isVisible = false
}else{
val item2 = itemURLs.indexOf(selectedItems.last())
val item1 = itemURLs.indexOf(selectedItems.first())
val item2 = resultItemIDs.indexOf(selectedItems.last())
val item1 = resultItemIDs.indexOf(selectedItems.first())
if(item1 > item2) listAdapter.checkRange(item2, item1)
else listAdapter.checkRange(item1, item2)
count.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
@ -249,7 +248,7 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
toTextInput.isEnabled = !isLoading
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){
count.text = "(${listAdapter.getCheckedItems().size}) ${resources.getString(R.string.all_items_selected)} "
}else{
@ -302,8 +301,8 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
val size = checkedItems.size
if(size != 2) false
else {
val item1 = itemURLs.indexOf(checkedItems.first())
val item2 = itemURLs.indexOf(checkedItems.last())
val item1 = resultItemIDs.indexOf(checkedItems.first())
val item2 = resultItemIDs.indexOf(checkedItems.last())
(item1-item2).absoluteValue > 1
}

View file

@ -167,12 +167,17 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
}
lifecycleScope.launch {
val activeQueuedDownloadsCount = withContext(Dispatchers.IO) {
downloadViewModel.getActiveQueuedDownloadsCount()
val activeCount = withContext(Dispatchers.IO) {
downloadViewModel.getActiveDownloadsCount()
}
val queuedCount = withContext(Dispatchers.IO) {
downloadViewModel.getQueuedDownloadsCount()
}
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
pause.isVisible = activeQueuedDownloadsCount > 0 && !pausedDownloads
resume.isVisible = activeQueuedDownloadsCount > 0 && pausedDownloads
pause.isVisible = (queuedCount > 0 || activeCount > 0) && !pausedDownloads
resume.isVisible = (queuedCount > 0 || activeCount > 0) && pausedDownloads
}
lifecycleScope.launch {

View file

@ -15,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.work.WorkManager
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
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.terminal.TerminalActivity
import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.NotificationUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.system.exitProcess
class MoreFragment : Fragment() {
@ -125,28 +129,40 @@ class MoreFragment : Fragment() {
doNotShowAgain = compoundButton.isChecked
}
val workManager = WorkManager.getInstance(requireContext())
val notificationUtil = NotificationUtil(requireContext())
terminateDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
terminateDialog.setPositiveButton(getString(R.string.ok)) { diag: DialogInterface?, _: Int ->
runBlocking {
val job : Job = lifecycleScope.launch(Dispatchers.IO) {
val activeDownloads = downloadViewModel.getActiveDownloads().toMutableList()
activeDownloads.map { it.status = DownloadRepository.Status.Queued.toString() }
activeDownloads.forEach {
downloadViewModel.updateDownload(it)
}
lifecycleScope.launch {
val activeDownloads = withContext(Dispatchers.IO){
downloadViewModel.getActiveDownloadsCount()
}
runBlocking {
job.join()
if (doNotShowAgain){
mainSharedPreferencesEditor.putBoolean("ask_terminate_app", false)
mainSharedPreferencesEditor.commit()
if (activeDownloads > 0) {
workManager.cancelAllWorkByTag("download")
val activeDownloadsList = withContext(Dispatchers.IO){
downloadViewModel.getActiveDownloads()
}
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()
}else{

View file

@ -20,10 +20,12 @@ import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
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.setCustomTextSize
import com.google.android.material.appbar.MaterialToolbar
@ -104,8 +106,14 @@ class DownloadLogFragment : Fragment() {
}
}
content.isFocusable = true
content.enableTextHighlight()
lifecycleScope.launch(Dispatchers.IO){
content.isFocusable = true
content.enableTextHighlight()
}
contentScrollView.enableFastScroll()
val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
when(m.itemId){

View file

@ -28,6 +28,7 @@ import android.view.ViewOutlineProvider
import android.view.animation.Interpolator
import android.widget.EditText
import android.widget.ImageView
import android.widget.ScrollView
import android.widget.TextView
import androidx.annotation.Px
import androidx.core.content.ContextCompat
@ -139,6 +140,15 @@ object Extensions {
.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 {
return kotlin.runCatching {
if (!exists()) return 0
@ -251,20 +261,20 @@ object Extensions {
}
}
fun String.appendLineToLog(line: String): String {
val lines = this.split("\n")
if (!lines.takeLast(3).contains(line)){
//clean dublicate progress + add newline
var newLine = line
if (newLine.contains("[download")) {
newLine = "[download]" + line.split("[download]").last()
}
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
}
return this
}
// fun String.appendLineToLog(line: String): String {
// val lines = this.split("\n")
// if (!lines.takeLast(3).contains(line)){
// //clean dublicate progress + add newline
// var newLine = line
// if (newLine.contains("[download")) {
// newLine = "[download]" + line.split("[download]").last()
// }
//
// return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
// }
//
// return this
// }
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
if(!hideThumb){

View file

@ -97,11 +97,15 @@ class DownloadWorker(
val running = ArrayList(runningYTDLInstances)
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 (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collect
}
}
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
@ -138,15 +142,16 @@ class DownloadWorker(
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(
0,
downloadItem.title.ifBlank { downloadItem.url },
"Downloading:\n" +
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
"Command: $logString",
logString.toString(),
downloadItem.format,
downloadItem.type,
System.currentTimeMillis(),
@ -182,7 +187,7 @@ class DownloadWorker(
resultRepo.updateDownloadItem(downloadItem)?.apply {
dao.updateWithoutUpsert(this)
}
val wasQuickDownloaded = resultDao.getCountInt() == 0
//val wasQuickDownloaded = resultDao.getCountInt() == 0
runBlocking {
var finalPaths : MutableList<String>?
@ -304,7 +309,7 @@ class DownloadWorker(
dao.delete(downloadItem.id)
if (logDownloads){
logRepo.update(it.out, logItem.id)
logRepo.update(initialLogDetails + "\n" + it.out, logItem.id, true)
}
}

View file

@ -88,11 +88,12 @@ class TerminalDownloadWorker(
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
val initialLogDetails = "Terminal Task\n" +
"Command: \n ${command}\n\n"
val logItem = LogItem(
0,
"Terminal Task",
"Terminal Task\n" +
"Command: \n ${command}\n\n",
initialLogDetails,
Format(),
DownloadViewModel.Type.command,
System.currentTimeMillis(),
@ -139,8 +140,7 @@ class TerminalDownloadWorker(
}
}
}
if (logDownloads) logRepo.update(it.out, logItem.id)
if (logDownloads) logRepo.update(initialLogDetails + "\n" + it.out, logItem.id, true)
dao.updateLog(it.out, itemId.toLong())
notificationUtil.cancelDownloadNotification(itemId)
delay(1000)

View 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"/>

View file

@ -187,6 +187,27 @@
app:layout_constraintStart_toStartOf="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
android:id="@+id/codec"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"

View file

@ -38,7 +38,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
<me.zhanghai.android.fastscroll.FastScrollScrollView
android:id="@+id/content_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -65,7 +65,7 @@
</LinearLayout>
</ScrollView>
</me.zhanghai.android.fastscroll.FastScrollScrollView>
</FrameLayout>

View file

@ -22,6 +22,12 @@
android:icon="@drawable/baseline_folder_24"
app:showAsAction="always"/>
<item
android:id="@+id/container"
android:title="@string/container"
app:actionLayout="@layout/bottom_app_bar_text"
app:showAsAction="always"/>
<item
android:id="@+id/incognito"
android:title="@string/incognito"

View file

@ -149,7 +149,7 @@
android:defaultValue=""
android:entries="@array/cleanup_leftover_downloads"
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:useSimpleSummaryProvider="true"
app:title="@string/cleanup_leftover_downloads" />

View file

@ -1,87 +1,43 @@
TEMPORARY just to save changelog
# What's Changed
List is empty.
kotlin.collections.CollectionsKt.first(SourceFile:2)
com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.checkIfAllProcessingItemsHaveSameType(Unknown Source:21)
com.deniscerri.ytdl.ui.downloadcard.DownloadMultipleBottomSheetDialog$setupDialog$9$3$res$1.invokeSuspend(SourceFile:18)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(SourceFile:9)
kotlinx.coroutines.DispatchedTask.run(Unknown Source:96)
androidx.work.Worker$2.run(SourceFile:39)
kotlinx.coroutines.scheduling.TaskImpl.run(Unknown Source:2)
kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(SourceFile:96)
add option in download settings to use alarm manager instead of workmanager for scheduled tasks
fix audio format importance
When updating formats for multiple items, now the app remembers what items dont need new formats and skips them
Move format source from the bottom of the format list to the filter bottom sheet
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
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
- 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
- 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
- Moved format source from the bottom of the format list to the filter bottom sheet
- 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
- Moved "In Queue" screen back in its separate tab due to recyclerview performance issues
- Format updating in multiple download card now applies updates immediately on each item
- Deleted saved downloads after a format update in the background after the user decided to open for review
- 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
- 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
- Added Write URI Permission when opening files
- Fixed App still updating formats even though the user closed the format bottom sheet
- 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
- 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
- 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
- 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+)"
Removed NA from meta album artist --parse-metadata "%(artist,uploader|)s:^(?P<meta_album_artist>[^,]*)"
Add --recode-video toggle in the settings to use instead of --merge-output-format
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
download_rescheduled_to shows up twice, one in original language and one in english
It's very difficult to tap on video icon for the video at the bottom
#529
https://www.instagram.com/reel/C9zTRlONU6G/?igsh=MWJ1djBhYjdscG5wMw== chooses smallest format
Limit audio id length
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)
- 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
- 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
- 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
- Made home recyclerview bottom padding bigger to avoid the FABs
- Fixed Issue #529
- App now remembers the scroll position in the download history tab
- Made piped api not fetch data when fed mix playlists
- Turned the incognito toast to a snackbar in the download card
- Fixed duplicate dialog crashing app when in share activity
- 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.
- Added ability to change the container in the multiple download card
- Fixed App issues with download logs
- Added preferred language in the format sorter in yt-dlp
- More other small changes i forgor what i fixed