more fixes and features
This commit is contained in:
parent
f4f527721c
commit
b45ddbeffc
33 changed files with 437 additions and 116 deletions
|
|
@ -23,6 +23,7 @@ import android.widget.Toast
|
|||
import androidx.annotation.RequiresApi
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isVisible
|
||||
|
|
@ -63,9 +64,11 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE status='Processing'")
|
||||
suspend fun deleteProcessing()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Duplicate'")
|
||||
suspend fun deleteWithDuplicateStatus()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Scheduled'")
|
||||
suspend fun deleteScheduled()
|
||||
|
||||
|
|
|
|||
|
|
@ -174,6 +174,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
deleteCache(cancelled)
|
||||
}
|
||||
|
||||
fun getActiveDownloadsCount() : Int {
|
||||
return downloadDao.getDownloadsCountByStatus(listOf(Status.Active).toListString())
|
||||
}
|
||||
|
||||
suspend fun deleteScheduled() {
|
||||
downloadDao.deleteScheduled()
|
||||
}
|
||||
|
|
@ -196,6 +200,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
downloadDao.deleteProcessing()
|
||||
}
|
||||
|
||||
suspend fun deleteWithDuplicateStatus() {
|
||||
downloadDao.deleteWithDuplicateStatus()
|
||||
}
|
||||
|
||||
suspend fun deleteAllWithIDs(ids: List<Long>){
|
||||
downloadDao.deleteAllWithIDs(ids)
|
||||
|
||||
|
|
@ -214,7 +222,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context, inputData: Data.Builder = Data.Builder()) {
|
||||
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context, inputData: Data.Builder = Data.Builder()) : Result<String> {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
|
|
@ -236,7 +244,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
|
||||
if (delay > 0L && useAlarmForScheduling) {
|
||||
AlarmScheduler(context).scheduleAt(queuedItems.minBy { it.downloadStartTime }.downloadStartTime)
|
||||
return
|
||||
return Result.success("")
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -256,26 +264,22 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
)
|
||||
|
||||
}
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val message = StringBuilder()
|
||||
|
||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
||||
}, 0)
|
||||
message.appendLine(context.getString(R.string.metered_network_download_start_info))
|
||||
}
|
||||
|
||||
if (queuedItems.isNotEmpty()) {
|
||||
val first = queuedItems.first()
|
||||
if (first.downloadStartTime > 0L) {
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(queuedItems.first().downloadStartTime)
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
}, 0)
|
||||
message.appendLine(context.getString(R.string.download_rescheduled_to) + " " + date)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(message.toString())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -638,6 +638,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.deleteProcessing()
|
||||
}
|
||||
|
||||
fun deleteWithDuplicateStatus() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteWithDuplicateStatus()
|
||||
}
|
||||
|
||||
suspend fun deleteAllWithID(ids: List<Long>) {
|
||||
repository.deleteAllWithIDs(ids)
|
||||
}
|
||||
|
|
@ -671,7 +675,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
fun getActiveDownloadsCount() : Int {
|
||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Active).toListString())
|
||||
return repository.getActiveDownloadsCount()
|
||||
}
|
||||
|
||||
fun getActiveQueuedDownloadsCount() : Int {
|
||||
|
|
@ -761,12 +765,17 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun queueProcessingDownloads(){
|
||||
suspend fun queueProcessingDownloads() : QueueDownloadsResult {
|
||||
val processingItems = repository.getProcessingDownloads()
|
||||
queueDownloads(processingItems)
|
||||
return queueDownloads(processingItems)
|
||||
}
|
||||
|
||||
suspend fun queueDownloads(items: List<DownloadItem>, ignoreDuplicates : Boolean = false) {
|
||||
data class QueueDownloadsResult(
|
||||
var message: String,
|
||||
var duplicateDownloadIDs : List<AlreadyExistsIDs>
|
||||
)
|
||||
|
||||
suspend fun queueDownloads(items: List<DownloadItem>, ignoreDuplicates : Boolean = false) : QueueDownloadsResult {
|
||||
val context = App.instance
|
||||
val alarmScheduler = AlarmScheduler(context)
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
|
|
@ -788,10 +797,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.Scheduled.toString()) {
|
||||
if (it.downloadStartTime > 0) {
|
||||
it.status = DownloadRepository.Status.Scheduled.toString()
|
||||
}else{
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
}
|
||||
|
||||
|
||||
//CHECK DUPLICATES
|
||||
var isDuplicate = false
|
||||
if (checkDuplicate.isNotEmpty() && !ignoreDuplicates){
|
||||
|
|
@ -903,22 +915,23 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
}
|
||||
|
||||
val queued = repository.updateAll(queuedItems)
|
||||
val result = QueueDownloadsResult("", listOf())
|
||||
|
||||
//if scheduler is on
|
||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
||||
if (alarmScheduler.canSchedule()){
|
||||
repository.updateAll(queuedItems)
|
||||
alarmScheduler.schedule()
|
||||
}else{
|
||||
sharedPreferences.edit().putBoolean("use_scheduler", false).apply()
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(context, context.getString(R.string.enable_alarm_permission), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
result.message = context.getString(R.string.enable_alarm_permission)
|
||||
}
|
||||
}else{
|
||||
val queued = repository.updateAll(queuedItems)
|
||||
|
||||
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
|
||||
repository.startDownloadWorker(queued, context)
|
||||
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
|
||||
}
|
||||
|
||||
if(!useScheduler){
|
||||
|
|
@ -947,7 +960,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
if (existingItemIDs.isNotEmpty()){
|
||||
alreadyExistsUiState.value = existingItemIDs.toList()
|
||||
result.duplicateDownloadIDs = existingItemIDs.toList()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun getQueuedCollectedFileSize() : Long {
|
||||
|
|
@ -1077,13 +1093,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updateProcessingDownloadTimeAndQueueScheduled(time: Long) {
|
||||
suspend fun updateProcessingDownloadTimeAndQueueScheduled(time: Long) : QueueDownloadsResult {
|
||||
val processing = repository.getProcessingDownloads()
|
||||
processing.forEach {
|
||||
it.downloadStartTime = time
|
||||
it.status = DownloadRepository.Status.Scheduled.toString()
|
||||
}
|
||||
queueDownloads(processing)
|
||||
return queueDownloads(processing)
|
||||
}
|
||||
|
||||
fun checkIfAllProcessingItemsHaveSameType() : Pair<Boolean, Type> {
|
||||
|
|
|
|||
|
|
@ -213,18 +213,6 @@ class ShareActivity : BaseActivity() {
|
|||
}
|
||||
this@ShareActivity.finish()
|
||||
}
|
||||
|
||||
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
|
||||
if (res.isNotEmpty()){
|
||||
withContext(Dispatchers.Main){
|
||||
val bundle = bundleOf(
|
||||
Pair("duplicates", res)
|
||||
)
|
||||
navController.navigate(R.id.downloadsAlreadyExistDialog2, bundle)
|
||||
}
|
||||
downloadViewModel.alreadyExistsUiState.value = mutableListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,19 @@ import android.provider.Settings
|
|||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: Downlo
|
|||
incognito = !incognito
|
||||
fragmentAdapter.isIncognito = incognito
|
||||
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
|
||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: $onOff", Toast.LENGTH_SHORT).show()
|
||||
Snackbar.make(incognitoBtn, "${getString(R.string.incognito)}: $onOff", Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
|||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.receiver.ShareActivity
|
||||
import com.deniscerri.ytdl.ui.BaseActivity
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
|
|
@ -80,6 +81,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
private lateinit var title : View
|
||||
private lateinit var shimmerLoadingSubtitle : ShimmerFrameLayout
|
||||
private lateinit var subtitle : View
|
||||
private lateinit var parentActivity: BaseActivity
|
||||
|
||||
|
||||
private lateinit var result: ResultItem
|
||||
|
|
@ -95,7 +97,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
val res: ResultItem?
|
||||
val dwl: DownloadItem?
|
||||
|
||||
|
|
@ -131,6 +132,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
parentActivity = activity as BaseActivity
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
@ -331,21 +333,34 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
audioDownloadItem.status = DownloadRepository.Status.Scheduled.toString()
|
||||
itemsToQueue.add(audioDownloadItem)
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(itemsToQueue)
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
lifecycleScope.launch {
|
||||
val result = withContext(Dispatchers.IO){
|
||||
downloadViewModel.queueDownloads(itemsToQueue)
|
||||
}
|
||||
|
||||
if (result.message.isNotBlank()){
|
||||
Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}else{
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
dismiss()
|
||||
lifecycleScope.launch {
|
||||
val result = withContext(Dispatchers.IO){
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
|
||||
if (result.message.isNotBlank()){
|
||||
Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -365,15 +380,17 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
itemsToQueue.add(it)
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(itemsToQueue)
|
||||
val result = downloadViewModel.queueDownloads(itemsToQueue)
|
||||
withContext(Dispatchers.Main){
|
||||
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}else{
|
||||
runBlocking {
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
dismiss()
|
||||
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -441,7 +458,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
incognito = !incognito
|
||||
fragmentAdapter.isIncognito = incognito
|
||||
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
|
||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: $onOff", Toast.LENGTH_SHORT).show()
|
||||
Snackbar.make(incognitoBtn, "${getString(R.string.incognito)}: $onOff", Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -584,6 +601,23 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
launch{
|
||||
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
|
||||
if (res.isNotEmpty() && activity is ShareActivity){
|
||||
withContext(Dispatchers.Main){
|
||||
val bundle = bundleOf(
|
||||
Pair("duplicates", ArrayList(res))
|
||||
)
|
||||
delay(500)
|
||||
findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_downloadsAlreadyExistDialog2, bundle)
|
||||
}
|
||||
downloadViewModel.alreadyExistsUiState.value = mutableListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
resultViewModel.updateFormatsResultData.collectLatest { formats ->
|
||||
if (formats == null) return@collectLatest
|
||||
|
|
@ -686,5 +720,13 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
super.onDismiss(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDuplicatesAndDismiss(res: List<DownloadViewModel.AlreadyExistsIDs>) {
|
||||
if (activity is ShareActivity && res.isNotEmpty()) {
|
||||
//let the lifecycle listener handle it
|
||||
}else{
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@ import android.view.Window
|
|||
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
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
|
@ -37,6 +39,8 @@ import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
|
|||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.receiver.ShareActivity
|
||||
import com.deniscerri.ytdl.ui.BaseActivity
|
||||
import com.deniscerri.ytdl.ui.adapter.ConfigureMultipleDownloadsAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
|
|
@ -56,6 +60,7 @@ import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
|||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -80,6 +85,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
private lateinit var shimmerTitle: ShimmerFrameLayout
|
||||
private lateinit var shimmerSubtitle: ShimmerFrameLayout
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private lateinit var parentActivity: BaseActivity
|
||||
|
||||
private lateinit var currentDownloadIDs: List<Long>
|
||||
private var processingItemsCount : Int = 0
|
||||
|
|
@ -103,6 +109,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
parentActivity = activity as BaseActivity
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
@ -204,9 +211,20 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis)
|
||||
val result = downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis)
|
||||
if (result.message.isNotBlank()){
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -216,9 +234,20 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
downloadViewModel.queueProcessingDownloads()
|
||||
val result = downloadViewModel.queueProcessingDownloads()
|
||||
if (result.message.isNotBlank()){
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +313,23 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
launch{
|
||||
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
|
||||
if (res.isNotEmpty() && activity is ShareActivity){
|
||||
withContext(Dispatchers.Main){
|
||||
val bundle = bundleOf(
|
||||
Pair("duplicates", ArrayList(res))
|
||||
)
|
||||
delay(500)
|
||||
findNavController().navigate(R.id.action_downloadMultipleBottomSheetDialog_to_downloadsAlreadyExistDialog2, bundle)
|
||||
}
|
||||
downloadViewModel.alreadyExistsUiState.value = mutableListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
|
|
@ -910,5 +956,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleDuplicatesAndDismiss(res: List<DownloadViewModel.AlreadyExistsIDs>) {
|
||||
if (activity is ShareActivity && res.isNotEmpty()) {
|
||||
//let the lifecycle listener handle it
|
||||
}else{
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
|
|
@ -28,6 +29,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -102,10 +104,17 @@ class DownloadsAlreadyExistDialog : BottomSheetDialogFragment(), AlreadyExistsAd
|
|||
|
||||
view.findViewById<MaterialButton>(R.id.bottomsheet_download_button).setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.deleteProcessing()
|
||||
downloadViewModel.deleteWithDuplicateStatus()
|
||||
val items = duplicates.map { it.downloadItem }
|
||||
items.forEach { it.id = 0 }
|
||||
downloadViewModel.queueDownloads(items, true)
|
||||
val result = downloadViewModel.queueDownloads(items, true)
|
||||
if (result.message.isNotBlank()){
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
dismiss()
|
||||
}
|
||||
|
|
@ -117,8 +126,7 @@ class DownloadsAlreadyExistDialog : BottomSheetDialogFragment(), AlreadyExistsAd
|
|||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.deleteProcessing()
|
||||
downloadViewModel.deleteAllWithID(duplicates.map { it.downloadItem.id })
|
||||
downloadViewModel.deleteWithDuplicateStatus()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -504,7 +504,10 @@ class FormatSelectionBottomSheetDialog(
|
|||
finalFormats = if (items.first()?.type == Type.audio){
|
||||
formatSorter.sortAudioFormats(finalFormats)
|
||||
}else{
|
||||
formatSorter.sortVideoFormats(finalFormats)
|
||||
val audioFormats = finalFormats.filter { it.vcodec.isBlank() || it.vcodec == "none" }
|
||||
val videoFormats = finalFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }
|
||||
|
||||
formatSorter.sortVideoFormats(videoFormats) + formatSorter.sortAudioFormats(audioFormats)
|
||||
}
|
||||
}
|
||||
FormatCategory.SMALLEST -> {
|
||||
|
|
|
|||
|
|
@ -179,9 +179,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.url) }
|
||||
if (checkedResultItems.size == 1){
|
||||
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0]!!.url)!!
|
||||
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0].url)!!
|
||||
withContext(Dispatchers.Main){
|
||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadBottomSheetDialog, bundleOf(
|
||||
Pair("result", resultItem),
|
||||
|
|
|
|||
|
|
@ -1,23 +1,15 @@
|
|||
package com.deniscerri.ytdl.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -26,12 +18,10 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.inflate
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.downloadcard.DownloadAudioFragment
|
||||
import com.deniscerri.ytdl.util.Extensions.createBadge
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
|
|
@ -42,7 +32,6 @@ import com.google.android.material.tabs.TabLayout
|
|||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
|
|||
|
|
@ -164,10 +164,11 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
historyViewModel.getFilteredList().observe(viewLifecycleOwner) {
|
||||
historyAdapter!!.submitList(it)
|
||||
historyList = it
|
||||
scrollToTop()
|
||||
|
||||
if (recyclerView.adapter == null){
|
||||
recyclerView.adapter = historyAdapter
|
||||
}else{
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.interaction.DragInteraction
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
|
|
@ -13,12 +14,14 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
|
@ -69,6 +72,42 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
|||
archivePathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
|
||||
val workManager = WorkManager.getInstance(requireContext())
|
||||
val cleanupLeftoverDownloads = findPreference<Preference>("cleanup_leftover_downloads")
|
||||
cleanupLeftoverDownloads?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
var nextTime : Calendar? = Calendar.getInstance()
|
||||
when(newValue) {
|
||||
"daily" -> nextTime?.add(Calendar.DAY_OF_WEEK, 1)
|
||||
"weekly" -> nextTime?.add(Calendar.DAY_OF_WEEK, 7)
|
||||
"monthly" -> nextTime?.add(Calendar.MONTH, 1)
|
||||
else -> nextTime = null
|
||||
}
|
||||
|
||||
if (nextTime == null) workManager.cancelAllWorkByTag("cleanup_leftover_downloads")
|
||||
else {
|
||||
val workConstraints = Constraints.Builder()
|
||||
val allowMeteredNetworks = preferences.getBoolean("metered_networks", true)
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
||||
val delay = nextTime.timeInMillis.minus(System.currentTimeMillis())
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<CleanUpLeftoverDownloads>()
|
||||
.addTag("cleanup_leftover_downloads")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
val scheduler = AlarmScheduler(requireContext())
|
||||
|
||||
val useAlarmManagerInsteadOfWorkManager = findPreference<SwitchPreferenceCompat>("use_alarm_for_scheduling")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import android.os.Environment
|
|||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
|
|
@ -19,10 +21,14 @@ import androidx.work.OneTimeWorkRequestBuilder
|
|||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.MoveCacheFilesWorker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
|
@ -43,6 +49,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private var activeDownloadCount = 0
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
|
@ -50,6 +57,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
editor = preferences.edit()
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
|
||||
musicPath = findPreference("music_path")
|
||||
videoPath = findPreference("video_path")
|
||||
|
|
@ -166,13 +174,18 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
clearCache!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
if (activeDownloadCount == 0){
|
||||
File(FileUtil.getCachePath(requireContext())).deleteRecursively()
|
||||
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
||||
cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
}else{
|
||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
||||
lifecycleScope.launch {
|
||||
activeDownloadCount = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
}
|
||||
if (activeDownloadCount == 0){
|
||||
File(FileUtil.getCachePath(requireContext())).deleteRecursively()
|
||||
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
||||
cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
}else{
|
||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,11 +142,10 @@ class FormatSorter(private var context: Context) {
|
|||
|
||||
val aIndex = videoResolutionOrder.indexOfFirst { a.format_note.contains(it, ignoreCase = true) }
|
||||
val bIndex = videoResolutionOrder.indexOfFirst { b.format_note.contains(it, ignoreCase = true) }
|
||||
|
||||
if (aIndex > preferenceIndex || bIndex > preferenceIndex) {
|
||||
-1
|
||||
}else if(aIndex == -1 && bIndex == -1){
|
||||
-1
|
||||
0
|
||||
}else{
|
||||
bIndex.compareTo(aIndex)
|
||||
}
|
||||
|
|
@ -168,7 +167,6 @@ class FormatSorter(private var context: Context) {
|
|||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return formats.sortedWith(fieldSorter)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Resources.NotFoundException
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
|
|
@ -26,6 +27,7 @@ import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
|||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLException
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -112,23 +114,37 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
|
||||
try{
|
||||
try {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
// -------------- PIPED API FUNCTION -------------------
|
||||
var url = ""
|
||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||
else """$pipedURL/nextpage/playlists/$id?nextpage=${nextPageToken.replace("&prettyPrint", "%26prettyPrint")}"""
|
||||
else """$pipedURL/nextpage/playlists/$id?nextpage=${
|
||||
nextPageToken.replace(
|
||||
"&prettyPrint",
|
||||
"%26prettyPrint"
|
||||
)
|
||||
}"""
|
||||
|
||||
println(url)
|
||||
|
||||
val res = genericRequest(url)
|
||||
if (!res.has("relatedStreams")) throw Exception()
|
||||
|
||||
val dataArray = res.getJSONArray("relatedStreams")
|
||||
var nextpage = res.getString("nextpage")
|
||||
for (i in 0 until dataArray.length()){
|
||||
val isMixPlaylist = nextPageToken.isBlank() && res.getInt("videos") < 0
|
||||
if (isMixPlaylist) throw YoutubeDLException("This playlist type is unviewable.")
|
||||
|
||||
for (i in 0 until dataArray.length()) {
|
||||
kotlin.runCatching {
|
||||
val obj = dataArray.getJSONObject(i)
|
||||
createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))?.apply {
|
||||
playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||
createVideoFromPipedJSON(
|
||||
obj,
|
||||
"https://youtube.com" + obj.getString("url")
|
||||
)?.apply {
|
||||
playlistTitle =
|
||||
runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||
playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||
items.add(this)
|
||||
}
|
||||
|
|
@ -137,11 +153,14 @@ class InfoUtil(private val context: Context) {
|
|||
if (nextpage == "null") nextpage = ""
|
||||
return PlaylistTuple(nextpage, items)
|
||||
}catch (e: Exception){
|
||||
Log.e("PLST", e.toString())
|
||||
return PlaylistTuple(
|
||||
"",
|
||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
||||
)
|
||||
if (e is YoutubeDLException) {
|
||||
throw(e)
|
||||
}else{
|
||||
return PlaylistTuple(
|
||||
"",
|
||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +221,7 @@ class InfoUtil(private val context: Context) {
|
|||
Html.fromHtml(obj.getString("uploader").toString()).toString()
|
||||
}catch (e: Exception){
|
||||
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
||||
}.replace(" - Topic", "")
|
||||
}.removeSuffix(" - Topic")
|
||||
|
||||
val duration = obj.getInt("duration").toStringDuration(Locale.US)
|
||||
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
||||
|
|
@ -1222,18 +1241,20 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
|
||||
val ext = downloadItem.container
|
||||
val preferredLanguage = sharedPreferences.getString("audio_language","")!!
|
||||
println(audioQualityId)
|
||||
if (audioQualityId.isNotBlank()) {
|
||||
if(audioQualityId.contains("-")){
|
||||
if (audioQualityId.matches(".*-[0-9]+.*".toRegex())) {
|
||||
audioQualityId = if(!downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){
|
||||
"ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b"
|
||||
}else{
|
||||
"$audioQualityId/${audioQualityId.split("-")[0]}"
|
||||
}
|
||||
}
|
||||
|
||||
request.addOption("-f", audioQualityId)
|
||||
}else{
|
||||
//enters here if generic or quick downloaded with ba format
|
||||
val preferredLanguage = sharedPreferences.getString("audio_language","")!!
|
||||
if (preferredLanguage.isNotBlank()){
|
||||
request.addOption("-f", "ba[language^=$preferredLanguage]/ba/b")
|
||||
}
|
||||
|
|
@ -1257,6 +1278,10 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if (preferredLanguage.isNotBlank()) {
|
||||
formatSorting.append(",lang:${preferredLanguage}")
|
||||
}
|
||||
|
||||
request.addOption("-P", downDir.absolutePath)
|
||||
request.addOption("-S", formatSorting.toString())
|
||||
|
||||
|
|
@ -1357,7 +1382,8 @@ class InfoUtil(private val context: Context) {
|
|||
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
|
||||
val format = downloadItem.allFormats.find { it.format_id == f }
|
||||
format?.run {
|
||||
if (this.format_id.contains("-")) {
|
||||
println(format_id)
|
||||
if (this.format_id.matches(".*-[0-9]+".toRegex())) {
|
||||
if (!this.lang.isNullOrBlank() && this.lang != "None") {
|
||||
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
|
||||
} else {
|
||||
|
|
@ -1477,13 +1503,11 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
}
|
||||
|
||||
val genericFormats = getGenericVideoFormats(context.resources).map { it.format_id }
|
||||
val preferredLanguage = sharedPreferences.getString("audio_language","")!!
|
||||
|
||||
StringBuilder().apply {
|
||||
if (hasGenericResulutionFormat.isNotBlank()) {
|
||||
append(",res:${hasGenericResulutionFormat}")
|
||||
}else if (genericFormats.contains(videoF) && preferredQuality!!.contains("p_")){
|
||||
append(",res:${preferredQuality.split("_")[0]}")
|
||||
}
|
||||
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
|
||||
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
||||
|
|
@ -1491,6 +1515,7 @@ class InfoUtil(private val context: Context) {
|
|||
if (cont.isNotBlank()) append(",vext:$cont")
|
||||
if (acont.isNotBlank()) append(",aext:$acont")
|
||||
if (abrSort.isNotBlank()) append(",abr~${abrSort}")
|
||||
if (preferredLanguage.isNotBlank()) append(",lang:${preferredLanguage}")
|
||||
if (this.isNotBlank()){
|
||||
request.addOption("-S", "+hasaud$this")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,6 +474,27 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.cancel(DOWNLOAD_ERRORED_NOTIFICATION_ID + id)
|
||||
}
|
||||
|
||||
fun createDeletingLeftoverDownloadsNotification() : Notification {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(resources.getString(R.string.cleanup_leftover_downloads))
|
||||
.setCategory(Notification.CATEGORY_EVENT)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
.setContentText("")
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.clearActions()
|
||||
.build()
|
||||
}
|
||||
|
||||
fun createMoveCacheFilesNotification(pendingIntent: PendingIntent?, downloadMiscChannelId: String): Notification {
|
||||
val notificationBuilder = getBuilder(downloadMiscChannelId)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.deniscerri.ytdl.work
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import com.deniscerri.ytdl.App
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import java.io.File
|
||||
|
||||
|
||||
class CleanUpLeftoverDownloads(
|
||||
private val context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
val notificationUtil = NotificationUtil(App.instance)
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
|
||||
val notification = notificationUtil.createDeletingLeftoverDownloadsNotification()
|
||||
val foregroundInfo = ForegroundInfo(id, notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val downloadRepo = DownloadRepository(dbManager.downloadDao)
|
||||
downloadRepo.deleteCancelled()
|
||||
downloadRepo.deleteErrored()
|
||||
|
||||
val activeDownloadCount = downloadRepo.getActiveDownloadsCount()
|
||||
if (activeDownloadCount == 0){
|
||||
File(FileUtil.getCachePath(context)).deleteRecursively()
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
android:paddingBottom="200dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
android:paddingBottom="200dp"
|
||||
app:spanCount="3"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
android:paddingBottom="200dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
android:paddingBottom="200dp"
|
||||
app:spanCount="4"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -105,25 +105,25 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/audio_formats"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||
android:backgroundTint="?attr/colorPrimary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textCursorDrawable="@string/audio_format"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:drawableStartCompat="@drawable/ic_music_formatcard"
|
||||
app:drawableTint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
|
|
@ -180,8 +180,7 @@
|
|||
android:ellipsize="end"
|
||||
android:gravity="bottom|end"
|
||||
android:maxWidth="70dp"
|
||||
android:maxLines="2"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/format_note"
|
||||
android:maxLines="1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
|
||||
android:paddingBottom="200dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/drag"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="10dp"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
app:launchSingleTop="true"
|
||||
app:popUpTo="@id/downloadBottomSheetDialog"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_downloadBottomSheetDialog_to_downloadsAlreadyExistDialog2"
|
||||
app:destination="@id/downloadsAlreadyExistDialog2"
|
||||
app:launchSingleTop="true"
|
||||
app:popUpTo="@id/downloadBottomSheetDialog"
|
||||
app:popUpToInclusive="true" />
|
||||
</dialog>
|
||||
<dialog
|
||||
android:id="@+id/selectPlaylistItemsDialog"
|
||||
|
|
@ -33,7 +39,14 @@
|
|||
<dialog
|
||||
android:id="@+id/downloadMultipleBottomSheetDialog"
|
||||
android:name="com.deniscerri.ytdl.ui.downloadcard.DownloadMultipleBottomSheetDialog"
|
||||
android:label="DownloadMultipleBottomSheetDialog" />
|
||||
android:label="DownloadMultipleBottomSheetDialog" >
|
||||
<action
|
||||
android:id="@+id/action_downloadMultipleBottomSheetDialog_to_downloadsAlreadyExistDialog2"
|
||||
app:destination="@id/downloadsAlreadyExistDialog2"
|
||||
app:launchSingleTop="true"
|
||||
app:popUpTo="@id/downloadMultipleBottomSheetDialog"
|
||||
app:popUpToInclusive="true" />
|
||||
</dialog>
|
||||
<dialog
|
||||
android:id="@+id/downloadsAlreadyExistDialog2"
|
||||
android:name="com.deniscerri.ytdl.ui.downloadcard.DownloadsAlreadyExistDialog"
|
||||
|
|
|
|||
|
|
@ -411,4 +411,7 @@
|
|||
<string name="recode_video">Rikodo Videon</string>
|
||||
<string name="recode_video_summary">Rikodon skedarin e videos sipas formatit të cilësuar nga përdoruesi</string>
|
||||
<string name="restore_info">Shtyp \'Rikthe\' për të kombinuar të dhënat e ruajtura me të dhënat aktuale. Shtyp \'Rivendos\' për të fshirë gjithçka dhe vetëm përdor të dhënat e ruajtura në skedar.</string>
|
||||
<string name="daily">Çdo ditë</string>
|
||||
<string name="weekly">Çdo javë</string>
|
||||
<string name="monthly">Çdo muaj</string>
|
||||
</resources>
|
||||
|
|
@ -1475,4 +1475,18 @@
|
|||
<item>never</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="cleanup_leftover_downloads">
|
||||
<item>@string/disabled</item>
|
||||
<item>@string/daily</item>
|
||||
<item>@string/weekly</item>
|
||||
<item>@string/monthly</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="cleanup_leftover_downloads_values">
|
||||
<item></item>
|
||||
<item>daily</item>
|
||||
<item>weekly</item>
|
||||
<item>monthly</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
@ -414,4 +414,8 @@
|
|||
<string name="use_item_url_not_playlist_summary">Useful for Libretube offline playlists that are not recognised by yt-dlp. Playlist metadata wont be embedded with this enabled</string>
|
||||
<string name="recode_video">Recode Video</string>
|
||||
<string name="recode_video_summary">Recodes the video file to the specified video format</string>
|
||||
<string name="daily">Daily</string>
|
||||
<string name="weekly">Weekly</string>
|
||||
<string name="monthly">Monthly</string>
|
||||
<string name="cleanup_leftover_downloads">Clean-up leftover downloads (cancelled, errored)</string>
|
||||
</resources>
|
||||
|
|
@ -145,6 +145,15 @@
|
|||
app:summary="@string/log_downloads_summary"
|
||||
app:title="@string/log_downloads" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue=""
|
||||
android:entries="@array/cleanup_leftover_downloads"
|
||||
android:entryValues="@array/cleanup_leftover_downloads_values"
|
||||
android:icon="@drawable/baseline_delete_24"
|
||||
app:key="cleanup_leftover_downloads"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/cleanup_leftover_downloads" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="retries"
|
||||
android:icon="@drawable/ic_retries"
|
||||
|
|
|
|||
|
|
@ -63,4 +63,25 @@ Add --recode-video toggle in the settings to use instead of --merge-output-forma
|
|||
|
||||
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 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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue