1.7.7
This commit is contained in:
parent
f724b14dfa
commit
3c5167f6eb
15 changed files with 193 additions and 477 deletions
|
|
@ -10,7 +10,7 @@ plugins {
|
|||
def properties = new Properties()
|
||||
def versionMajor = 1
|
||||
def versionMinor = 7
|
||||
def versionPatch = 6
|
||||
def versionPatch = 7
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def isBeta = false
|
||||
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@
|
|||
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<!-- for display over apps-->
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<!-- alarm scheduler-->
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||
|
||||
|
|
@ -434,8 +430,6 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service android:name=".services.ProcessDownloadsInBackgroundService" android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ interface DownloadDao {
|
|||
|
||||
|
||||
@Query("""
|
||||
SELECT DISTINCT type from downloads where status = 'Processing' and id in (:ids)
|
||||
SELECT DISTINCT type from downloads where status = 'Processing'
|
||||
""")
|
||||
fun getProcessingDownloadTypes(ids: List<Long>) : List<String>
|
||||
fun getProcessingDownloadTypes() : List<String>
|
||||
|
||||
|
||||
@Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)")
|
||||
|
|
@ -56,6 +56,12 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
fun getProcessingDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("UPDATE downloads set downloadStartTime=:time, status='Scheduled' WHERE status ='Processing'")
|
||||
suspend fun updateProcessingDownloadTime(time: Long)
|
||||
|
||||
@Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'")
|
||||
suspend fun updateProcessingDownloadPath(path: String)
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
suspend fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
||||
|
|
@ -126,12 +132,6 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
|
||||
fun getDownloadsByIds(ids: List<Long>) : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Processing' AND id >=:id")
|
||||
fun getProcessingItemsFromID(id: Long) : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Processing' AND id >=:id and id<=:id2")
|
||||
fun getProcessingItemsBetweenIDs(id: Long, id2: Long) : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
|
||||
fun getDownloadsByIdsFlow(ids: List<Long>) : Flow<List<DownloadItem>>
|
||||
|
||||
|
|
@ -231,8 +231,8 @@ interface DownloadDao {
|
|||
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
||||
suspend fun reQueueDownloadItems(list: List<Long>)
|
||||
|
||||
@Query("Update downloads SET status='Saved' WHERE status='Processing' and id in (:ids)")
|
||||
fun updateProcessingtoSavedStatus(ids: List<Long>)
|
||||
@Query("Update downloads SET status='Saved' WHERE status='Processing'")
|
||||
suspend fun updateProcessingtoSavedStatus()
|
||||
|
||||
@Transaction
|
||||
suspend fun putAtTopOfTheQueue(existingIDs: List<Long>){
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package com.deniscerri.ytdl.database.repository
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.preference.PreferenceManager
|
||||
|
|
@ -28,6 +31,8 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
|
|
@ -109,14 +114,6 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getDownloadById(id)
|
||||
}
|
||||
|
||||
fun getProcessingItemsFromID(id: Long) : Flow<List<DownloadItem>> {
|
||||
return downloadDao.getProcessingItemsFromID(id)
|
||||
}
|
||||
|
||||
fun getProcessingItemsBetweenIDs(first: Long, last:Long) : List<DownloadItem> {
|
||||
return downloadDao.getProcessingItemsBetweenIDs(first, last)
|
||||
}
|
||||
|
||||
fun getAllItemsByIDs(ids : List<Long>) : List<DownloadItem>{
|
||||
return downloadDao.getDownloadsByIds(ids)
|
||||
}
|
||||
|
|
@ -137,6 +134,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getActiveAndQueuedDownloadsList()
|
||||
}
|
||||
|
||||
suspend fun updateProcessingDownloadTime(time: Long) {
|
||||
downloadDao.updateProcessingDownloadTime(time)
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long> {
|
||||
return downloadDao.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
|
|
@ -258,12 +259,23 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
|
||||
}
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
||||
Looper.prepare().run {
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -9,11 +9,8 @@ import android.util.DisplayMetrics
|
|||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.paging.PagingData
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Data
|
||||
|
|
@ -46,21 +43,9 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -75,8 +60,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val allDownloads : Flow<PagingData<DownloadItem>>
|
||||
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val activeDownloads : Flow<List<DownloadItem>>
|
||||
val allProcessingDownloads : Flow<List<DownloadItem>>
|
||||
var mostRecentProcessingDownloads: Flow<List<DownloadItem>>
|
||||
val processingDownloads : Flow<List<DownloadItem>>
|
||||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
|
|
@ -115,8 +99,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
auto, audio, video, command
|
||||
}
|
||||
|
||||
|
||||
val processingDownloads = MediatorLiveData<List<DownloadItem>>(listOf())
|
||||
var processingItemsFlow : ProcessingItemsJob? = null
|
||||
var processingItems = MutableStateFlow(false)
|
||||
|
||||
|
|
@ -143,8 +125,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
allDownloads = repository.allDownloads.flow
|
||||
queuedDownloads = repository.queuedDownloads.flow
|
||||
activeDownloads = repository.activeDownloads
|
||||
allProcessingDownloads = repository.processingDownloads
|
||||
mostRecentProcessingDownloads = repository.getProcessingItemsFromID(0)
|
||||
processingDownloads = repository.processingDownloads
|
||||
savedDownloads = repository.savedDownloads.flow
|
||||
scheduledDownloads = repository.scheduledDownloads.flow
|
||||
cancelledDownloads = repository.cancelledDownloads.flow
|
||||
|
|
@ -385,6 +366,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||
updateProcessingJobData(ProcessingItemsJob(null, DownloadItem::class.java.toString(), itemIDs))
|
||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteProcessing()
|
||||
processingItems.emit(true)
|
||||
try {
|
||||
itemIDs.forEachIndexed { idx, it ->
|
||||
|
|
@ -395,7 +377,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
item.status = DownloadRepository.Status.Processing.toString()
|
||||
processingItemsFlow?.apply {
|
||||
val id = repository.insert(item)
|
||||
if (idx == 0) mostRecentProcessingDownloads = repository.getProcessingItemsFromID(id)
|
||||
processingDownloadItemIDs.add(id)
|
||||
updateProcessingJobData(this)
|
||||
}
|
||||
|
|
@ -499,7 +480,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.deleteProcessing()
|
||||
}
|
||||
|
||||
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||
suspend fun deleteAllWithID(ids: List<Long>) {
|
||||
repository.deleteAllWithIDs(ids)
|
||||
}
|
||||
|
||||
|
|
@ -618,6 +599,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun queueProcessingDownloads(){
|
||||
val processingItems = repository.getProcessingDownloads()
|
||||
queueDownloads(processingItems)
|
||||
}
|
||||
|
||||
suspend fun queueDownloads(items: List<DownloadItem>, ign : Boolean = false) : List<SharedDownloadViewModel.AlreadyExistsIDs> {
|
||||
val ids = sharedDownloadViewModel.queueDownloads(items, ign)
|
||||
return ids
|
||||
|
|
@ -639,43 +625,33 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() })
|
||||
}
|
||||
|
||||
fun moveProcessingToSavedCategory(ids: List<Long>){
|
||||
ids.chunked(100).forEach {
|
||||
dao.updateProcessingtoSavedStatus(it)
|
||||
suspend fun moveProcessingToSavedCategory(){
|
||||
dao.updateProcessingtoSavedStatus()
|
||||
}
|
||||
|
||||
suspend fun updateProcessingFormat(selectedFormats: List<FormatTuple>): List<Long> {
|
||||
val items = repository.getProcessingDownloads()
|
||||
items.forEachIndexed { index, i ->
|
||||
selectedFormats[index].format?.apply {
|
||||
i.format = this
|
||||
}
|
||||
if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
||||
repository.update(i)
|
||||
}
|
||||
|
||||
return items.map { itm -> itm.format.filesize }
|
||||
}
|
||||
|
||||
suspend fun updateProcessingCommandFormat(format: Format){
|
||||
val items = repository.getProcessingDownloads()
|
||||
items.forEach {
|
||||
it.format = format
|
||||
repository.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateProcessingFormat(ids: List<Long>, selectedFormats: List<FormatTuple>): List<Long> {
|
||||
return ids.chunked(100).map {
|
||||
val items = repository.getAllItemsByIDs(it)
|
||||
items.forEachIndexed { index, i ->
|
||||
selectedFormats[index].format?.apply {
|
||||
i.format = this
|
||||
}
|
||||
if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
||||
repository.update(i)
|
||||
}
|
||||
|
||||
items.map { itm -> itm.format.filesize }
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
suspend fun updateProcessingCommandFormat(ids: List<Long>, format: Format){
|
||||
ids.chunked(100).forEach {
|
||||
repository.getAllItemsByIDs(it).forEach { i ->
|
||||
i.format = format
|
||||
repository.update(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateProcessingDownloadPath(ids: List<Long>, path: String){
|
||||
ids.chunked(100).forEach {
|
||||
repository.getAllItemsByIDs(it).forEach { i ->
|
||||
i.downloadPath = path
|
||||
repository.update(i)
|
||||
}
|
||||
}
|
||||
suspend fun updateProcessingDownloadPath(path: String){
|
||||
dao.updateProcessingDownloadPath(path)
|
||||
}
|
||||
|
||||
fun getProcessingDownloadsCount() : Int {
|
||||
|
|
@ -687,42 +663,35 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
|
||||
suspend fun updateProcessingAllFormats(ids: List<Long>, formatCollection: List<List<Format>>) {
|
||||
ids.chunked(100).forEach {
|
||||
val items = repository.getAllItemsByIDs(it)
|
||||
items.forEachIndexed { index, i ->
|
||||
i.allFormats.clear()
|
||||
if (formatCollection.size == items.size && formatCollection[index].isNotEmpty()) {
|
||||
runCatching {
|
||||
i.allFormats.addAll(formatCollection[index])
|
||||
}
|
||||
suspend fun updateProcessingAllFormats(formatCollection: List<List<Format>>) {
|
||||
val items = repository.getProcessingDownloads()
|
||||
items.forEachIndexed { index, i ->
|
||||
i.allFormats.clear()
|
||||
if (formatCollection.size == items.size && formatCollection[index].isNotEmpty()) {
|
||||
runCatching {
|
||||
i.allFormats.addAll(formatCollection[index])
|
||||
}
|
||||
i.format = getFormat(i.allFormats, i.type)
|
||||
kotlin.runCatching {
|
||||
dbManager.resultDao.getResultByURL(i.url)?.apply {
|
||||
this.formats = formatCollection[index].toMutableList()
|
||||
dbManager.resultDao.update(this)
|
||||
}
|
||||
}
|
||||
repository.update(i)
|
||||
}
|
||||
i.format = getFormat(i.allFormats, i.type)
|
||||
kotlin.runCatching {
|
||||
dbManager.resultDao.getResultByURL(i.url)?.apply {
|
||||
this.formats = formatCollection[index].toMutableList()
|
||||
dbManager.resultDao.update(this)
|
||||
}
|
||||
}
|
||||
repository.update(i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun continueUpdatingFormatsOnBackground(itemIDs: List<Long>){
|
||||
itemIDs.chunked(100).forEach {list ->
|
||||
repository.getAllItemsByIDs(list).onEach {
|
||||
it.status = DownloadRepository.Status.Saved.toString()
|
||||
repository.update(it)
|
||||
}
|
||||
}
|
||||
suspend fun continueUpdatingFormatsOnBackground(){
|
||||
val ids = dao.getProcessingDownloadsList().map { it.id }
|
||||
dao.updateProcessingtoSavedStatus()
|
||||
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putLongArray("ids", itemIDs.toLongArray())
|
||||
.putLongArray("ids", ids.toLongArray())
|
||||
.putInt("id", id)
|
||||
.build())
|
||||
.addTag("updateFormats")
|
||||
|
|
@ -736,23 +705,22 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
}
|
||||
|
||||
suspend fun updateProcessingType(ids: List<Long>, newType: Type) {
|
||||
ids.chunked(100).forEach { _ ->
|
||||
repository.getAllItemsByIDs(ids).apply {
|
||||
val new = switchDownloadType(this, newType)
|
||||
new.forEach {
|
||||
repository.update(it)
|
||||
}
|
||||
suspend fun updateProcessingType(newType: Type) {
|
||||
val processing = repository.getProcessingDownloads()
|
||||
processing.apply {
|
||||
val new = switchDownloadType(this, newType)
|
||||
new.forEach {
|
||||
repository.update(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkIfAllProcessingItemsHaveSameType(ids: List<Long>) : Pair<Boolean, Type> {
|
||||
val types = mutableSetOf<String>()
|
||||
ids.chunked(100).forEach {
|
||||
types.addAll(dao.getProcessingDownloadTypes(it))
|
||||
}
|
||||
suspend fun updateProcessingDownloadTime(time: Long) {
|
||||
repository.updateProcessingDownloadTime(time)
|
||||
}
|
||||
|
||||
fun checkIfAllProcessingItemsHaveSameType() : Pair<Boolean, Type> {
|
||||
val types = dao.getProcessingDownloadTypes()
|
||||
return Pair(types.size == 1, Type.valueOf(types.first()))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
val prefVideo = sharedPreferences.getString("format_importance_video", itemValues.joinToString(","))!!
|
||||
|
||||
prefVideo.split(",").forEachIndexed { idx, s ->
|
||||
val importance = (itemValues.size - idx) * 10
|
||||
var importance = (itemValues.size - idx) * 10
|
||||
|
||||
when(s) {
|
||||
"id" -> {
|
||||
|
|
@ -345,8 +345,9 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
for(i in 0..preferenceIndex){
|
||||
removeAt(0)
|
||||
}
|
||||
add(0, preference)
|
||||
requirements.add { it: Format -> if (it.format_note.contains(preference, ignoreCase = true)) importance else 0 }
|
||||
forEachIndexed { index, res ->
|
||||
if (index >= 2) importance = 0
|
||||
requirements.add { it: Format -> if (it.format_note.contains(res, ignoreCase = true)) (importance - index - 1) else 0 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,165 +0,0 @@
|
|||
package com.deniscerri.ytdl.services
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.models.ResultItem
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.repository.ResultRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.SharedDownloadViewModel
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class ProcessDownloadsInBackgroundService : Service() {
|
||||
|
||||
private val binder: IBinder = LocalBinder()
|
||||
private val queueProcessingDownloadsJobList = mutableListOf<Job>()
|
||||
private lateinit var repository: DownloadRepository
|
||||
private lateinit var resultRepository: ResultRepository
|
||||
private lateinit var downloadViewModel: SharedDownloadViewModel
|
||||
inner class LocalBinder: Binder() {
|
||||
val service: ProcessDownloadsInBackgroundService
|
||||
get() = this@ProcessDownloadsInBackgroundService
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val dbManager = DBManager.getInstance(this)
|
||||
repository = DownloadRepository(dbManager.downloadDao)
|
||||
resultRepository = ResultRepository(dbManager.resultDao, this)
|
||||
downloadViewModel = SharedDownloadViewModel(this)
|
||||
}
|
||||
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
|
||||
val binding = intent.getBooleanExtra("binding", false)
|
||||
if (binding) {
|
||||
val cancel = intent.getBooleanExtra("cancel", false)
|
||||
if (cancel) {
|
||||
cancelAllProcessingJobs()
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
|
||||
repository.deleteProcessing()
|
||||
withContext(Dispatchers.Main){
|
||||
if (Build.VERSION.SDK_INT > 23){
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}else{
|
||||
stopForeground(true)
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
}
|
||||
|
||||
val notificationUtil = NotificationUtil(this)
|
||||
startForeground(System.currentTimeMillis().toInt(), notificationUtil.createProcessingDownloads())
|
||||
|
||||
|
||||
val itemType = intent.getStringExtra("itemType")!!
|
||||
val itemIDs = intent.getLongArrayExtra("itemIDs")!!.toList()
|
||||
val processingItemIDs = intent.getLongArrayExtra("processingItemIDs")!!.toList()
|
||||
val timeInMillis = intent.getLongExtra("timeInMillis", 0)
|
||||
val processingFinished = intent.getBooleanExtra("processingFinished", true)
|
||||
|
||||
val job = CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
if (!processingFinished) {
|
||||
when(itemType) {
|
||||
ResultItem::class.java.toString() -> {
|
||||
itemIDs.chunked(100).map { ids ->
|
||||
resultRepository.getAllByIDs(ids).map {
|
||||
downloadViewModel.createDownloadItemFromResult(
|
||||
result = it, givenType = DownloadViewModel.Type.valueOf(
|
||||
downloadViewModel.getDownloadType(url = it.url).toString()
|
||||
)
|
||||
)
|
||||
}.apply {
|
||||
if (timeInMillis > 0) {
|
||||
this.forEach {
|
||||
it.setAsScheduling(timeInMillis)
|
||||
}
|
||||
}
|
||||
if (isActive){
|
||||
downloadViewModel.queueDownloads(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DownloadItem::class.java.toString() -> {
|
||||
itemIDs.chunked(100).map { ids ->
|
||||
repository.getAllItemsByIDs(ids).apply {
|
||||
if (timeInMillis > 0) {
|
||||
this.forEach {
|
||||
it.setAsScheduling(timeInMillis)
|
||||
}
|
||||
}
|
||||
if (isActive){
|
||||
downloadViewModel.queueDownloads(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
repository.getProcessingItemsBetweenIDs(processingItemIDs.first(), processingItemIDs.last()).apply {
|
||||
this.chunked(100).map {
|
||||
if (timeInMillis > 0){
|
||||
this.forEach { d ->
|
||||
d.setAsScheduling(timeInMillis)
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive){
|
||||
downloadViewModel.queueDownloads(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
job.invokeOnCompletion {
|
||||
queueProcessingDownloadsJobList.remove(job)
|
||||
if (queueProcessingDownloadsJobList.isEmpty()){
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
queueProcessingDownloadsJobList.add(job)
|
||||
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
private fun DownloadItem.setAsScheduling(timeInMillis: Long) {
|
||||
status = DownloadRepository.Status.Scheduled.toString()
|
||||
downloadStartTime = timeInMillis
|
||||
}
|
||||
|
||||
fun cancelAllProcessingJobs(){
|
||||
queueProcessingDownloadsJobList.onEach { it.cancel(CancellationException()) }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -3,32 +3,23 @@ package com.deniscerri.ytdl.ui.downloadcard
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
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.content.res.Configuration
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -45,13 +36,11 @@ 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.services.ProcessDownloadsInBackgroundService
|
||||
import com.deniscerri.ytdl.ui.adapter.ConfigureMultipleDownloadsAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.facebook.shimmer.Shimmer
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
|
@ -61,22 +50,14 @@ import com.google.android.material.button.MaterialButton
|
|||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.function.Predicate
|
||||
|
||||
class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
|
||||
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
|
||||
|
|
@ -91,6 +72,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
private lateinit var bottomAppBar: BottomAppBar
|
||||
private lateinit var filesize : TextView
|
||||
private lateinit var count : TextView
|
||||
private lateinit var downloadBtn : MaterialButton
|
||||
private lateinit var scheduleBtn : MaterialButton
|
||||
private lateinit var title: TextView
|
||||
private lateinit var subtitle: TextView
|
||||
private lateinit var shimmerTitle: ShimmerFrameLayout
|
||||
|
|
@ -154,19 +137,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
||||
scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||
downloadBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_download_button)
|
||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
||||
|
||||
filesize = view.findViewById(R.id.filesize)
|
||||
count = view.findViewById(R.id.count)
|
||||
|
||||
val continueInBackgroundSnackBar = Snackbar.make(recyclerView, R.string.process_downloads_background, Snackbar.LENGTH_LONG)
|
||||
val snackbarView: View = continueInBackgroundSnackBar.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
|
||||
title = view.findViewById(R.id.bottom_sheet_title)
|
||||
subtitle = view.findViewById(R.id.bottom_sheet_subtitle)
|
||||
shimmerTitle = view.findViewById(R.id.shimmer_loading_title)
|
||||
|
|
@ -174,17 +152,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.processingItems.collectLatest {
|
||||
val loading = it
|
||||
toggleLoadingShimmerTitle(loading)
|
||||
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
|
||||
if (!loading){
|
||||
continueInBackgroundSnackBar.dismiss()
|
||||
}
|
||||
toggleLoading(it)
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.mostRecentProcessingDownloads.collectLatest { items ->
|
||||
downloadViewModel.processingDownloads.collectLatest { items ->
|
||||
processingItemsCount = items.size
|
||||
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
|
||||
listAdapter.submitList(items)
|
||||
|
|
@ -224,71 +197,31 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
|
||||
scheduleBtn.setOnClickListener{
|
||||
fun initSchedule(processingFinished: Boolean) {
|
||||
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
||||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
}
|
||||
|
||||
getProcessingItemsData()?.apply {
|
||||
this.job?.cancel(CancellationException())
|
||||
this.job = null
|
||||
downloadProcessDownloadsInBackground(this, processingFinished)
|
||||
}
|
||||
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(cal.timeInMillis)
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shimmerTitle.isVisible){
|
||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||
initSchedule(false)
|
||||
}
|
||||
continueInBackgroundSnackBar.show()
|
||||
}else{
|
||||
initSchedule(true)
|
||||
}
|
||||
}
|
||||
|
||||
download!!.setOnClickListener {
|
||||
fun initDownload(processingFinished: Boolean) {
|
||||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
||||
toggleLoading(true)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateProcessingDownloadTime(cal.timeInMillis)
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
}
|
||||
|
||||
getProcessingItemsData()?.apply {
|
||||
this.job?.cancel(CancellationException())
|
||||
this.job = null
|
||||
downloadProcessDownloadsInBackground(this, processingFinished)
|
||||
downloadViewModel.queueProcessingDownloads()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
if (shimmerTitle.isVisible){
|
||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||
initDownload(false)
|
||||
}
|
||||
continueInBackgroundSnackBar.show()
|
||||
}else{
|
||||
initDownload(true)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
download.setOnLongClickListener {
|
||||
downloadBtn.setOnClickListener {
|
||||
toggleLoading(true)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
downloadViewModel.queueProcessingDownloads()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
downloadBtn.setOnLongClickListener {
|
||||
val dd = MaterialAlertDialogBuilder(requireContext())
|
||||
dd.setTitle(getString(R.string.save_for_later))
|
||||
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
|
|
@ -296,7 +229,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
downloadViewModel.moveProcessingToSavedCategory(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.moveProcessingToSavedCategory()
|
||||
}
|
||||
getProcessingItemsData()?.apply {
|
||||
this.job?.cancel(CancellationException())
|
||||
|
|
@ -312,13 +245,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
val formatListener = object : OnFormatClickListener {
|
||||
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingFormat(getProcessingDownloadItemIDs(), selectedFormats)
|
||||
downloadViewModel.updateProcessingFormat(selectedFormats)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingAllFormats(getProcessingDownloadItemIDs(), allFormats)
|
||||
downloadViewModel.updateProcessingAllFormats(allFormats)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -326,7 +259,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
override fun onContinueOnBackground() {
|
||||
requireActivity().lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.continueUpdatingFormatsOnBackground(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.continueUpdatingFormatsOnBackground()
|
||||
}
|
||||
getProcessingItemsData()?.apply {
|
||||
this.job?.cancel(CancellationException())
|
||||
|
|
@ -369,7 +302,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
audio!!.setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.audio)
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
|
|
@ -381,7 +314,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
video!!.setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.video)
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
|
|
@ -393,7 +326,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
command!!.setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.command)
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
|
|
@ -417,7 +350,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
R.id.format -> {
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||
}
|
||||
if (!res.first){
|
||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -435,13 +368,15 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
it.joinToString(" ") { c -> c.content }
|
||||
)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingCommandFormat(getProcessingDownloadItemIDs(), format)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateProcessingCommandFormat(format)
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
}
|
||||
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
||||
val commonFormats = withContext(Dispatchers.IO){
|
||||
|
|
@ -465,7 +400,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
R.id.more -> {
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||
}
|
||||
if (!res.first) {
|
||||
Toast.makeText(
|
||||
|
|
@ -486,7 +421,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
}
|
||||
|
||||
UiUtil.configureAudio(
|
||||
|
|
@ -567,7 +502,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
}
|
||||
|
||||
UiUtil.configureVideo(
|
||||
|
|
@ -664,19 +599,27 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
}
|
||||
|
||||
private fun toggleLoadingShimmerTitle(show: Boolean) {
|
||||
shimmerTitle.isVisible = show
|
||||
title.isVisible = !show
|
||||
shimmerSubtitle.isVisible = show
|
||||
subtitle.isVisible = !show
|
||||
private fun toggleLoading(loading: Boolean){
|
||||
shimmerTitle.isVisible = loading
|
||||
title.isVisible = !loading
|
||||
shimmerSubtitle.isVisible = loading
|
||||
subtitle.isVisible = !loading
|
||||
|
||||
if (show){
|
||||
if (loading){
|
||||
shimmerTitle.startShimmer()
|
||||
shimmerSubtitle.startShimmer()
|
||||
}else{
|
||||
shimmerTitle.stopShimmer()
|
||||
shimmerSubtitle.stopShimmer()
|
||||
}
|
||||
|
||||
scheduleBtn.isEnabled = !loading
|
||||
downloadBtn.isEnabled = !loading
|
||||
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
|
||||
}
|
||||
|
||||
private fun toggleLoadingShimmerTitle(show: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
private fun updateFileSize(items: List<Long>){
|
||||
|
|
@ -705,7 +648,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingDownloadPath(getProcessingDownloadItemIDs(), result.data?.data.toString())
|
||||
downloadViewModel.updateProcessingDownloadPath(result.data?.data.toString())
|
||||
}
|
||||
|
||||
val path = FileUtil.formatPath(result.data!!.data.toString())
|
||||
|
|
@ -837,12 +780,18 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
getProcessingItemsData()?.apply {
|
||||
if (this.job != null){
|
||||
this.job?.cancel(CancellationException())
|
||||
downloadViewModel.deleteAllWithID(this.processingDownloadItemIDs)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
getProcessingItemsData()?.apply {
|
||||
if (this.job?.isActive == true){
|
||||
this.job?.cancel(CancellationException())
|
||||
downloadViewModel.deleteAllWithID(this.processingDownloadItemIDs)
|
||||
}
|
||||
}
|
||||
downloadViewModel.deleteProcessing()
|
||||
}
|
||||
}
|
||||
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
|
|
@ -923,24 +872,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
|
||||
private fun getProcessingDownloadItemIDs() : List<Long> {
|
||||
return downloadViewModel.processingItemsFlow?.processingDownloadItemIDs ?: listOf()
|
||||
}
|
||||
|
||||
private fun getProcessingItemsData() : DownloadViewModel.ProcessingItemsJob? {
|
||||
return downloadViewModel.processingItemsFlow
|
||||
}
|
||||
|
||||
private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob, processingFinished: Boolean, timeInMillis: Long = 0){
|
||||
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
||||
intent.putExtra("itemType", jobData.originItemType)
|
||||
intent.putExtra("itemIDs", jobData.originItemIDs.toLongArray())
|
||||
intent.putExtra("processingItemIDs", jobData.processingDownloadItemIDs.toLongArray())
|
||||
intent.putExtra("timeInMillis", timeInMillis)
|
||||
intent.putExtra("processingFinished", processingFinished)
|
||||
requireActivity().startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -257,7 +257,24 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
|||
|
||||
|
||||
private fun checkRanges(start: String, end: String) : Boolean {
|
||||
return start.isNotBlank() && end.isNotBlank()
|
||||
fromTextInput.error = ""
|
||||
toTextInput.error = ""
|
||||
|
||||
if (start.isBlank() || end.isBlank()) return false
|
||||
|
||||
val startValid = start.toInt() >= 0
|
||||
val endValid = end.toInt() <= resultItemIDs.size
|
||||
|
||||
if (!startValid) {
|
||||
fromTextInput.editText?.setText("")
|
||||
fromTextInput.error = "Invalid Number"
|
||||
}
|
||||
if (!endValid) {
|
||||
toTextInput.editText?.setText("")
|
||||
toTextInput.error = "Invalid Number"
|
||||
}
|
||||
|
||||
return startValid && endValid
|
||||
}
|
||||
|
||||
private fun reset(){
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ 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.services.ProcessDownloadsInBackgroundService
|
||||
import com.deniscerri.ytdl.util.Extensions.createBadge
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
|
|
@ -266,7 +265,6 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
|
||||
private fun cancelAllDownloads() {
|
||||
cancelBackgroundProcessingDownloads()
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
lifecycleScope.launch {
|
||||
val notificationUtil = NotificationUtil(requireContext())
|
||||
|
|
@ -280,26 +278,4 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
downloadViewModel.cancelActiveQueued()
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelBackgroundProcessingDownloads(){
|
||||
val connection = object : ServiceConnection {
|
||||
private lateinit var mService: ProcessDownloadsInBackgroundService
|
||||
private var mBound: Boolean = false
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
val binder = service as ProcessDownloadsInBackgroundService.LocalBinder
|
||||
mService = binder.service
|
||||
mBound = true
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
mBound = false
|
||||
}
|
||||
}
|
||||
|
||||
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
||||
intent.putExtra("binding", true)
|
||||
intent.putExtra("cancel", true)
|
||||
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
|
|||
import com.deniscerri.ytdl.receiver.CancelWorkReceiver
|
||||
import com.deniscerri.ytdl.receiver.PauseDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdl.receiver.ResumeActivity
|
||||
import com.deniscerri.ytdl.services.ProcessDownloadsInBackgroundService
|
||||
import com.deniscerri.ytdl.util.Extensions.toBitmap
|
||||
import java.io.File
|
||||
|
||||
|
|
@ -643,36 +642,6 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.notify(QUERY_PROCESS_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
fun createProcessingDownloads() : Notification{
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
|
||||
val intent = Intent(context, ProcessDownloadsInBackgroundService::class.java)
|
||||
intent.putExtra("binding", true)
|
||||
intent.putExtra("cancel", true)
|
||||
val cancelNotificationPendingIntent = PendingIntent.getService(
|
||||
context,
|
||||
System.currentTimeMillis().toInt(),
|
||||
intent,
|
||||
PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle(resources.getString(R.string.processing))
|
||||
.setSmallIcon(R.drawable.ic_app_icon)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
resources,
|
||||
R.drawable.ic_app_icon
|
||||
)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||
|
||||
return notificationBuilder.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DOWNLOAD_SERVICE_CHANNEL_ID = "1"
|
||||
const val COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="50dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:hint="@string/start">
|
||||
|
||||
|
|
@ -48,6 +50,7 @@
|
|||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/end"
|
||||
app:errorEnabled="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/colon">
|
||||
|
||||
|
|
|
|||
|
|
@ -391,8 +391,7 @@
|
|||
<string name="minute">Phút</string>
|
||||
<string name="second">Giây</string>
|
||||
<string name="milliseconds">Mili-giây</string>
|
||||
<string name="format_importance_video">Định dạng theo thứ tự quan trọng (Video)</string>
|
||||
<string name="format_importance_audio">Định dạng theo thứ tự quan trọng (Âm thanh)</string>
|
||||
<string name="format_importance">Định dạng theo thứ tự quan trọng</string>
|
||||
<string name="format_importance_note">Điều chỉnh định dạng theo tầm quan trọng của phần tử. Thứ tự này sẽ được sử dụng khi ứng dụng tìm nạp các định dạng trong thẻ tải xuống và tự động chọn định dạng!</string>
|
||||
<string name="no_audio">Video không âm thanh</string>
|
||||
<string name="enable_alarm_permission">Bạn cần cho phép quyền SCHEDULE_EXACT_ALARM trong cài đặt ứng dụng.</string>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@
|
|||
app:defaultValue="false"
|
||||
android:icon="@drawable/baseline_layers_24"
|
||||
android:key="display_over_apps"
|
||||
app:isPreferenceVisible="false"
|
||||
app:summary="@string/display_over_apps_summary"
|
||||
app:title="@string/display_over_apps" />
|
||||
|
||||
|
|
|
|||
8
fastlane/metadata/android/en-US/changelogs/10770.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/10770.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
## Shortest changelog ever
|
||||
|
||||
- Fixed multiple download card issues. Processing them in the background was not a good idea for most users as it was confusing and users thought it wasnt doing anything. It led to duplicate downloads
|
||||
Usually playlist downloads dont really have 4000 items in them so background. If you have one, just enable quick download and consider the whole playlist as a single item. :)
|
||||
|
||||
- Some users had issues with the display over apps perm. Since that perm ended up not doing anything to the revanced issue, i removed it
|
||||
|
||||
Had to push this out since multiple download card was almost broken
|
||||
Loading…
Reference in a new issue