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.annotation.RequiresApi
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
|
@ -63,9 +64,11 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,9 @@ interface DownloadDao {
|
||||||
@Query("DELETE FROM downloads WHERE status='Processing'")
|
@Query("DELETE FROM downloads WHERE status='Processing'")
|
||||||
suspend fun deleteProcessing()
|
suspend fun deleteProcessing()
|
||||||
|
|
||||||
|
@Query("DELETE FROM downloads WHERE status='Duplicate'")
|
||||||
|
suspend fun deleteWithDuplicateStatus()
|
||||||
|
|
||||||
@Query("DELETE FROM downloads WHERE status='Scheduled'")
|
@Query("DELETE FROM downloads WHERE status='Scheduled'")
|
||||||
suspend fun deleteScheduled()
|
suspend fun deleteScheduled()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
deleteCache(cancelled)
|
deleteCache(cancelled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getActiveDownloadsCount() : Int {
|
||||||
|
return downloadDao.getDownloadsCountByStatus(listOf(Status.Active).toListString())
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun deleteScheduled() {
|
suspend fun deleteScheduled() {
|
||||||
downloadDao.deleteScheduled()
|
downloadDao.deleteScheduled()
|
||||||
}
|
}
|
||||||
|
|
@ -196,6 +200,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.deleteProcessing()
|
downloadDao.deleteProcessing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteWithDuplicateStatus() {
|
||||||
|
downloadDao.deleteWithDuplicateStatus()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun deleteAllWithIDs(ids: List<Long>){
|
suspend fun deleteAllWithIDs(ids: List<Long>){
|
||||||
downloadDao.deleteAllWithIDs(ids)
|
downloadDao.deleteAllWithIDs(ids)
|
||||||
|
|
||||||
|
|
@ -214,7 +222,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@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 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||||
val workManager = WorkManager.getInstance(context)
|
val workManager = WorkManager.getInstance(context)
|
||||||
|
|
@ -236,7 +244,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
|
|
||||||
if (delay > 0L && useAlarmForScheduling) {
|
if (delay > 0L && useAlarmForScheduling) {
|
||||||
AlarmScheduler(context).scheduleAt(queuedItems.minBy { it.downloadStartTime }.downloadStartTime)
|
AlarmScheduler(context).scheduleAt(queuedItems.minBy { it.downloadStartTime }.downloadStartTime)
|
||||||
return
|
return Result.success("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -256,26 +264,22 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
val message = StringBuilder()
|
||||||
val handler = Handler(Looper.getMainLooper())
|
|
||||||
|
|
||||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
||||||
handler.postDelayed({
|
message.appendLine(context.getString(R.string.metered_network_download_start_info))
|
||||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queuedItems.isNotEmpty()) {
|
if (queuedItems.isNotEmpty()) {
|
||||||
val first = queuedItems.first()
|
val first = queuedItems.first()
|
||||||
if (first.downloadStartTime > 0L) {
|
if (first.downloadStartTime > 0L) {
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(queuedItems.first().downloadStartTime)
|
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(queuedItems.first().downloadStartTime)
|
||||||
handler.postDelayed({
|
message.appendLine(context.getString(R.string.download_rescheduled_to) + " " + date)
|
||||||
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
|
||||||
}, 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.success(message.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -638,6 +638,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
repository.deleteProcessing()
|
repository.deleteProcessing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteWithDuplicateStatus() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
repository.deleteWithDuplicateStatus()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun deleteAllWithID(ids: List<Long>) {
|
suspend fun deleteAllWithID(ids: List<Long>) {
|
||||||
repository.deleteAllWithIDs(ids)
|
repository.deleteAllWithIDs(ids)
|
||||||
}
|
}
|
||||||
|
|
@ -671,7 +675,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveDownloadsCount() : Int {
|
fun getActiveDownloadsCount() : Int {
|
||||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Active).toListString())
|
return repository.getActiveDownloadsCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveQueuedDownloadsCount() : Int {
|
fun getActiveQueuedDownloadsCount() : Int {
|
||||||
|
|
@ -761,12 +765,17 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
repository.startDownloadWorker(emptyList(), application)
|
repository.startDownloadWorker(emptyList(), application)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun queueProcessingDownloads(){
|
suspend fun queueProcessingDownloads() : QueueDownloadsResult {
|
||||||
val processingItems = repository.getProcessingDownloads()
|
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 context = App.instance
|
||||||
val alarmScheduler = AlarmScheduler(context)
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
|
|
@ -788,10 +797,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
items.forEach {
|
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()
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//CHECK DUPLICATES
|
//CHECK DUPLICATES
|
||||||
var isDuplicate = false
|
var isDuplicate = false
|
||||||
if (checkDuplicate.isNotEmpty() && !ignoreDuplicates){
|
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
|
//if scheduler is on
|
||||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
||||||
if (alarmScheduler.canSchedule()){
|
if (alarmScheduler.canSchedule()){
|
||||||
|
repository.updateAll(queuedItems)
|
||||||
alarmScheduler.schedule()
|
alarmScheduler.schedule()
|
||||||
}else{
|
}else{
|
||||||
sharedPreferences.edit().putBoolean("use_scheduler", false).apply()
|
sharedPreferences.edit().putBoolean("use_scheduler", false).apply()
|
||||||
Handler(Looper.getMainLooper()).post {
|
result.message = context.getString(R.string.enable_alarm_permission)
|
||||||
Toast.makeText(context, context.getString(R.string.enable_alarm_permission), Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
val queued = repository.updateAll(queuedItems)
|
||||||
|
|
||||||
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
|
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
|
||||||
repository.startDownloadWorker(queued, context)
|
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!useScheduler){
|
if(!useScheduler){
|
||||||
|
|
@ -947,7 +960,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
|
|
||||||
if (existingItemIDs.isNotEmpty()){
|
if (existingItemIDs.isNotEmpty()){
|
||||||
alreadyExistsUiState.value = existingItemIDs.toList()
|
alreadyExistsUiState.value = existingItemIDs.toList()
|
||||||
|
result.duplicateDownloadIDs = existingItemIDs.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getQueuedCollectedFileSize() : Long {
|
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()
|
val processing = repository.getProcessingDownloads()
|
||||||
processing.forEach {
|
processing.forEach {
|
||||||
it.downloadStartTime = time
|
it.downloadStartTime = time
|
||||||
it.status = DownloadRepository.Status.Scheduled.toString()
|
it.status = DownloadRepository.Status.Scheduled.toString()
|
||||||
}
|
}
|
||||||
queueDownloads(processing)
|
return queueDownloads(processing)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkIfAllProcessingItemsHaveSameType() : Pair<Boolean, Type> {
|
fun checkIfAllProcessingItemsHaveSameType() : Pair<Boolean, Type> {
|
||||||
|
|
|
||||||
|
|
@ -213,18 +213,6 @@ class ShareActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
this@ShareActivity.finish()
|
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.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
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.R
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.util.ThemeUtil
|
import com.deniscerri.ytdl.util.ThemeUtil
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.elevation.SurfaceColors
|
import com.google.android.material.elevation.SurfaceColors
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
open class BaseActivity : AppCompatActivity() {
|
open class BaseActivity : AppCompatActivity() {
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: Downlo
|
||||||
incognito = !incognito
|
incognito = !incognito
|
||||||
fragmentAdapter.isIncognito = incognito
|
fragmentAdapter.isIncognito = incognito
|
||||||
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
|
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.HistoryViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdl.receiver.ShareActivity
|
import com.deniscerri.ytdl.receiver.ShareActivity
|
||||||
|
import com.deniscerri.ytdl.ui.BaseActivity
|
||||||
import com.deniscerri.ytdl.util.InfoUtil
|
import com.deniscerri.ytdl.util.InfoUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.facebook.shimmer.ShimmerFrameLayout
|
import com.facebook.shimmer.ShimmerFrameLayout
|
||||||
|
|
@ -80,6 +81,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
private lateinit var title : View
|
private lateinit var title : View
|
||||||
private lateinit var shimmerLoadingSubtitle : ShimmerFrameLayout
|
private lateinit var shimmerLoadingSubtitle : ShimmerFrameLayout
|
||||||
private lateinit var subtitle : View
|
private lateinit var subtitle : View
|
||||||
|
private lateinit var parentActivity: BaseActivity
|
||||||
|
|
||||||
|
|
||||||
private lateinit var result: ResultItem
|
private lateinit var result: ResultItem
|
||||||
|
|
@ -95,7 +97,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||||
infoUtil = InfoUtil(requireContext())
|
infoUtil = InfoUtil(requireContext())
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
val res: ResultItem?
|
val res: ResultItem?
|
||||||
val dwl: DownloadItem?
|
val dwl: DownloadItem?
|
||||||
|
|
||||||
|
|
@ -131,6 +132,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
|
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
|
||||||
dialog.setContentView(view)
|
dialog.setContentView(view)
|
||||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||||
|
parentActivity = activity as BaseActivity
|
||||||
|
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||||
|
|
@ -331,21 +333,34 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
audioDownloadItem.status = DownloadRepository.Status.Scheduled.toString()
|
audioDownloadItem.status = DownloadRepository.Status.Scheduled.toString()
|
||||||
itemsToQueue.add(audioDownloadItem)
|
itemsToQueue.add(audioDownloadItem)
|
||||||
|
|
||||||
runBlocking {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.queueDownloads(itemsToQueue)
|
val result = withContext(Dispatchers.IO){
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
downloadViewModel.queueDownloads(itemsToQueue)
|
||||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
}
|
||||||
|
|
||||||
|
if (result.message.isNotBlank()){
|
||||||
|
Toast.makeText(requireContext(), result.message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
runBlocking {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
val result = withContext(Dispatchers.IO){
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
}
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
|
|
||||||
|
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)
|
itemsToQueue.add(it)
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(itemsToQueue)
|
val result = downloadViewModel.queueDownloads(itemsToQueue)
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
runBlocking {
|
val result = withContext(Dispatchers.IO) {
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
}
|
}
|
||||||
dismiss()
|
handleDuplicatesAndDismiss(result.duplicateDownloadIDs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -441,7 +458,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
incognito = !incognito
|
incognito = !incognito
|
||||||
fragmentAdapter.isIncognito = incognito
|
fragmentAdapter.isIncognito = incognito
|
||||||
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
|
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 {
|
lifecycleScope.launch {
|
||||||
resultViewModel.updateFormatsResultData.collectLatest { formats ->
|
resultViewModel.updateFormatsResultData.collectLatest { formats ->
|
||||||
if (formats == null) return@collectLatest
|
if (formats == null) return@collectLatest
|
||||||
|
|
@ -686,5 +720,13 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
super.onDismiss(dialog)
|
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.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
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.ui.adapter.ConfigureMultipleDownloadsAdapter
|
||||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
|
|
@ -56,6 +60,7 @@ import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -80,6 +85,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
private lateinit var shimmerTitle: ShimmerFrameLayout
|
private lateinit var shimmerTitle: ShimmerFrameLayout
|
||||||
private lateinit var shimmerSubtitle: ShimmerFrameLayout
|
private lateinit var shimmerSubtitle: ShimmerFrameLayout
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var parentActivity: BaseActivity
|
||||||
|
|
||||||
private lateinit var currentDownloadIDs: List<Long>
|
private lateinit var currentDownloadIDs: List<Long>
|
||||||
private var processingItemsCount : Int = 0
|
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)
|
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
|
||||||
dialog.setContentView(view)
|
dialog.setContentView(view)
|
||||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||||
|
parentActivity = activity as BaseActivity
|
||||||
|
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||||
|
|
@ -204,9 +211,20 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
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 {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
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 ->
|
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
when (m.itemId) {
|
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.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
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.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.elevation.SurfaceColors
|
import com.google.android.material.elevation.SurfaceColors
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -102,10 +104,17 @@ class DownloadsAlreadyExistDialog : BottomSheetDialogFragment(), AlreadyExistsAd
|
||||||
|
|
||||||
view.findViewById<MaterialButton>(R.id.bottomsheet_download_button).setOnClickListener {
|
view.findViewById<MaterialButton>(R.id.bottomsheet_download_button).setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.deleteProcessing()
|
downloadViewModel.deleteWithDuplicateStatus()
|
||||||
val items = duplicates.map { it.downloadItem }
|
val items = duplicates.map { it.downloadItem }
|
||||||
items.forEach { it.id = 0 }
|
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){
|
withContext(Dispatchers.Main){
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
@ -117,8 +126,7 @@ class DownloadsAlreadyExistDialog : BottomSheetDialogFragment(), AlreadyExistsAd
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
super.onDismiss(dialog)
|
super.onDismiss(dialog)
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.deleteProcessing()
|
downloadViewModel.deleteWithDuplicateStatus()
|
||||||
downloadViewModel.deleteAllWithID(duplicates.map { it.downloadItem.id })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -504,7 +504,10 @@ class FormatSelectionBottomSheetDialog(
|
||||||
finalFormats = if (items.first()?.type == Type.audio){
|
finalFormats = if (items.first()?.type == Type.audio){
|
||||||
formatSorter.sortAudioFormats(finalFormats)
|
formatSorter.sortAudioFormats(finalFormats)
|
||||||
}else{
|
}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 -> {
|
FormatCategory.SMALLEST -> {
|
||||||
|
|
|
||||||
|
|
@ -179,9 +179,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
ok.isEnabled = false
|
ok.isEnabled = false
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val checkedItems = listAdapter.getCheckedItems()
|
val checkedItems = listAdapter.getCheckedItems()
|
||||||
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
|
val checkedResultItems = items.filter { item -> checkedItems.contains(item.url) }
|
||||||
if (checkedResultItems.size == 1){
|
if (checkedResultItems.size == 1){
|
||||||
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0]!!.url)!!
|
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0].url)!!
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadBottomSheetDialog, bundleOf(
|
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadBottomSheetDialog, bundleOf(
|
||||||
Pair("result", resultItem),
|
Pair("result", resultItem),
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,15 @@
|
||||||
package com.deniscerri.ytdl.ui.downloads
|
package com.deniscerri.ytdl.ui.downloads
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.get
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -26,12 +18,10 @@ import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.inflate
|
|
||||||
import com.deniscerri.ytdl.MainActivity
|
import com.deniscerri.ytdl.MainActivity
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
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.Extensions.createBadge
|
||||||
import com.deniscerri.ytdl.util.NavbarUtil
|
import com.deniscerri.ytdl.util.NavbarUtil
|
||||||
import com.deniscerri.ytdl.util.NotificationUtil
|
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.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
|
||||||
|
|
@ -164,10 +164,11 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
historyViewModel.getFilteredList().observe(viewLifecycleOwner) {
|
historyViewModel.getFilteredList().observe(viewLifecycleOwner) {
|
||||||
historyAdapter!!.submitList(it)
|
historyAdapter!!.submitList(it)
|
||||||
historyList = it
|
historyList = it
|
||||||
scrollToTop()
|
|
||||||
|
|
||||||
if (recyclerView.adapter == null){
|
if (recyclerView.adapter == null){
|
||||||
recyclerView.adapter = historyAdapter
|
recyclerView.adapter = historyAdapter
|
||||||
|
}else{
|
||||||
|
scrollToTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.interaction.DragInteraction
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
|
@ -13,12 +14,14 @@ import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||||
|
import com.deniscerri.ytdl.work.CleanUpLeftoverDownloads
|
||||||
import com.deniscerri.ytdl.work.DownloadWorker
|
import com.deniscerri.ytdl.work.DownloadWorker
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
@ -69,6 +72,42 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
||||||
archivePathResultLauncher.launch(intent)
|
archivePathResultLauncher.launch(intent)
|
||||||
true
|
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 scheduler = AlarmScheduler(requireContext())
|
||||||
|
|
||||||
val useAlarmManagerInsteadOfWorkManager = findPreference<SwitchPreferenceCompat>("use_alarm_for_scheduling")
|
val useAlarmManagerInsteadOfWorkManager = findPreference<SwitchPreferenceCompat>("use_alarm_for_scheduling")
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import android.os.Environment
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
|
|
@ -19,10 +21,14 @@ import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
|
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.deniscerri.ytdl.work.MoveCacheFilesWorker
|
import com.deniscerri.ytdl.work.MoveCacheFilesWorker
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -43,6 +49,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
private lateinit var preferences: SharedPreferences
|
private lateinit var preferences: SharedPreferences
|
||||||
private lateinit var editor: SharedPreferences.Editor
|
private lateinit var editor: SharedPreferences.Editor
|
||||||
|
|
||||||
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
private var activeDownloadCount = 0
|
private var activeDownloadCount = 0
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
|
@ -50,6 +57,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
editor = preferences.edit()
|
editor = preferences.edit()
|
||||||
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
|
|
||||||
musicPath = findPreference("music_path")
|
musicPath = findPreference("music_path")
|
||||||
videoPath = findPreference("video_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!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||||
clearCache!!.onPreferenceClickListener =
|
clearCache!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
if (activeDownloadCount == 0){
|
lifecycleScope.launch {
|
||||||
File(FileUtil.getCachePath(requireContext())).deleteRecursively()
|
activeDownloadCount = withContext(Dispatchers.IO){
|
||||||
Snackbar.make(requireView(), getString(R.string.cache_cleared), Snackbar.LENGTH_SHORT).show()
|
downloadViewModel.getActiveDownloadsCount()
|
||||||
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)}"
|
if (activeDownloadCount == 0){
|
||||||
}else{
|
File(FileUtil.getCachePath(requireContext())).deleteRecursively()
|
||||||
Snackbar.make(requireView(), getString(R.string.downloads_running_try_later), Snackbar.LENGTH_SHORT).show()
|
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
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,11 +142,10 @@ class FormatSorter(private var context: Context) {
|
||||||
|
|
||||||
val aIndex = videoResolutionOrder.indexOfFirst { a.format_note.contains(it, ignoreCase = true) }
|
val aIndex = videoResolutionOrder.indexOfFirst { a.format_note.contains(it, ignoreCase = true) }
|
||||||
val bIndex = videoResolutionOrder.indexOfFirst { b.format_note.contains(it, ignoreCase = true) }
|
val bIndex = videoResolutionOrder.indexOfFirst { b.format_note.contains(it, ignoreCase = true) }
|
||||||
|
|
||||||
if (aIndex > preferenceIndex || bIndex > preferenceIndex) {
|
if (aIndex > preferenceIndex || bIndex > preferenceIndex) {
|
||||||
-1
|
-1
|
||||||
}else if(aIndex == -1 && bIndex == -1){
|
}else if(aIndex == -1 && bIndex == -1){
|
||||||
-1
|
0
|
||||||
}else{
|
}else{
|
||||||
bIndex.compareTo(aIndex)
|
bIndex.compareTo(aIndex)
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +167,6 @@ class FormatSorter(private var context: Context) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formats.sortedWith(fieldSorter)
|
return formats.sortedWith(fieldSorter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.content.res.Resources.NotFoundException
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
|
|
@ -26,6 +27,7 @@ import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDLException
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -112,23 +114,37 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
|
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
|
||||||
try{
|
try {
|
||||||
val items = arrayListOf<ResultItem>()
|
val items = arrayListOf<ResultItem>()
|
||||||
// -------------- PIPED API FUNCTION -------------------
|
// -------------- PIPED API FUNCTION -------------------
|
||||||
var url = ""
|
var url = ""
|
||||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
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)
|
val res = genericRequest(url)
|
||||||
if (!res.has("relatedStreams")) throw Exception()
|
if (!res.has("relatedStreams")) throw Exception()
|
||||||
|
|
||||||
val dataArray = res.getJSONArray("relatedStreams")
|
val dataArray = res.getJSONArray("relatedStreams")
|
||||||
var nextpage = res.getString("nextpage")
|
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 {
|
kotlin.runCatching {
|
||||||
val obj = dataArray.getJSONObject(i)
|
val obj = dataArray.getJSONObject(i)
|
||||||
createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))?.apply {
|
createVideoFromPipedJSON(
|
||||||
playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
obj,
|
||||||
|
"https://youtube.com" + obj.getString("url")
|
||||||
|
)?.apply {
|
||||||
|
playlistTitle =
|
||||||
|
runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||||
playlistURL = "https://www.youtube.com/playlist?list=$id"
|
playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||||
items.add(this)
|
items.add(this)
|
||||||
}
|
}
|
||||||
|
|
@ -137,11 +153,14 @@ class InfoUtil(private val context: Context) {
|
||||||
if (nextpage == "null") nextpage = ""
|
if (nextpage == "null") nextpage = ""
|
||||||
return PlaylistTuple(nextpage, items)
|
return PlaylistTuple(nextpage, items)
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
Log.e("PLST", e.toString())
|
if (e is YoutubeDLException) {
|
||||||
return PlaylistTuple(
|
throw(e)
|
||||||
"",
|
}else{
|
||||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
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()
|
Html.fromHtml(obj.getString("uploader").toString()).toString()
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
||||||
}.replace(" - Topic", "")
|
}.removeSuffix(" - Topic")
|
||||||
|
|
||||||
val duration = obj.getInt("duration").toStringDuration(Locale.US)
|
val duration = obj.getInt("duration").toStringDuration(Locale.US)
|
||||||
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
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 ext = downloadItem.container
|
||||||
|
val preferredLanguage = sharedPreferences.getString("audio_language","")!!
|
||||||
|
println(audioQualityId)
|
||||||
if (audioQualityId.isNotBlank()) {
|
if (audioQualityId.isNotBlank()) {
|
||||||
if(audioQualityId.contains("-")){
|
if (audioQualityId.matches(".*-[0-9]+.*".toRegex())) {
|
||||||
audioQualityId = if(!downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){
|
audioQualityId = if(!downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){
|
||||||
"ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b"
|
"ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b"
|
||||||
}else{
|
}else{
|
||||||
"$audioQualityId/${audioQualityId.split("-")[0]}"
|
"$audioQualityId/${audioQualityId.split("-")[0]}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("-f", audioQualityId)
|
request.addOption("-f", audioQualityId)
|
||||||
}else{
|
}else{
|
||||||
//enters here if generic or quick downloaded with ba format
|
//enters here if generic or quick downloaded with ba format
|
||||||
val preferredLanguage = sharedPreferences.getString("audio_language","")!!
|
|
||||||
if (preferredLanguage.isNotBlank()){
|
if (preferredLanguage.isNotBlank()){
|
||||||
request.addOption("-f", "ba[language^=$preferredLanguage]/ba/b")
|
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("-P", downDir.absolutePath)
|
||||||
request.addOption("-S", formatSorting.toString())
|
request.addOption("-S", formatSorting.toString())
|
||||||
|
|
||||||
|
|
@ -1357,7 +1382,8 @@ class InfoUtil(private val context: Context) {
|
||||||
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
|
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
|
||||||
val format = downloadItem.allFormats.find { it.format_id == f }
|
val format = downloadItem.allFormats.find { it.format_id == f }
|
||||||
format?.run {
|
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") {
|
if (!this.lang.isNullOrBlank() && this.lang != "None") {
|
||||||
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
|
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
|
||||||
} else {
|
} 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 {
|
StringBuilder().apply {
|
||||||
if (hasGenericResulutionFormat.isNotBlank()) {
|
if (hasGenericResulutionFormat.isNotBlank()) {
|
||||||
append(",res:${hasGenericResulutionFormat}")
|
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 (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
|
||||||
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
||||||
|
|
@ -1491,6 +1515,7 @@ class InfoUtil(private val context: Context) {
|
||||||
if (cont.isNotBlank()) append(",vext:$cont")
|
if (cont.isNotBlank()) append(",vext:$cont")
|
||||||
if (acont.isNotBlank()) append(",aext:$acont")
|
if (acont.isNotBlank()) append(",aext:$acont")
|
||||||
if (abrSort.isNotBlank()) append(",abr~${abrSort}")
|
if (abrSort.isNotBlank()) append(",abr~${abrSort}")
|
||||||
|
if (preferredLanguage.isNotBlank()) append(",lang:${preferredLanguage}")
|
||||||
if (this.isNotBlank()){
|
if (this.isNotBlank()){
|
||||||
request.addOption("-S", "+hasaud$this")
|
request.addOption("-S", "+hasaud$this")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,27 @@ class NotificationUtil(var context: Context) {
|
||||||
notificationManager.cancel(DOWNLOAD_ERRORED_NOTIFICATION_ID + id)
|
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 {
|
fun createMoveCacheFilesNotification(pendingIntent: PendingIntent?, downloadMiscChannelId: String): Notification {
|
||||||
val notificationBuilder = getBuilder(downloadMiscChannelId)
|
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:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="200dp"
|
||||||
app:spanCount="2"
|
app:spanCount="2"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="200dp"
|
||||||
app:spanCount="3"
|
app:spanCount="3"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="200dp"
|
||||||
app:spanCount="2"
|
app:spanCount="2"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="200dp"
|
||||||
app:spanCount="4"
|
app:spanCount="4"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -105,25 +105,25 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/audio_formats"
|
android:id="@+id/audio_formats"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="5dp"
|
||||||
android:background="@drawable/rounded_corner"
|
android:background="@drawable/rounded_corner"
|
||||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
android:backgroundTint="?attr/colorPrimary"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:maxLength="17"
|
||||||
android:minWidth="30dp"
|
android:minWidth="30dp"
|
||||||
android:paddingHorizontal="5dp"
|
android:paddingHorizontal="5dp"
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textCursorDrawable="@string/audio_format"
|
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:cornerRadius="10dp"
|
app:cornerRadius="10dp"
|
||||||
app:drawableStartCompat="@drawable/ic_music_formatcard"
|
app:drawableStartCompat="@drawable/ic_music_formatcard"
|
||||||
app:drawableTint="@color/white"
|
app:drawableTint="@color/white"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
@ -180,8 +180,7 @@
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="bottom|end"
|
android:gravity="bottom|end"
|
||||||
android:maxWidth="70dp"
|
android:maxWidth="70dp"
|
||||||
android:maxLines="2"
|
android:maxLines="1"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/format_note"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:paddingBottom="100dp"
|
android:paddingBottom="200dp"
|
||||||
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/drag"
|
android:id="@+id/drag"
|
||||||
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="10dp"
|
android:paddingVertical="10dp"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,12 @@
|
||||||
app:launchSingleTop="true"
|
app:launchSingleTop="true"
|
||||||
app:popUpTo="@id/downloadBottomSheetDialog"
|
app:popUpTo="@id/downloadBottomSheetDialog"
|
||||||
app:popUpToInclusive="true" />
|
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>
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/selectPlaylistItemsDialog"
|
android:id="@+id/selectPlaylistItemsDialog"
|
||||||
|
|
@ -33,7 +39,14 @@
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/downloadMultipleBottomSheetDialog"
|
android:id="@+id/downloadMultipleBottomSheetDialog"
|
||||||
android:name="com.deniscerri.ytdl.ui.downloadcard.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
|
<dialog
|
||||||
android:id="@+id/downloadsAlreadyExistDialog2"
|
android:id="@+id/downloadsAlreadyExistDialog2"
|
||||||
android:name="com.deniscerri.ytdl.ui.downloadcard.DownloadsAlreadyExistDialog"
|
android:name="com.deniscerri.ytdl.ui.downloadcard.DownloadsAlreadyExistDialog"
|
||||||
|
|
|
||||||
|
|
@ -411,4 +411,7 @@
|
||||||
<string name="recode_video">Rikodo Videon</string>
|
<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="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="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>
|
</resources>
|
||||||
|
|
@ -1475,4 +1475,18 @@
|
||||||
<item>never</item>
|
<item>never</item>
|
||||||
</string-array>
|
</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>
|
</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="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">Recode Video</string>
|
||||||
<string name="recode_video_summary">Recodes the video file to the specified video format</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>
|
</resources>
|
||||||
|
|
@ -145,6 +145,15 @@
|
||||||
app:summary="@string/log_downloads_summary"
|
app:summary="@string/log_downloads_summary"
|
||||||
app:title="@string/log_downloads" />
|
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
|
<EditTextPreference
|
||||||
app:key="retries"
|
app:key="retries"
|
||||||
android:icon="@drawable/ic_retries"
|
android:icon="@drawable/ic_retries"
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,24 @@ 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
|
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