diff --git a/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt index ac180500..d16b5214 100644 --- a/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt @@ -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 diff --git a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt index 515257c1..ec647dc7 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/dao/DownloadDao.kt @@ -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() diff --git a/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt index 37bc0d4d..e23931af 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/repository/DownloadRepository.kt @@ -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){ downloadDao.deleteAllWithIDs(ids) @@ -214,7 +222,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) { } @SuppressLint("RestrictedApi") - suspend fun startDownloadWorker(queuedItems: List, context: Context, inputData: Data.Builder = Data.Builder()) { + suspend fun startDownloadWorker(queuedItems: List, context: Context, inputData: Data.Builder = Data.Builder()) : Result { 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()) } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt index a1cf5e72..3834ea6c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/viewmodel/DownloadViewModel.kt @@ -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) { 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, ignoreDuplicates : Boolean = false) { + data class QueueDownloadsResult( + var message: String, + var duplicateDownloadIDs : List + ) + + suspend fun queueDownloads(items: List, ignoreDuplicates : Boolean = false) : QueueDownloadsResult { val context = App.instance val alarmScheduler = AlarmScheduler(context) val queuedItems = mutableListOf() @@ -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 { diff --git a/app/src/main/java/com/deniscerri/ytdl/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdl/receiver/ShareActivity.kt index 36200238..75fc7237 100644 --- a/app/src/main/java/com/deniscerri/ytdl/receiver/ShareActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdl/receiver/ShareActivity.kt @@ -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() - } - } } } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/BaseActivity.kt b/app/src/main/java/com/deniscerri/ytdl/ui/BaseActivity.kt index 4fa5a8da..b5e8f696 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/BaseActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/BaseActivity.kt @@ -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() { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt index 44996f50..606063fe 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt @@ -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() } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt index 9876e6ed..477c4cdc 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadBottomSheetDialog.kt @@ -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) { + if (activity is ShareActivity && res.isNotEmpty()) { + //let the lifecycle listener handle it + }else{ + dismiss() + } + } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index d5c0ac41..086a9a60 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -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 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) { + if (activity is ShareActivity && res.isNotEmpty()) { + //let the lifecycle listener handle it + }else{ + dismiss() + } + } + } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadsAlreadyExistDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadsAlreadyExistDialog.kt index 6d9cd033..cd9fdded 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadsAlreadyExistDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/DownloadsAlreadyExistDialog.kt @@ -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(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() } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt index 3fea7e27..0a6e3d7a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/FormatSelectionBottomSheetDialog.kt @@ -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 -> { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/SelectPlaylistItemsDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/SelectPlaylistItemsDialog.kt index f227ffe6..5d04605c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/SelectPlaylistItemsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/SelectPlaylistItemsDialog.kt @@ -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), diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt index 037ffab8..a832e4d4 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/DownloadQueueMainFragment.kt @@ -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 diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt index 934408af..0e395778 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloads/HistoryFragment.kt @@ -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() } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt index d453f1e2..0ead49e4 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/DownloadSettingsFragment.kt @@ -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("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() + .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("use_alarm_for_scheduling") diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt index 2863555a..30dd34bd 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/FolderSettingsFragment.kt @@ -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 } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/FormatSorter.kt b/app/src/main/java/com/deniscerri/ytdl/util/FormatSorter.kt index b94832ac..e84b79d7 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/FormatSorter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/FormatSorter.kt @@ -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) } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt index 28848d00..780cdd58 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/InfoUtil.kt @@ -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() // -------------- 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") } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt index 5b21e164..ada4893b 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.kt @@ -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) diff --git a/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt b/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt new file mode 100644 index 00000000..709ce891 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/work/CleanUpLeftoverDownloads.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_home.xml b/app/src/main/res/layout-land/fragment_home.xml index 218d92a4..c514ab7e 100644 --- a/app/src/main/res/layout-land/fragment_home.xml +++ b/app/src/main/res/layout-land/fragment_home.xml @@ -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" /> diff --git a/app/src/main/res/layout-sw600dp-land/fragment_home.xml b/app/src/main/res/layout-sw600dp-land/fragment_home.xml index 8b1992d4..b098a7f4 100644 --- a/app/src/main/res/layout-sw600dp-land/fragment_home.xml +++ b/app/src/main/res/layout-sw600dp-land/fragment_home.xml @@ -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" /> diff --git a/app/src/main/res/layout-sw600dp/fragment_home.xml b/app/src/main/res/layout-sw600dp/fragment_home.xml index b63a255b..d5b4b793 100644 --- a/app/src/main/res/layout-sw600dp/fragment_home.xml +++ b/app/src/main/res/layout-sw600dp/fragment_home.xml @@ -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" /> diff --git a/app/src/main/res/layout-sw950dp-land/fragment_home.xml b/app/src/main/res/layout-sw950dp-land/fragment_home.xml index 1f0606f3..c9bc206f 100644 --- a/app/src/main/res/layout-sw950dp-land/fragment_home.xml +++ b/app/src/main/res/layout-sw950dp-land/fragment_home.xml @@ -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" /> diff --git a/app/src/main/res/layout/format_item.xml b/app/src/main/res/layout/format_item.xml index f63f1e31..0fa989b2 100644 --- a/app/src/main/res/layout/format_item.xml +++ b/app/src/main/res/layout/format_item.xml @@ -105,25 +105,25 @@ + app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index f8f86631..7673302e 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -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" /> diff --git a/app/src/main/res/layout/fragment_inqueue.xml b/app/src/main/res/layout/fragment_inqueue.xml index 54a3a860..28971d19 100644 --- a/app/src/main/res/layout/fragment_inqueue.xml +++ b/app/src/main/res/layout/fragment_inqueue.xml @@ -31,6 +31,7 @@ + + android:label="DownloadMultipleBottomSheetDialog" > + + Rikodo Videon Rikodon skedarin e videos sipas formatit të cilësuar nga përdoruesi 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. + Çdo ditë + Çdo javë + Çdo muaj \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 396865fd..815c602c 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1475,4 +1475,18 @@ never + + @string/disabled + @string/daily + @string/weekly + @string/monthly + + + + + daily + weekly + monthly + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f23eefe1..9c6d70a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -414,4 +414,8 @@ Useful for Libretube offline playlists that are not recognised by yt-dlp. Playlist metadata wont be embedded with this enabled Recode Video Recodes the video file to the specified video format + Daily + Weekly + Monthly + Clean-up leftover downloads (cancelled, errored) \ No newline at end of file diff --git a/app/src/main/res/xml/downloading_preferences.xml b/app/src/main/res/xml/downloading_preferences.xml index ceaa77d8..52f2d9da 100644 --- a/app/src/main/res/xml/downloading_preferences.xml +++ b/app/src/main/res/xml/downloading_preferences.xml @@ -145,6 +145,15 @@ app:summary="@string/log_downloads_summary" app:title="@string/log_downloads" /> + +