1.7.6.1-beta
This commit is contained in:
parent
732ffa7367
commit
33de21bce9
49 changed files with 1537 additions and 1183 deletions
|
|
@ -11,7 +11,7 @@ def properties = new Properties()
|
|||
def versionMajor = 1
|
||||
def versionMinor = 7
|
||||
def versionPatch = 6
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
|
||||
def isBeta = true
|
||||
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
||||
|
||||
|
|
@ -195,4 +195,5 @@ dependencies {
|
|||
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.1'
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
implementation("org.greenrobot:eventbus:3.3.1")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads ORDER BY status")
|
||||
fun getAllDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused')")
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
|
|
@ -38,12 +38,9 @@ interface DownloadDao {
|
|||
|
||||
|
||||
@Query("""
|
||||
SELECT COUNT(*) FROM downloads WHERE status = 'Processing'
|
||||
UNION
|
||||
SELECT COUNT(*) FROM downloads WHERE status ='Processing' AND type =
|
||||
(SELECT type from downloads WHERE status = 'Processing' ORDER BY id LIMIT 1)
|
||||
SELECT DISTINCT type from downloads where status = 'Processing' and id in (:ids)
|
||||
""")
|
||||
fun getProcessingDownloadsCountByType() : List<Int>
|
||||
fun getProcessingDownloadTypes(ids: List<Long>) : List<String>
|
||||
|
||||
|
||||
@Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)")
|
||||
|
|
@ -53,7 +50,7 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status = 'Processing' ORDER BY id LIMIT 1")
|
||||
fun getFirstProcessingDownload() : DownloadItem
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','ActivePaused')")
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
|
|
@ -62,32 +59,31 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
suspend fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused', 'Scheduled')")
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued', 'Scheduled')")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
@Query("UPDATE downloads SET status='Queued' where status in ('Active', 'QueuedPaused', 'ActivePaused')")
|
||||
suspend fun resetActivePausedItems()
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused')")
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued')")
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused')")
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued')")
|
||||
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY id")
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY id")
|
||||
fun getQueuedDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
||||
@Query("SELECT format FROM downloads WHERE status in('Queued','QueuedPaused')")
|
||||
@Query("SELECT format FROM downloads WHERE status='Queued'")
|
||||
fun getSelectedFormatFromQueued() : List<Format>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE downloadStartTime <= :currentTime and status in ('Queued', 'Scheduled') ORDER BY downloadStartTime, id LIMIT 20")
|
||||
fun getQueuedScheduledDownloadsUntil(currentTime: Long) : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in ('Queued','QueuedPaused','ActivePaused') ORDER BY downloadStartTime, id")
|
||||
fun getQueuedAndPausedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY id")
|
||||
@Query("SELECT id FROM downloads WHERE status='Queued' ORDER BY id")
|
||||
fun getQueuedDownloadsListIDs() : List<Long>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
|
|
@ -133,22 +129,6 @@ interface DownloadDao {
|
|||
@Query("SELECT status FROM downloads WHERE id=:id")
|
||||
fun checkStatus(id: Long) : DownloadRepository.Status?
|
||||
|
||||
@Query("UPDATE downloads " +
|
||||
"SET status = CASE " +
|
||||
" WHEN status = 'Active' THEN 'ActivePaused' " +
|
||||
" WHEN status = 'Queued' THEN 'QueuedPaused' " +
|
||||
" ELSE status " +
|
||||
" END;")
|
||||
fun pauseActiveAndQueued()
|
||||
|
||||
@Query("UPDATE downloads " +
|
||||
"SET status = CASE " +
|
||||
" WHEN status = 'ActivePaused' THEN 'Active' " +
|
||||
" WHEN status = 'QueuedPaused' THEN 'Queued' " +
|
||||
" ELSE status " +
|
||||
" END;")
|
||||
fun unPauseActiveAndQueued()
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(item: DownloadItem) : Long
|
||||
|
||||
|
|
@ -179,7 +159,7 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE id in (:list)")
|
||||
suspend fun deleteAllWithIDs(list: List<Long>)
|
||||
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status in('Queued','QueuedPaused','Active','ActivePaused', 'Scheduled')")
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status in('Queued','Active', 'Scheduled')")
|
||||
suspend fun cancelActiveQueued()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||
|
|
@ -188,6 +168,9 @@ interface DownloadDao {
|
|||
@Upsert
|
||||
suspend fun update(item: DownloadItem)
|
||||
|
||||
@Query("UPDATE downloads set status=:status where id=:id")
|
||||
suspend fun setStatus(id: Long, status: String)
|
||||
|
||||
@Update
|
||||
suspend fun updateWithoutUpsert(item: DownloadItem)
|
||||
|
||||
|
|
@ -233,14 +216,11 @@ interface DownloadDao {
|
|||
@Query("UPDATE downloads SET downloadStartTime=0, status='Queued' where id in (:list)")
|
||||
suspend fun resetScheduleTimeForItems(list: List<Long>)
|
||||
|
||||
@Query("UPDATE downloads SET status='Queued' where status in ('QueuedPaused', 'ActivePaused')")
|
||||
suspend fun resetPausedItems()
|
||||
|
||||
@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'")
|
||||
fun updateProcessingtoSavedStatus()
|
||||
@Query("Update downloads SET status='Saved' WHERE status='Processing' and id in (:ids)")
|
||||
fun updateProcessingtoSavedStatus(ids: List<Long>)
|
||||
|
||||
@Transaction
|
||||
suspend fun putAtTopOfTheQueue(existingIDs: List<Long>){
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.deniscerri.ytdl.util.FileUtil
|
|||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
|
@ -59,17 +60,15 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
)
|
||||
|
||||
val activeDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active).toListString())
|
||||
val activeAndActivePausedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.ActivePaused).toListString())
|
||||
val queuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Queued, Status.QueuedPaused).toListString())
|
||||
val activeQueuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.ActivePaused, Status.Queued, Status.QueuedPaused).toListString())
|
||||
val queuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Queued).toListString())
|
||||
val activeQueuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.Queued).toListString())
|
||||
val cancelledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Cancelled).toListString())
|
||||
val erroredDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Error).toListString())
|
||||
val savedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Saved).toListString())
|
||||
val pausedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.ActivePaused, Status.QueuedPaused).toListString())
|
||||
val scheduledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Scheduled).toListString())
|
||||
|
||||
enum class Status {
|
||||
Active, ActivePaused, Queued, QueuedPaused, Error, Cancelled, Saved, Processing, Scheduled
|
||||
Active, ActivePaused, Queued, Error, Cancelled, Saved, Processing, Scheduled
|
||||
}
|
||||
|
||||
suspend fun insert(item: DownloadItem) : Long {
|
||||
|
|
@ -102,9 +101,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
}
|
||||
|
||||
|
||||
suspend fun setDownloadStatus(item: DownloadItem, status: Status){
|
||||
item.status = status.toString()
|
||||
update(item)
|
||||
suspend fun setDownloadStatus(id: Long, status: Status){
|
||||
downloadDao.setStatus(id, status.toString())
|
||||
}
|
||||
|
||||
fun getItemByID(id: Long) : DownloadItem {
|
||||
|
|
@ -184,14 +182,6 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
downloadDao.cancelActiveQueued()
|
||||
}
|
||||
|
||||
fun pauseDownloads(){
|
||||
downloadDao.pauseActiveAndQueued()
|
||||
}
|
||||
|
||||
fun unPauseDownloads(){
|
||||
downloadDao.unPauseActiveAndQueued()
|
||||
}
|
||||
|
||||
fun removeLogID(logID: Long){
|
||||
downloadDao.removeLogID(logID)
|
||||
}
|
||||
|
|
@ -200,6 +190,13 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
downloadDao.removeAllLogID()
|
||||
}
|
||||
|
||||
fun getFilteredProcessingDownloads(ids: List<Long>) : Flow<List<DownloadItem>> {
|
||||
return downloadDao.getProcessingDownloads()
|
||||
.map {
|
||||
it.filter { ids.contains(it.id) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context, inputData: Data.Builder = Data.Builder()) {
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
|
||||
private fun getQueryType(inputQuery: String) : SourceType {
|
||||
var type = SourceType.SEARCH_QUERY
|
||||
val p = Pattern.compile("((^(https?)://)?(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||
val p = Pattern.compile("((^(https?)://)?(www.)?(m.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||
val m = p.matcher(inputQuery)
|
||||
if (m.find()) {
|
||||
type = SourceType.YOUTUBE_VIDEO
|
||||
|
|
|
|||
|
|
@ -5,13 +5,10 @@ import android.app.Application
|
|||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Parcelable
|
||||
import android.util.DisplayMetrics
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
|
|
@ -20,7 +17,6 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.App
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
|
|
@ -41,21 +37,20 @@ import com.deniscerri.ytdl.ui.downloadcard.FormatTuple
|
|||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.deniscerri.ytdl.work.UpdatePlaylistFormatsWorker
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -77,14 +72,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val scheduledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
|
||||
val activeDownloadsCount : Flow<Int>
|
||||
val activeAndActivePausedDownloadsCount : Flow<Int>
|
||||
val queuedDownloadsCount : Flow<Int>
|
||||
val activeQueuedDownloadsCount : Flow<Int>
|
||||
val cancelledDownloadsCount : Flow<Int>
|
||||
val erroredDownloadsCount : Flow<Int>
|
||||
val savedDownloadsCount : Flow<Int>
|
||||
val scheduledDownloadsCount : Flow<Int>
|
||||
val pausedDownloadsCount: Flow<Int>
|
||||
|
||||
val alreadyExistsUiState: MutableStateFlow<List<SharedDownloadViewModel.AlreadyExistsIDs>>
|
||||
|
||||
|
|
@ -130,14 +123,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
infoUtil = InfoUtil(application)
|
||||
|
||||
activeDownloadsCount = repository.activeDownloadsCount
|
||||
activeAndActivePausedDownloadsCount = repository.activeAndActivePausedDownloadsCount
|
||||
queuedDownloadsCount = repository.queuedDownloadsCount
|
||||
activeQueuedDownloadsCount = repository.activeQueuedDownloadsCount
|
||||
cancelledDownloadsCount = repository.cancelledDownloadsCount
|
||||
erroredDownloadsCount = repository.erroredDownloadsCount
|
||||
savedDownloadsCount = repository.savedDownloadsCount
|
||||
scheduledDownloadsCount = repository.scheduledDownloadsCount
|
||||
pausedDownloadsCount = repository.pausedDownloadsCount
|
||||
|
||||
allDownloads = repository.allDownloads.flow
|
||||
queuedDownloads = repository.queuedDownloads.flow
|
||||
|
|
@ -294,8 +285,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
|
||||
|
||||
val customFileNameTemplate = when(historyItem.type) {
|
||||
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
|
||||
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s")
|
||||
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)30B - %(title)120B")
|
||||
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)30B - %(title)120B")
|
||||
else -> ""
|
||||
}
|
||||
|
||||
|
|
@ -371,78 +362,57 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
data class ProcessingItemsJob(
|
||||
var jobID: Long = 0,
|
||||
var job: Job? = null,
|
||||
var itemType: String,
|
||||
var itemIDs: List<Long>
|
||||
var originItemType: String,
|
||||
var originItemIDs: List<Long>,
|
||||
var processingDownloadItemIDs: MutableList<Long> = mutableListOf()
|
||||
)
|
||||
|
||||
val loadingProcessingItems: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||
var loadingProcessingDownloadsJobs: MutableStateFlow<List<ProcessingItemsJob>> = MutableStateFlow(listOf())
|
||||
private fun cancelAllLoadingProcessingDownloads() {
|
||||
loadingProcessingDownloadsJobs.value.onEach {
|
||||
it.job?.cancel(CancellationException())
|
||||
}
|
||||
var processingItemsJob: MutableLiveData<ProcessingItemsJob?> = MutableLiveData(null)
|
||||
var _filteredProcessingItems = MutableStateFlow<List<DownloadItem>>(listOf())
|
||||
var filteredProcessingItems: StateFlow<List<DownloadItem>> = _filteredProcessingItems
|
||||
private fun updateProcessingJobData(item: ProcessingItemsJob) = viewModelScope.launch {
|
||||
processingItemsJob.postValue(item)
|
||||
}
|
||||
|
||||
fun getLoadingProcessingDownloadJob(jobID: Long) : ProcessingItemsJob? {
|
||||
return loadingProcessingDownloadsJobs.value.firstOrNull { it.jobID == jobID }
|
||||
}
|
||||
|
||||
fun cancelLoadingProcessingDownloads(jobID: Long) {
|
||||
loadingProcessingDownloadsJobs.value.firstOrNull {it.jobID == jobID }?.apply {
|
||||
this.job?.cancel(CancellationException())
|
||||
}
|
||||
}
|
||||
|
||||
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) : Long {
|
||||
val jobID = System.currentTimeMillis()
|
||||
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||
processingItemsJob.postValue(ProcessingItemsJob(null, DownloadItem::class.java.toString(), itemIDs))
|
||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||
val insertedIDs = mutableListOf<Long>()
|
||||
try {
|
||||
loadingProcessingItems.emit(true)
|
||||
|
||||
itemIDs.chunked(100).forEach { ids ->
|
||||
val items = repository.getAllItemsByIDs(ids)
|
||||
items.apply {
|
||||
if (!isActive) {
|
||||
throw CancellationException()
|
||||
}
|
||||
this.forEach {
|
||||
it.id = 0
|
||||
it.status = DownloadRepository.Status.Processing.toString()
|
||||
insertedIDs.add(repository.insert(it))
|
||||
}
|
||||
if (!isActive) {
|
||||
throw CancellationException()
|
||||
}
|
||||
items.forEach {
|
||||
it.id = 0
|
||||
it.status = DownloadRepository.Status.Processing.toString()
|
||||
}
|
||||
|
||||
processingItemsJob.value?.apply {
|
||||
processingDownloadItemIDs.addAll(repository.insertAll(items))
|
||||
updateProcessingJobData(this)
|
||||
}
|
||||
}
|
||||
|
||||
loadingProcessingItems.emit(false)
|
||||
} catch (e: Exception) {
|
||||
repository.deleteAllWithIDs(insertedIDs)
|
||||
loadingProcessingItems.emit(false)
|
||||
processingItemsJob.value?.apply {
|
||||
this.processingDownloadItemIDs.chunked(100).forEach {
|
||||
repository.deleteAllWithIDs(it)
|
||||
}
|
||||
}
|
||||
processingItemsJob.postValue(null)
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val currentJobs = loadingProcessingDownloadsJobs.value.toMutableList()
|
||||
currentJobs.add(
|
||||
ProcessingItemsJob(jobID, job, DownloadItem::class.java.toString(), itemIDs)
|
||||
)
|
||||
loadingProcessingDownloadsJobs.emit(currentJobs)
|
||||
}
|
||||
|
||||
return jobID
|
||||
processingItemsJob.postValue(ProcessingItemsJob(job, DownloadItem::class.java.toString(), itemIDs))
|
||||
}
|
||||
|
||||
|
||||
fun turnResultItemsToProcessingDownloads(itemIds: List<Long>, downloadNow: Boolean = false) : Long {
|
||||
val jobID = System.currentTimeMillis()
|
||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||
val insertedIds = mutableListOf<Long>()
|
||||
fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
|
||||
processingItemsJob.postValue(ProcessingItemsJob(null, ResultItem::class.java.toString(), itemIDs))
|
||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
loadingProcessingItems.emit(true)
|
||||
|
||||
itemIds.chunked(100).forEach { ids ->
|
||||
itemIDs.chunked(100).forEach { ids ->
|
||||
val items = resultRepository.getAllByIDs(ids)
|
||||
val downloadItems = items.map {
|
||||
val preferredType = getDownloadType(url = it.url).toString()
|
||||
|
|
@ -462,41 +432,29 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
queueDownloads(downloadItems)
|
||||
}else{
|
||||
downloadItems.forEach {
|
||||
insertedIds.add(repository.insert(it))
|
||||
processingItemsJob.value?.apply {
|
||||
processingDownloadItemIDs.addAll(repository.insertAll(downloadItems))
|
||||
updateProcessingJobData(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
loadingProcessingItems.emit(false)
|
||||
}catch (e: Exception) {
|
||||
repository.deleteAllWithIDs(insertedIds)
|
||||
loadingProcessingItems.emit(false)
|
||||
processingItemsJob.value?.apply {
|
||||
this.processingDownloadItemIDs.chunked(100).forEach {
|
||||
repository.deleteAllWithIDs(it)
|
||||
}
|
||||
}
|
||||
processingItemsJob.postValue(null)
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val currentJobs = loadingProcessingDownloadsJobs.value.toMutableList()
|
||||
currentJobs.add(
|
||||
ProcessingItemsJob(jobID, job, ResultItem::class.java.toString(), itemIds)
|
||||
)
|
||||
loadingProcessingDownloadsJobs.emit(currentJobs)
|
||||
}
|
||||
|
||||
return jobID
|
||||
processingItemsJob.postValue(ProcessingItemsJob(job, ResultItem::class.java.toString(), itemIDs))
|
||||
}
|
||||
|
||||
fun insert(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.insert(item)
|
||||
}
|
||||
|
||||
fun pauseDownloads() = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.pauseDownloads()
|
||||
}
|
||||
|
||||
fun unPauseDownloads() = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.unPauseDownloads()
|
||||
}
|
||||
|
||||
fun insertAll(items: List<DownloadItem>)= viewModelScope.launch(Dispatchers.IO){
|
||||
items.forEach{
|
||||
repository.insert(it)
|
||||
|
|
@ -540,7 +498,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||
cancelAllLoadingProcessingDownloads()
|
||||
processingItemsJob.value?.apply { this.job?.cancel(CancellationException()) }
|
||||
repository.cancelActiveQueued()
|
||||
}
|
||||
|
||||
|
|
@ -563,8 +521,12 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return repository.getActiveDownloads()
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloads() : List<DownloadItem>{
|
||||
return repository.getActiveAndQueuedDownloads()
|
||||
fun getActiveDownloadsCount() : Int {
|
||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused).toListString())
|
||||
}
|
||||
|
||||
fun getQueuedDownloadsCount() : Int {
|
||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Queued).toListString())
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>{
|
||||
|
|
@ -573,10 +535,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||
dbManager.downloadDao.resetPausedItems()
|
||||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun resetActivePaused() {
|
||||
dbManager.downloadDao.resetActivePausedItems()
|
||||
}
|
||||
|
||||
suspend fun startDownloadWorker(list: List<DownloadItem>){
|
||||
repository.startDownloadWorker(list, application)
|
||||
}
|
||||
|
|
@ -664,36 +629,42 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() })
|
||||
}
|
||||
|
||||
fun moveProcessingToSavedCategory(){
|
||||
dao.updateProcessingtoSavedStatus()
|
||||
fun moveProcessingToSavedCategory(ids: List<Long>){
|
||||
ids.chunked(100).forEach {
|
||||
dao.updateProcessingtoSavedStatus(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateProcessingFormat(selectedFormats: List<FormatTuple>): List<Long> {
|
||||
val items = repository.getProcessingDownloads()
|
||||
items.forEachIndexed { index, i ->
|
||||
selectedFormats[index].format?.apply {
|
||||
i.format = this
|
||||
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)
|
||||
}
|
||||
if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
||||
repository.update(i)
|
||||
}
|
||||
|
||||
return items.map { it.format.filesize }
|
||||
items.map { itm -> itm.format.filesize }
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
suspend fun updateProcessingCommandFormat(format: Format){
|
||||
val items = repository.getProcessingDownloads()
|
||||
items.forEach { i ->
|
||||
i.format = format
|
||||
repository.update(i)
|
||||
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(path: String){
|
||||
val items = repository.getProcessingDownloads()
|
||||
items.forEach { i ->
|
||||
i.downloadPath = path
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -706,66 +677,73 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
|
||||
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])
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
i.format = getFormat(i.allFormats, i.type)
|
||||
kotlin.runCatching {
|
||||
dbManager.resultDao.getResultByURL(i.url)?.apply {
|
||||
this.formats = formatCollection[index].toMutableList()
|
||||
dbManager.resultDao.update(this)
|
||||
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)
|
||||
}
|
||||
repository.update(i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun continueUpdatingFormatsOnBackground(){
|
||||
dao.getProcessingDownloadsList().apply {
|
||||
this.forEach {
|
||||
suspend fun continueUpdatingFormatsOnBackground(itemIDs: List<Long>){
|
||||
itemIDs.chunked(100).forEach {list ->
|
||||
repository.getAllItemsByIDs(list).onEach {
|
||||
it.status = DownloadRepository.Status.Saved.toString()
|
||||
repository.update(it)
|
||||
}
|
||||
|
||||
val ids = this.map { it.id }
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putLongArray("ids", ids.toLongArray())
|
||||
.putInt("id", id)
|
||||
.build())
|
||||
.addTag("updateFormats")
|
||||
.build()
|
||||
val context = App.instance
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
id.toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest
|
||||
)
|
||||
}
|
||||
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putLongArray("ids", itemIDs.toLongArray())
|
||||
.putInt("id", id)
|
||||
.build())
|
||||
.addTag("updateFormats")
|
||||
.build()
|
||||
val context = App.instance
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
id.toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
suspend fun updateProcessingType(newType: Type) {
|
||||
repository.getProcessingDownloads().apply {
|
||||
val new = switchDownloadType(this, newType)
|
||||
new.forEach {
|
||||
repository.update(it)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkIfAllProcessingItemsHaveSameType() : Pair<Boolean, Type> {
|
||||
val counts = dao.getProcessingDownloadsCountByType()
|
||||
val sameType = counts.size == 1 || counts[0] == counts[1]
|
||||
val first = dao.getFirstProcessingDownload()
|
||||
return Pair(sameType, first.type)
|
||||
fun checkIfAllProcessingItemsHaveSameType(ids: List<Long>) : Pair<Boolean, Type> {
|
||||
val types = mutableSetOf<String>()
|
||||
ids.chunked(100).forEach {
|
||||
types.addAll(dao.getProcessingDownloadTypes(it))
|
||||
}
|
||||
|
||||
return Pair(types.size == 1, Type.valueOf(types.first()))
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -775,6 +753,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val first = dao.getFirstProcessingDownload()
|
||||
}
|
||||
|
||||
suspend fun updateToStatus(id: Long, status: DownloadRepository.Status) {
|
||||
repository.setDownloadStatus(id, status)
|
||||
}
|
||||
|
||||
fun getURLsByStatus(list: List<DownloadRepository.Status>) : List<String> {
|
||||
return dao.getURLsByStatus(list.map { it.toString() })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import okhttp3.internal.immutableListOf
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -282,16 +283,32 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
|
||||
private fun getPreferredAudioRequirements(): MutableList<(Format) -> Int> {
|
||||
val requirements: MutableList<(Format) -> Int> = mutableListOf()
|
||||
requirements.add {it: Format -> if (audioFormatIDPreference.contains(it.format_id)) 10 else 0}
|
||||
|
||||
sharedPreferences.getString("audio_language", "")?.apply {
|
||||
if (this.isNotBlank()){
|
||||
requirements.add { it: Format -> if (it.lang?.contains(this) == true) 3 else 0 }
|
||||
val itemValues = resources.getStringArray(R.array.format_importance_audio_values).toSet()
|
||||
val prefAudio = sharedPreferences.getString("format_importance_audio", itemValues.joinToString(","))!!
|
||||
|
||||
prefAudio.split(",").forEachIndexed { idx, s ->
|
||||
var importance = (itemValues.size - idx) * 10
|
||||
when(s) {
|
||||
"id" -> {
|
||||
requirements.add {it: Format -> if (audioFormatIDPreference.contains(it.format_id)) importance else 0}
|
||||
}
|
||||
"language" -> {
|
||||
sharedPreferences.getString("audio_language", "")?.apply {
|
||||
if (this.isNotBlank()){
|
||||
requirements.add { it: Format -> if (it.lang?.contains(this) == true) importance else 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
"codec" -> {
|
||||
requirements.add {it: Format -> if ("^(${audioCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.acodec)) importance else 0}
|
||||
}
|
||||
"container" -> {
|
||||
requirements.add {it: Format -> if (it.container == audioContainer) importance else 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requirements.add {it: Format -> if ("^(${audioCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.acodec)) 2 else 0}
|
||||
requirements.add {it: Format -> if (it.container == audioContainer) 1 else 0 }
|
||||
return requirements
|
||||
}
|
||||
|
||||
|
|
@ -299,42 +316,61 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
@SuppressLint("RestrictedApi")
|
||||
fun getPreferredVideoRequirements(): MutableList<(Format) -> Int> {
|
||||
val requirements: MutableList<(Format) -> Int> = mutableListOf()
|
||||
//format id
|
||||
requirements.add { it: Format -> if (formatIDPreference.contains(it.format_id)) 20 else 0 }
|
||||
//resolutions
|
||||
context.getStringArray(R.array.video_formats_values)
|
||||
.filter { it.contains("_") }
|
||||
.map{ it.split("_")[0].dropLast(1)
|
||||
}.toMutableList().apply {
|
||||
when(videoQualityPreference) {
|
||||
"worst" -> {
|
||||
requirements.add { it: Format -> if (it.format_note.contains("worst", ignoreCase = true)) (15) else 0 }
|
||||
}
|
||||
"best" -> {
|
||||
requirements.add { it: Format -> if (it.format_note.contains("best", ignoreCase = true)) (15) else 0 }
|
||||
}
|
||||
else -> {
|
||||
val preferenceIndex = this.indexOfFirst { videoQualityPreference.contains(it) }
|
||||
val preference = this[preferenceIndex]
|
||||
for(i in 0..preferenceIndex){
|
||||
removeAt(0)
|
||||
}
|
||||
add(0, preference)
|
||||
forEachIndexed { index, res ->
|
||||
requirements.add { it: Format -> if (it.format_note.contains(res, ignoreCase = true)) (15 - index - 1) else 0 }
|
||||
|
||||
val itemValues = resources.getStringArray(R.array.format_importance_video_values).toSet()
|
||||
val prefVideo = sharedPreferences.getString("format_importance_video", itemValues.joinToString(","))!!
|
||||
|
||||
prefVideo.split(",").forEachIndexed { idx, s ->
|
||||
val importance = (itemValues.size - idx) * 10
|
||||
|
||||
when(s) {
|
||||
"id" -> {
|
||||
requirements.add { it: Format -> if (formatIDPreference.contains(it.format_id)) importance else 0 }
|
||||
}
|
||||
"resolution" -> {
|
||||
context.getStringArray(R.array.video_formats_values)
|
||||
.filter { it.contains("_") }
|
||||
.map{ it.split("_")[0].dropLast(1)
|
||||
}.toMutableList().apply {
|
||||
when(videoQualityPreference) {
|
||||
"worst" -> {
|
||||
requirements.add { it: Format -> if (it.format_note.contains("worst", ignoreCase = true)) (importance) else 0 }
|
||||
}
|
||||
"best" -> {
|
||||
requirements.add { it: Format -> if (it.format_note.contains("best", ignoreCase = true)) (importance) else 0 }
|
||||
}
|
||||
else -> {
|
||||
val preferenceIndex = this.indexOfFirst { videoQualityPreference.contains(it) }
|
||||
val preference = this[preferenceIndex]
|
||||
for(i in 0..preferenceIndex){
|
||||
removeAt(0)
|
||||
}
|
||||
add(0, preference)
|
||||
forEachIndexed { index, res ->
|
||||
requirements.add { it: Format -> if (it.format_note.contains(res, ignoreCase = true)) (importance - index - 1) else 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
"codec" -> {
|
||||
requirements.add { it: Format -> if ("^(${videoCodec})(.+)?$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)) importance else 0 }
|
||||
}
|
||||
"no_audio" -> {
|
||||
requirements.add { it: Format -> if (it.acodec == "none" || it.acodec == "") importance else 0 }
|
||||
}
|
||||
"container" -> {
|
||||
requirements.add { it: Format ->
|
||||
if (videoContainer == "mp4")
|
||||
if (it.container.equals("mpeg_4", true)) importance else 0
|
||||
else
|
||||
if (it.container.equals(videoContainer, true)) importance else 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
requirements.add { it: Format -> if ("^(${videoCodec})(.+)?$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)) 5 else 0 }
|
||||
requirements.add { it: Format -> if (it.acodec == "none" || it.acodec == "") 1 else 0 }
|
||||
requirements.add { it: Format ->
|
||||
if (videoContainer == "mp4")
|
||||
if (it.container.equals("mpeg_4", true)) 1 else 0
|
||||
else
|
||||
if (it.container.equals(videoContainer, true)) 1 else 0
|
||||
}
|
||||
|
||||
return requirements
|
||||
}
|
||||
|
||||
|
|
@ -445,7 +481,7 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
val downloadArchive = runCatching { File(FileUtil.getDownloadArchivePath(context)).useLines { it.toList() } }.getOrElse { listOf() }
|
||||
.map { it.split(" ")[1] }
|
||||
items.forEach {
|
||||
if (! listOf(DownloadRepository.Status.ActivePaused, DownloadRepository.Status.Scheduled).toListString().contains(it.status))
|
||||
if (it.status != DownloadRepository.Status.Scheduled.toString())
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
var alreadyExists = false
|
||||
|
||||
|
|
@ -584,7 +620,9 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
}
|
||||
}else{
|
||||
if (queuedItems.isNotEmpty()){
|
||||
repository.startDownloadWorker(queuedItems, context)
|
||||
if (!sharedPreferences.getBoolean("paused_downloads", false)) {
|
||||
repository.startDownloadWorker(queuedItems, context)
|
||||
}
|
||||
|
||||
if(!useScheduler){
|
||||
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
|
|
@ -43,39 +44,25 @@ class ProcessDownloadsInBackgroundService : Service() {
|
|||
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
|
||||
val binding = intent.getBooleanExtra("binding", false)
|
||||
if (binding) 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") ?: longArrayOf()
|
||||
val jobData = DownloadViewModel.ProcessingItemsJob(
|
||||
itemType = itemType,
|
||||
itemIDs = itemIDs.toList()
|
||||
)
|
||||
|
||||
val itemType = intent.getStringExtra("itemType")!!
|
||||
val itemIDs = intent.getLongArrayExtra("itemIDs")!!.toList()
|
||||
val processingItemIDs = intent.getLongArrayExtra("processingItemIDs")!!.toList()
|
||||
val timeInMillis = intent.getLongExtra("timeInMillis", 0)
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
runJob(jobData, timeInMillis)
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
val processingFinished = intent.getBooleanExtra("processingFinished", true)
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
private fun DownloadItem.setAsScheduling(timeInMillis: Long) {
|
||||
status = DownloadRepository.Status.Scheduled.toString()
|
||||
downloadStartTime = timeInMillis
|
||||
}
|
||||
|
||||
private suspend fun runJob(jobData: DownloadViewModel.ProcessingItemsJob, timeInMillis: Long = 0) {
|
||||
val job = CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
if (jobData.itemType != "") {
|
||||
val itemIDS = jobData.itemIDs
|
||||
val processingType = jobData.itemType
|
||||
when(processingType) {
|
||||
if (!processingFinished) {
|
||||
when(itemType) {
|
||||
ResultItem::class.java.toString() -> {
|
||||
itemIDS.chunked(100).map { ids ->
|
||||
itemIDs.chunked(100).map { ids ->
|
||||
resultRepository.getAllByIDs(ids).map {
|
||||
downloadViewModel.createDownloadItemFromResult(
|
||||
result = it, givenType = DownloadViewModel.Type.valueOf(
|
||||
|
|
@ -88,33 +75,42 @@ class ProcessDownloadsInBackgroundService : Service() {
|
|||
it.setAsScheduling(timeInMillis)
|
||||
}
|
||||
}
|
||||
downloadViewModel.queueDownloads(this)
|
||||
if (isActive){
|
||||
downloadViewModel.queueDownloads(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DownloadItem::class.java.toString() -> {
|
||||
itemIDS.chunked(100).map { ids ->
|
||||
itemIDs.chunked(100).map { ids ->
|
||||
repository.getAllItemsByIDs(ids).apply {
|
||||
if (timeInMillis > 0) {
|
||||
this.forEach {
|
||||
it.setAsScheduling(timeInMillis)
|
||||
}
|
||||
}
|
||||
downloadViewModel.queueDownloads(this)
|
||||
if (isActive){
|
||||
downloadViewModel.queueDownloads(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else {
|
||||
repository.getProcessingDownloads().apply {
|
||||
if (timeInMillis > 0){
|
||||
this.forEach {
|
||||
it.setAsScheduling(timeInMillis)
|
||||
repository.getAllItemsByIDs(processingItemIDs).apply {
|
||||
this.chunked(100).map {
|
||||
if (timeInMillis > 0){
|
||||
this.forEach { d ->
|
||||
d.setAsScheduling(timeInMillis)
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive){
|
||||
downloadViewModel.queueDownloads(it)
|
||||
}
|
||||
}
|
||||
|
||||
downloadViewModel.queueDownloads(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +122,17 @@ class ProcessDownloadsInBackgroundService : Service() {
|
|||
}
|
||||
}
|
||||
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(){
|
||||
|
|
|
|||
|
|
@ -51,11 +51,13 @@ import com.facebook.shimmer.ShimmerFrameLayout
|
|||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.search.SearchBar
|
||||
import com.google.android.material.search.SearchView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -176,12 +178,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
searchSuggestionsRecyclerView?.adapter = searchSuggestionsAdapter
|
||||
searchSuggestionsRecyclerView?.itemAnimator = null
|
||||
|
||||
val progressBar = view.findViewById<View>(R.id.progress)
|
||||
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
resultViewModel.items.observe(requireActivity()) {
|
||||
kotlin.runCatching {
|
||||
homeAdapter!!.submitList(it)
|
||||
resultsList = it
|
||||
progressBar.isVisible = loadingItems && resultsList!!.isNotEmpty()
|
||||
if(resultViewModel.repository.itemCount.value > 1 || resultViewModel.repository.itemCount.value == -1){
|
||||
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && !loadingItems){
|
||||
downloadAllFab!!.visibility = VISIBLE
|
||||
|
|
@ -252,6 +256,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
|
||||
loadingItems = res.processing
|
||||
progressBar.isVisible = loadingItems && resultsList!!.isNotEmpty()
|
||||
if (res.processing){
|
||||
recyclerView?.setPadding(0,0,0,0)
|
||||
shimmerCards!!.startShimmer()
|
||||
|
|
@ -303,11 +308,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
arguments?.remove("showDownloadsWithUpdatedFormats")
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val ids = arguments?.getLongArray("downloadIds") ?: return@launch
|
||||
val jobID = downloadViewModel.turnDownloadItemsToProcessingDownloads(ids.toList())
|
||||
downloadViewModel.turnDownloadItemsToProcessingDownloads(ids.toList())
|
||||
withContext(Dispatchers.Main){
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
||||
Pair("processingDownloadsJobID", jobID)
|
||||
))
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -453,6 +456,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
searchBar!!.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
R.id.delete_results -> {
|
||||
resultsList = listOf()
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultViewModel.cancelParsingQueries()
|
||||
|
|
@ -733,11 +737,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
if (viewIdName.isNotEmpty()) {
|
||||
if (viewIdName == "downloadAll") {
|
||||
val showDownloadCard = sharedPreferences!!.getBoolean("download_card", true)
|
||||
val jobID = downloadViewModel.turnResultItemsToProcessingDownloads(resultsList!!.map { it!!.id }, downloadNow = !showDownloadCard)
|
||||
downloadViewModel.turnResultItemsToProcessingDownloads(resultsList!!.map { it!!.id }, downloadNow = !showDownloadCard)
|
||||
if (showDownloadCard){
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
||||
Pair("processingDownloadsJobID", jobID)
|
||||
))
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -807,11 +809,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
downloadViewModel.getDownloadType(url = selectedObjects[0].url)
|
||||
)
|
||||
}else{
|
||||
val jobID = downloadViewModel.turnResultItemsToProcessingDownloads(selectedObjects.map { it.id }, downloadNow = !showDownloadCard)
|
||||
downloadViewModel.turnResultItemsToProcessingDownloads(selectedObjects.map { it.id }, downloadNow = !showDownloadCard)
|
||||
if (showDownloadCard){
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
||||
Pair("processingDownloadsJobID", jobID)
|
||||
))
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2)
|
||||
}
|
||||
}
|
||||
clearCheckedItems()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.view.ViewGroup
|
|||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
|
@ -121,22 +122,27 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
|
||||
cancelButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)}
|
||||
|
||||
val activePaused = item.status == DownloadRepository.Status.ActivePaused.toString()
|
||||
val resumeButton = card.findViewById<MaterialButton>(R.id.active_download_resume)
|
||||
resumeButton.isVisible = activePaused
|
||||
if (resumeButton.hasOnClickListeners()) resumeButton.setOnClickListener(null)
|
||||
resumeButton.setOnClickListener {
|
||||
resumeButton.isVisible = false
|
||||
onItemClickListener.onResumeClick(item.id)
|
||||
}
|
||||
|
||||
when(DownloadRepository.Status.valueOf(item.status)){
|
||||
DownloadRepository.Status.Active -> {
|
||||
progressBar.isIndeterminate = true
|
||||
cancelButton.isEnabled = true
|
||||
}
|
||||
DownloadRepository.Status.ActivePaused -> {
|
||||
progressBar.isIndeterminate = false
|
||||
cancelButton.isEnabled = true
|
||||
output.text = activity.getString(R.string.exo_download_paused)
|
||||
}
|
||||
else -> {}
|
||||
if (sharedPreferences.getBoolean("paused_downloads", false) || activePaused) {
|
||||
progressBar.isIndeterminate = false
|
||||
cancelButton.isEnabled = true
|
||||
output.text = activity.getString(R.string.exo_download_paused)
|
||||
}else{
|
||||
progressBar.isIndeterminate = true
|
||||
cancelButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
interface OnItemClickListener {
|
||||
fun onCancelClick(itemID: Long)
|
||||
fun onResumeClick(itemID: Long)
|
||||
fun onOutputClick(item: DownloadItem)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
else fileSize.text = fileSizeReadable
|
||||
|
||||
val menu = card.findViewById<View>(R.id.options)
|
||||
val paused = sharedPreferences.getBoolean("paused_downloads", false) || item.status == DownloadRepository.Status.ActivePaused.toString()
|
||||
menu.setOnClickListener {
|
||||
val popup = PopupMenu(activity, it)
|
||||
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
|
||||
|
|
@ -133,7 +134,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
val pause = popup.menu[0]
|
||||
val resume = popup.menu[1]
|
||||
|
||||
if (item.status == DownloadRepository.Status.ActivePaused.toString()){
|
||||
if (paused){
|
||||
pause.isVisible = false
|
||||
resume.isVisible = true
|
||||
}else{
|
||||
|
|
@ -165,7 +166,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
|
||||
}
|
||||
|
||||
progressBar.isIndeterminate = item.status != DownloadRepository.Status.ActivePaused.toString()
|
||||
progressBar.isIndeterminate = !paused
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onCardClick()
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
val author = card.findViewById<TextView>(R.id.author)
|
||||
author.text = video.author
|
||||
val duration = card.findViewById<TextView>(R.id.duration)
|
||||
if (video.duration.isNotEmpty()) {
|
||||
if (video.duration.isNotEmpty() && video.duration != "-1") {
|
||||
duration.text = video.duration
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.content.res.ColorStateList
|
|||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.method.DigitsKeyListener
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -144,7 +145,9 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
|||
muteBtn = view.findViewById(R.id.mute)
|
||||
rangeSlider = view.findViewById(R.id.rangeSlider)
|
||||
fromTextInput = view.findViewById(R.id.from_textinput_edittext)
|
||||
fromTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||
toTextInput = view.findViewById(R.id.to_textinput_edittext)
|
||||
toTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.")
|
||||
cancelBtn = view.findViewById(R.id.cancelButton)
|
||||
okBtn = view.findViewById(R.id.okButton)
|
||||
suggestedChips = view.findViewById(R.id.chapters)
|
||||
|
|
|
|||
|
|
@ -85,7 +85,21 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO) {
|
||||
if (currentDownloadItem != null){
|
||||
if (savedInstanceState?.containsKey("updated") == true) {
|
||||
downloadItem.apply {
|
||||
title = resultItem!!.title
|
||||
author = resultItem!!.author
|
||||
allFormats = resultItem!!.formats
|
||||
format = downloadViewModel.getFormat(allFormats, Type.audio)
|
||||
duration = resultItem!!.duration
|
||||
playlistIndex = resultItem!!.playlistIndex
|
||||
playlistURL = resultItem!!.playlistURL
|
||||
playlistTitle = resultItem!!.playlistTitle
|
||||
thumb = resultItem!!.thumb
|
||||
website = resultItem!!.website
|
||||
url = resultItem!!.url
|
||||
}
|
||||
}else if (currentDownloadItem != null){
|
||||
//object cloning
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
|
|
|
|||
|
|
@ -323,6 +323,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
|
||||
getAlsoAudioDownloadItem{ audioDownloadItem ->
|
||||
audioDownloadItem.downloadStartTime = it.timeInMillis
|
||||
audioDownloadItem.status = DownloadRepository.Status.Scheduled.toString()
|
||||
itemsToQueue.add(audioDownloadItem)
|
||||
|
||||
runBlocking {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ 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
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
|
@ -66,6 +68,7 @@ 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
|
||||
|
|
@ -73,6 +76,7 @@ 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 {
|
||||
|
|
@ -87,12 +91,17 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
private lateinit var bottomAppBar: BottomAppBar
|
||||
private lateinit var filesize : TextView
|
||||
private lateinit var count : TextView
|
||||
private lateinit var title: TextView
|
||||
private lateinit var subtitle: TextView
|
||||
private lateinit var shimmerTitle: ShimmerFrameLayout
|
||||
private lateinit var shimmerSubtitle: ShimmerFrameLayout
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private lateinit var currentDownloadIDs: List<Long>
|
||||
private var processingDownloadsJobID: Long = 0
|
||||
private var processingItemsCount : Int = 0
|
||||
|
||||
private lateinit var jobData: DownloadViewModel.ProcessingItemsJob
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
|
|
@ -103,7 +112,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
infoUtil = InfoUtil(requireContext())
|
||||
|
||||
currentDownloadIDs = arguments?.getLongArray("currentDownloadIDs")?.toList() ?: listOf()
|
||||
processingDownloadsJobID = arguments?.getLong("processingDownloadsJobID") ?: 0
|
||||
processingItemsCount = currentDownloadIDs.size
|
||||
}
|
||||
|
||||
|
|
@ -150,14 +158,76 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
||||
val progressBar = view.findViewById<LinearProgressIndicator>(R.id.loadingItemsProgress)
|
||||
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)
|
||||
shimmerSubtitle = view.findViewById(R.id.shimmer_loading_subtitle)
|
||||
|
||||
downloadViewModel.processingItemsJob.observe(this) {
|
||||
it?.apply {
|
||||
lifecycleScope.launch {
|
||||
jobData = it
|
||||
processingItemsCount = it.processingDownloadItemIDs.size
|
||||
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
|
||||
|
||||
val loading = it.originItemIDs.size != processingItemsCount
|
||||
toggleLoadingShimmerTitle(loading)
|
||||
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
|
||||
if (!loading){
|
||||
continueInBackgroundSnackBar.dismiss()
|
||||
}
|
||||
|
||||
downloadViewModel.processingDownloads.map { p -> p.filter { pd -> jobData.processingDownloadItemIDs.contains(pd.id) } }.collectLatest { items ->
|
||||
listAdapter.submitList(items)
|
||||
updateFileSize(items.map { it2 -> it2.format.filesize })
|
||||
|
||||
if (items.isNotEmpty()){
|
||||
if (items.all { it2 -> it2.type == items[0].type }) {
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
if (items[0].type != DownloadViewModel.Type.command) {
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
}
|
||||
} else {
|
||||
bottomAppBar.menu[1].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}
|
||||
|
||||
val type = items.first().type
|
||||
|
||||
when(type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
|
||||
}
|
||||
DownloadViewModel.Type.command -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scheduleBtn.setOnClickListener{
|
||||
fun initSchedule() {
|
||||
fun initSchedule(processingFinished: Boolean) {
|
||||
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
||||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
|
|
@ -167,11 +237,9 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
}
|
||||
|
||||
val jobData = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getLoadingProcessingDownloadJob(processingDownloadsJobID)
|
||||
}
|
||||
jobData?.job?.cancel(CancellationException())
|
||||
downloadProcessDownloadsInBackground(jobData, cal.timeInMillis)
|
||||
jobData.job?.cancel(CancellationException())
|
||||
jobData.job = null
|
||||
downloadProcessDownloadsInBackground(jobData, 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()
|
||||
|
|
@ -181,22 +249,18 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
|
||||
if (progressBar.isVisible){
|
||||
val continueInBackgroundSnackBar = Snackbar.make(view, 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
|
||||
if (shimmerTitle.isVisible){
|
||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||
initSchedule()
|
||||
initSchedule(false)
|
||||
}
|
||||
continueInBackgroundSnackBar.show()
|
||||
}else{
|
||||
initSchedule()
|
||||
initSchedule(true)
|
||||
}
|
||||
}
|
||||
|
||||
download!!.setOnClickListener {
|
||||
fun initDownload() {
|
||||
fun initDownload(processingFinished: Boolean) {
|
||||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
lifecycleScope.launch {
|
||||
|
|
@ -204,26 +268,20 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
}
|
||||
|
||||
val jobData = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getLoadingProcessingDownloadJob(processingDownloadsJobID)
|
||||
}
|
||||
jobData?.job?.cancel(CancellationException())
|
||||
downloadProcessDownloadsInBackground(jobData)
|
||||
jobData.job?.cancel(CancellationException())
|
||||
jobData.job = null
|
||||
downloadProcessDownloadsInBackground(jobData, processingFinished)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
if (progressBar.isVisible){
|
||||
val continueInBackgroundSnackBar = Snackbar.make(view, 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
|
||||
if (shimmerTitle.isVisible){
|
||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||
initDownload()
|
||||
initDownload(false)
|
||||
}
|
||||
continueInBackgroundSnackBar.show()
|
||||
}else{
|
||||
initDownload()
|
||||
initDownload(true)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -237,8 +295,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||
downloadViewModel.moveProcessingToSavedCategory()
|
||||
downloadViewModel.moveProcessingToSavedCategory(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
jobData.job?.cancel(CancellationException())
|
||||
jobData.job = null
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -246,19 +306,16 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
true
|
||||
}
|
||||
|
||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
||||
|
||||
val formatListener = object : OnFormatClickListener {
|
||||
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingFormat(selectedFormats)
|
||||
downloadViewModel.updateProcessingFormat(jobData.processingDownloadItemIDs, selectedFormats)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingAllFormats(allFormats)
|
||||
downloadViewModel.updateProcessingAllFormats(jobData.processingDownloadItemIDs, allFormats)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,8 +323,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
override fun onContinueOnBackground() {
|
||||
requireActivity().lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.continueUpdatingFormatsOnBackground()
|
||||
downloadViewModel.continueUpdatingFormatsOnBackground(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
jobData.job?.cancel(CancellationException())
|
||||
jobData.job = null
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -305,7 +364,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
audio!!.setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
|
||||
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.audio)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
|
|
@ -317,7 +376,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
video!!.setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
|
||||
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.video)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
|
|
@ -329,7 +388,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
command!!.setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
|
||||
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.command)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
|
|
@ -353,7 +412,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
R.id.format -> {
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs,)
|
||||
}
|
||||
if (!res.first){
|
||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -372,12 +431,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingCommandFormat(format)
|
||||
downloadViewModel.updateProcessingCommandFormat(jobData.processingDownloadItemIDs, format)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
||||
val commonFormats = withContext(Dispatchers.IO){
|
||||
|
|
@ -401,7 +460,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
R.id.more -> {
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
if (!res.first) {
|
||||
Toast.makeText(
|
||||
|
|
@ -422,7 +481,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
|
||||
UiUtil.configureAudio(
|
||||
|
|
@ -503,7 +562,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
|
||||
UiUtil.configureVideo(
|
||||
|
|
@ -598,64 +657,31 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
true
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.loadingProcessingDownloadsJobs.collectLatest { jobs ->
|
||||
processingItemsCount = jobs.firstOrNull { j -> j.jobID == processingDownloadsJobID }?.itemIDs?.size ?: currentDownloadIDs.size
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.processingDownloads.collectLatest {
|
||||
count.text = "${it.size} ${getString(R.string.selected)}"
|
||||
}
|
||||
|
||||
val loading = it.size != processingItemsCount
|
||||
progressBar.isVisible = loading
|
||||
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
|
||||
private fun toggleLoadingShimmerTitle(show: Boolean) {
|
||||
shimmerTitle.isVisible = show
|
||||
title.isVisible = !show
|
||||
shimmerSubtitle.isVisible = show
|
||||
subtitle.isVisible = !show
|
||||
|
||||
listAdapter.submitList(it)
|
||||
withContext(Dispatchers.Main){
|
||||
updateFileSize(it.map { it2 -> it2.format.filesize })
|
||||
}
|
||||
|
||||
if (it.isNotEmpty()){
|
||||
if (it.all { it2 -> it2.type == it[0].type }) {
|
||||
withContext(Dispatchers.Main) {
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
if (it[0].type != DownloadViewModel.Type.command) {
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bottomAppBar.menu[1].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}
|
||||
|
||||
val type = it.first().type
|
||||
|
||||
when(type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
|
||||
}
|
||||
DownloadViewModel.Type.command -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if (show){
|
||||
shimmerTitle.startShimmer()
|
||||
shimmerSubtitle.startShimmer()
|
||||
}else{
|
||||
shimmerTitle.stopShimmer()
|
||||
shimmerSubtitle.stopShimmer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFileSize(items: List<Long>){
|
||||
if (items.all { it > 0L }){
|
||||
filesize.visibility = View.VISIBLE
|
||||
filesize.text = "${getString(R.string.file_size)}: >~ ${FileUtil.convertFileSize(items.sum())}"
|
||||
if (items.all { it > 5L }){
|
||||
val size = FileUtil.convertFileSize(items.sum())
|
||||
if (size != "?"){
|
||||
filesize.visibility = View.VISIBLE
|
||||
filesize.text = "${getString(R.string.file_size)}: >~ $size"
|
||||
}
|
||||
|
||||
}else{
|
||||
filesize.visibility = View.GONE
|
||||
}
|
||||
|
|
@ -674,7 +700,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingDownloadPath(result.data?.data.toString())
|
||||
downloadViewModel.updateProcessingDownloadPath(jobData.processingDownloadItemIDs, result.data?.data.toString())
|
||||
}
|
||||
|
||||
val path = FileUtil.formatPath(result.data!!.data.toString())
|
||||
|
|
@ -802,13 +828,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
listAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
downloadViewModel.cancelLoadingProcessingDownloads(processingDownloadsJobID)
|
||||
downloadViewModel.deleteProcessing()
|
||||
if (jobData.job != null){
|
||||
jobData.job?.cancel(CancellationException())
|
||||
downloadViewModel.deleteAllWithID(jobData.processingDownloadItemIDs)
|
||||
}
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
|
|
@ -889,14 +916,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
|
||||
private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob?, timeInMillis: Long = 0){
|
||||
private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob, processingFinished: Boolean, timeInMillis: Long = 0){
|
||||
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
||||
jobData?.apply {
|
||||
intent.putExtra("itemType", jobData.itemType)
|
||||
intent.putExtra("itemIDs", jobData.itemIDs.toLongArray())
|
||||
intent.putExtra("timeInMillis", timeInMillis)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,21 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO){
|
||||
if (currentDownloadItem != null){
|
||||
if (savedInstanceState?.containsKey("updated") == true) {
|
||||
downloadItem.apply {
|
||||
title = resultItem!!.title
|
||||
author = resultItem!!.author
|
||||
allFormats = resultItem!!.formats
|
||||
format = downloadViewModel.getFormat(allFormats, Type.video)
|
||||
duration = resultItem!!.duration
|
||||
playlistIndex = resultItem!!.playlistIndex
|
||||
playlistURL = resultItem!!.playlistURL
|
||||
playlistTitle = resultItem!!.playlistTitle
|
||||
thumb = resultItem!!.thumb
|
||||
website = resultItem!!.website
|
||||
url = resultItem!!.url
|
||||
}
|
||||
}else if (currentDownloadItem != null){
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
try{
|
||||
//simple download
|
||||
if (items.size == 1) {
|
||||
formatCollection.clear()
|
||||
kotlin.runCatching {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
infoUtil.getFormats(items.first()!!.url)
|
||||
|
|
@ -195,6 +196,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
withContext(Dispatchers.Main){
|
||||
listener.onFormatsUpdated(formats)
|
||||
}
|
||||
formatCollection.add(res)
|
||||
}.onFailure { err ->
|
||||
withContext(Dispatchers.Main){
|
||||
UiUtil.handleResultResponse(requireActivity(), ResultViewModel.ResultsUiState(
|
||||
|
|
|
|||
|
|
@ -186,11 +186,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
|||
))
|
||||
}
|
||||
}else{
|
||||
val jobID = downloadViewModel.turnResultItemsToProcessingDownloads(checkedResultItems.map { it!!.id })
|
||||
downloadViewModel.turnResultItemsToProcessingDownloads(checkedResultItems.map { it.id })
|
||||
withContext(Dispatchers.Main){
|
||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadMultipleBottomSheetDialog,
|
||||
bundleOf(Pair("processingDownloadsJobID", jobID))
|
||||
)
|
||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadMultipleBottomSheetDialog)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,21 +2,13 @@ package com.deniscerri.ytdl.ui.downloads
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -24,38 +16,30 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkQuery
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter
|
||||
import com.deniscerri.ytdl.ui.adapter.QueuedDownloadAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
|
||||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
|
||||
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener {
|
||||
|
|
@ -71,6 +55,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
private lateinit var resume: ExtendedFloatingActionButton
|
||||
private lateinit var noResults: RelativeLayout
|
||||
private lateinit var workManager: WorkManager
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -90,6 +75,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
notificationUtil = NotificationUtil(requireContext())
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
workManager = WorkManager.getInstance(requireContext())
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
activeDownloads = ActiveDownloadAdapter(this,requireActivity())
|
||||
activeRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
|
|
@ -105,102 +91,45 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
pause.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
pause.isEnabled = false
|
||||
|
||||
// pause queued
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}.forEach {
|
||||
it.status = DownloadRepository.Status.QueuedPaused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
// pause active
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}.forEach {
|
||||
cancelItem(it.id.toInt())
|
||||
it.status = DownloadRepository.Status.ActivePaused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
||||
}
|
||||
|
||||
preferences.edit().putBoolean("paused_downloads", true).apply()
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
pause.isEnabled = true
|
||||
resume.isVisible = true
|
||||
pause.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
resume.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
resume.isEnabled = false
|
||||
|
||||
val active = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}
|
||||
|
||||
val toQueue = active.filter { it.status == DownloadRepository.Status.ActivePaused.toString() }.toMutableList()
|
||||
runBlocking {
|
||||
toQueue.forEach {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
preferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
resume.isVisible = false
|
||||
pause.isVisible = true
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.resetActivePaused()
|
||||
downloadViewModel.startDownloadWorker(listOf())
|
||||
}
|
||||
|
||||
val queuedItems = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
queuedItems.map {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
resume.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@forEach
|
||||
val id = work.progress.getLong("id", 0L)
|
||||
if(id == 0L) return@forEach
|
||||
|
||||
val progress = work.progress.getInt("progress", 0)
|
||||
val output = work.progress.getString("output")
|
||||
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
val outputText = view.findViewWithTag<TextView>("$id##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
outputText?.text = output
|
||||
}catch (ignored: Exception) {}
|
||||
withContext(Dispatchers.Main){
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeAndActivePausedDownloadsCount.collectLatest {
|
||||
noResults.isVisible = it == 0
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||
pause.isVisible = it > 0
|
||||
noResults.isVisible = it == 0
|
||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||
pause.isVisible = it > 0 && !pausedDownloads
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.pausedDownloadsCount.collectLatest {
|
||||
resume.isVisible = it > 0
|
||||
downloadViewModel.activeQueuedDownloadsCount.collectLatest {
|
||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||
resume.isVisible = it > 0 && pausedDownloads
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,27 +141,79 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
try {
|
||||
progressBar?.setProgressCompat(event.progress, true)
|
||||
outputText?.text = event.output
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
override fun onCancelClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val count = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}.count()
|
||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
||||
|
||||
if (count == 1){
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
||||
val activeCount = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
}
|
||||
|
||||
if (activeCount == 0){
|
||||
val queuedCount = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueuedDownloadsCount()
|
||||
}
|
||||
if (queuedCount == 0) {
|
||||
preferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
}
|
||||
}
|
||||
|
||||
if (activeCount == 1){
|
||||
val queue = withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
if (!preferences.getBoolean("paused_downloads", false)){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cancelDownload(itemID)
|
||||
override fun onResumeClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOutputClick(item: DownloadItem) {
|
||||
|
|
@ -246,22 +227,4 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
private fun cancelItem(id: Int){
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
}
|
||||
|
||||
private fun cancelDownload(itemID: Long){
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,15 +130,17 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
val reconfigureID = arguments?.getLong("reconfigure")
|
||||
reconfigureID?.apply {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(reconfigureID)
|
||||
kotlin.runCatching {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(reconfigureID)
|
||||
}
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", item),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(item)),
|
||||
Pair("type", item.type)
|
||||
)
|
||||
)
|
||||
}
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", item),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(item)),
|
||||
Pair("type", item.type)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -227,8 +229,8 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
R.id.copy_urls -> {
|
||||
lifecycleScope.launch {
|
||||
val tabStatus = mapOf(
|
||||
0 to listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused),
|
||||
1 to listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused),
|
||||
0 to listOf(DownloadRepository.Status.Active),
|
||||
1 to listOf(DownloadRepository.Status.Queued),
|
||||
2 to listOf(DownloadRepository.Status.Scheduled),
|
||||
3 to listOf(DownloadRepository.Status.Cancelled),
|
||||
4 to listOf(DownloadRepository.Status.Error),
|
||||
|
|
@ -263,11 +265,10 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
|
||||
private fun cancelAllDownloads() {
|
||||
cancelBackgroundProcessingDownloads()
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
lifecycleScope.launch {
|
||||
val notificationUtil = NotificationUtil(requireContext())
|
||||
downloadViewModel.cancelActiveQueued()
|
||||
cancelBackgroundProcessingDownloads()
|
||||
val activeAndQueued = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
|
|
@ -275,6 +276,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||
}
|
||||
downloadViewModel.cancelActiveQueued()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,6 +298,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
|
||||
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
||||
intent.putExtra("binding", true)
|
||||
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)).observe(viewLifecycleOwner){
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Queued)).observe(viewLifecycleOwner){
|
||||
totalSize = it
|
||||
noResults.isVisible = it == 0
|
||||
dragHandleToggle.isVisible = it > 1
|
||||
|
|
@ -190,7 +190,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
|||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(
|
||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused).toListString())
|
||||
DownloadRepository.Status.Queued).toListString())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
|
|
@ -294,7 +294,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
|||
return if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf(
|
||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
|
||||
DownloadRepository.Status.Queued))
|
||||
}
|
||||
}else{
|
||||
adapter.checkedItems.toList()
|
||||
|
|
@ -475,12 +475,18 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", it),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(it)),
|
||||
Pair("type", it.type)
|
||||
)
|
||||
)
|
||||
lifecycleScope.launch {
|
||||
it.status = DownloadRepository.Status.Saved.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateToStatus(it.id, DownloadRepository.Status.Saved)
|
||||
}
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", it),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(it)),
|
||||
Pair("type", it.type)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
|
|
@ -517,7 +523,7 @@ class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickLis
|
|||
if(selectedObjects == 2){
|
||||
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused).toListString())
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Queued).toListString())
|
||||
}
|
||||
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
val itemId = m.itemId
|
||||
if (itemId == R.id.remove_logs) {
|
||||
throw Exception("asffffffffff")
|
||||
try{
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.confirm_delete_history))
|
||||
|
|
|
|||
|
|
@ -80,7 +80,9 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
withContext(Dispatchers.IO){
|
||||
updateUtil!!.updateApp{ msg ->
|
||||
lifecycleScope.launch(Dispatchers.Main){
|
||||
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
|
||||
view?.apply {
|
||||
Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,14 +121,17 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
}
|
||||
}.onFailure {
|
||||
val msg = it.message ?: requireContext().getString(R.string.errored)
|
||||
val snackBar = Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG)
|
||||
snackBar.setAction(R.string.copy_log){
|
||||
UiUtil.copyToClipboard(msg, requireActivity())
|
||||
view?.apply {
|
||||
val snackBar = Snackbar.make(this, msg, Snackbar.LENGTH_LONG)
|
||||
snackBar.setAction(R.string.copy_log){
|
||||
UiUtil.copyToClipboard(msg, requireActivity())
|
||||
}
|
||||
val snackbarView: View = snackBar.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
snackBar.show()
|
||||
}
|
||||
val snackbarView: View = snackBar.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
snackBar.show()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,14 @@ import com.deniscerri.ytdl.database.models.TerminalItem
|
|||
import com.deniscerri.ytdl.database.viewmodel.TerminalViewModel
|
||||
import com.deniscerri.ytdl.ui.adapter.TerminalDownloadsAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
|
||||
class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnItemClickListener {
|
||||
|
|
@ -69,30 +73,30 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
|
|||
}
|
||||
}
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData("terminal")
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@forEach
|
||||
val id = work.progress.getInt("id", 0)
|
||||
if(id == 0) return@forEach
|
||||
|
||||
var progress = work.progress.getInt("progress", 0)
|
||||
if (progress < 0) progress = 0
|
||||
val output = work.progress.getString("output")
|
||||
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
val outputText = view.findViewWithTag<TextView>("$id##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
kotlin.runCatching {
|
||||
outputText?.text = output
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||
val outputText = requireView().findViewWithTag<TextView>("${event.downloadItemID}##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
kotlin.runCatching {
|
||||
outputText?.text = event.output
|
||||
progressBar?.setProgressCompat(event.progress, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelClick(itemID: Long) {
|
||||
terminalViewModel.cancelTerminalDownload(itemID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,15 +40,20 @@ import com.deniscerri.ytdl.util.Extensions.setCustomTextSize
|
|||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,8 @@ import kotlin.system.exitProcess
|
|||
class CrashListener(private val context: Context) : Thread.UncaughtExceptionHandler {
|
||||
|
||||
override fun uncaughtException(p0: Thread, p1: Throwable) {
|
||||
p1.message?.apply {
|
||||
Log.e("ERROR", this)
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
createLog(this@apply)
|
||||
}
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
createLog("${p1.message}\n${p1.stackTrace}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -467,7 +467,6 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--skip-download")
|
||||
request.addOption("-R", "1")
|
||||
request.addOption("--compat-options", "manifest-filesize-approx")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
|
|
@ -993,6 +992,8 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--no-resize-buffer")
|
||||
}
|
||||
|
||||
request.addOption("--socket-timeout", 60)
|
||||
|
||||
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
|
||||
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
||||
|
||||
|
|
@ -1059,7 +1060,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if(downloadItem.title.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(150)))
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(120)))
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1115,7 +1116,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
var audioQualityId : String = downloadItem.format.format_id
|
||||
if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "ba", "best", "").contains(audioQualityId)){
|
||||
audioQualityId = ""
|
||||
audioQualityId = "ba/b"
|
||||
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
|
||||
audioQualityId = "wa/w"
|
||||
}
|
||||
|
|
@ -1149,7 +1150,7 @@ class InfoUtil(private val context: Context) {
|
|||
if(ext.isNotBlank()){
|
||||
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){
|
||||
request.addOption("--audio-format", ext)
|
||||
formatSorting.append(",ext:$ext")
|
||||
formatSorting.append(",ext::$ext")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1171,7 +1172,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
if (downloadItem.playlistTitle.isNotEmpty()) {
|
||||
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
|
||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track_number)s")
|
||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(track_number)s")
|
||||
} else {
|
||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||
}
|
||||
|
|
@ -1261,6 +1262,7 @@ class InfoUtil(private val context: Context) {
|
|||
val f = StringBuilder()
|
||||
|
||||
val preferredCodec = sharedPreferences.getString("video_codec", "")
|
||||
val preferredQuality = sharedPreferences.getString("video_quality", "best")
|
||||
val vCodecPrefIndex = context.getStringArray(R.array.video_codec_values).indexOf(preferredCodec)
|
||||
val vCodecPref = context.getStringArray(R.array.video_codec_values_ytdlp)[vCodecPrefIndex]
|
||||
|
||||
|
|
@ -1358,12 +1360,18 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
}
|
||||
|
||||
val genericFormats = getGenericVideoFormats(context.resources).map { it.format_id }
|
||||
|
||||
StringBuilder().apply {
|
||||
if (hasGenericResulutionFormat.isNotBlank()) append(",res:${hasGenericResulutionFormat}")
|
||||
if (hasGenericResulutionFormat.isNotBlank()) {
|
||||
append(",res:${hasGenericResulutionFormat}")
|
||||
}else if (genericFormats.contains(videoF) && preferredQuality!!.contains("p_")){
|
||||
append(",res:${preferredQuality.split("_")[0]}")
|
||||
}
|
||||
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
|
||||
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
||||
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
|
||||
if (cont.isNotBlank()) append(",ext:$cont")
|
||||
if (cont.isNotBlank()) append(",ext:$cont:")
|
||||
if (this.isNotBlank()){
|
||||
request.addOption("-S", "+hasaud$this")
|
||||
}
|
||||
|
|
@ -1489,7 +1497,7 @@ class InfoUtil(private val context: Context) {
|
|||
val audioFormatsValues = resources.getStringArray(R.array.audio_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("audio_format", "")
|
||||
val acodecPreference = sharedPreferences.getString("audio_codec", "m4a|mp4a|aac")!!.run {
|
||||
val acodecPreference = sharedPreferences.getString("audio_codec", "")!!.run {
|
||||
if (this.isEmpty()){
|
||||
resources.getString(R.string.defaultValue)
|
||||
}else{
|
||||
|
|
@ -1508,14 +1516,14 @@ class InfoUtil(private val context: Context) {
|
|||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("video_format", "")
|
||||
val audioCodecPreference = sharedPreferences.getString("audio_codec", "m4a|mp4a|aac")!!.run {
|
||||
val audioCodecPreference = sharedPreferences.getString("audio_codec", "")!!.run {
|
||||
if (this.isNotEmpty()){
|
||||
val audioCodecs = resources.getStringArray(R.array.audio_codec)
|
||||
val audioCodecsValues = resources.getStringArray(R.array.audio_codec_values)
|
||||
audioCodecs[audioCodecsValues.indexOf(this)]
|
||||
}else this
|
||||
}
|
||||
val videoCodecPreference = sharedPreferences.getString("video_codec", "avc|h264")!!.run {
|
||||
val videoCodecPreference = sharedPreferences.getString("video_codec", "")!!.run {
|
||||
if (this.isEmpty()){
|
||||
resources.getString(R.string.defaultValue)
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
|
|
@ -14,6 +15,7 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
|
@ -36,15 +38,11 @@ class NotificationUtil(var context: Context) {
|
|||
private val erroredDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_ERRORED_CHANNEL_ID)
|
||||
private val miscDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_MISC_CHANNEL_ID)
|
||||
|
||||
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
||||
private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context)
|
||||
private val resources: Resources = context.resources
|
||||
|
||||
fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = context.getSystemService(
|
||||
NotificationManager::class.java
|
||||
)
|
||||
|
||||
//downloading worker notification
|
||||
var name: CharSequence = resources.getString(R.string.downloading)
|
||||
var description = "WorkManager Default Notification"
|
||||
|
|
@ -176,6 +174,7 @@ class NotificationUtil(var context: Context) {
|
|||
.build()
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun createResumeDownload(itemID: Int, title: String?){
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||
|
||||
|
|
@ -205,6 +204,7 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID + itemID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun createUpdatingItemNotification(channel: String){
|
||||
val notificationBuilder = getBuilder(channel)
|
||||
|
||||
|
|
@ -223,6 +223,7 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.notify(DOWNLOAD_UPDATING_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun createDownloadFinished(
|
||||
id: Long,
|
||||
title: String?,
|
||||
|
|
@ -319,6 +320,7 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build())
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun createDownloadErrored(
|
||||
id: Long,
|
||||
title: String?,
|
||||
|
|
@ -379,10 +381,12 @@ class NotificationUtil(var context: Context) {
|
|||
}
|
||||
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun notify(id: Int, notification: Notification){
|
||||
notificationManager.notify(id, notification)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateDownloadNotification(
|
||||
id: Int,
|
||||
desc: String,
|
||||
|
|
@ -428,6 +432,8 @@ class NotificationUtil(var context: Context) {
|
|||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateTerminalDownloadNotification(
|
||||
id: Int,
|
||||
desc: String,
|
||||
|
|
@ -488,6 +494,8 @@ class NotificationUtil(var context: Context) {
|
|||
.build()
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateCacheMovingNotification(id: Int, progress: Int, totalFiles: Int) {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
val contentText = "${progress}/${totalFiles}"
|
||||
|
|
@ -542,6 +550,7 @@ class NotificationUtil(var context: Context) {
|
|||
.build()
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateFormatUpdateNotification(
|
||||
workID: Int,
|
||||
workTag: String,
|
||||
|
|
@ -576,6 +585,7 @@ class NotificationUtil(var context: Context) {
|
|||
}
|
||||
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun showFormatsUpdatedNotification(downloadIds: List<Long>) {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||
|
||||
|
|
@ -606,6 +616,8 @@ class NotificationUtil(var context: Context) {
|
|||
notificationManager.notify(FORMAT_UPDATING_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun showQueriesFinished() {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ import java.io.File
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.Queue
|
||||
|
||||
|
||||
object UiUtil {
|
||||
|
|
@ -709,7 +710,13 @@ object UiUtil {
|
|||
}
|
||||
}
|
||||
DownloadRepository.Status.Queued -> {
|
||||
download!!.visibility = View.GONE
|
||||
download!!.text = context.getString(R.string.configure_download)
|
||||
download.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_retries, 0, 0, 0);
|
||||
download.setOnClickListener {
|
||||
longClickDownloadButton(item)
|
||||
bottomSheet.cancel()
|
||||
true
|
||||
}
|
||||
}
|
||||
DownloadRepository.Status.Scheduled -> {
|
||||
download!!.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_downloads, 0, 0, 0);
|
||||
|
|
@ -724,9 +731,11 @@ object UiUtil {
|
|||
}
|
||||
}
|
||||
|
||||
download?.setOnClickListener {
|
||||
bottomSheet.dismiss()
|
||||
downloadItem(item)
|
||||
if (status != DownloadRepository.Status.Queued){
|
||||
download?.setOnClickListener {
|
||||
bottomSheet.dismiss()
|
||||
downloadItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
|
|
@ -860,9 +869,10 @@ object UiUtil {
|
|||
showGeneratedCommand(context, item.command)
|
||||
}
|
||||
|
||||
location?.isVisible = true
|
||||
val availableFiles = item.downloadPath.filter { FileUtil.exists(it) }
|
||||
location?.isVisible = availableFiles.isNotEmpty()
|
||||
location?.setOnClickListener {
|
||||
showFullTextDialog(context, item.downloadPath.joinToString("\n"), context.getString(R.string.location))
|
||||
showFullTextDialog(context, availableFiles.joinToString("\n"), context.getString(R.string.location))
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1316,7 +1326,8 @@ object UiUtil {
|
|||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if (items.size > 1 || items.first().url.isEmpty()) cut.isVisible = false
|
||||
else{
|
||||
if(items[0].duration.isNotEmpty()){
|
||||
val invalidDuration = items[0].duration == "-1"
|
||||
if(items[0].duration.isNotEmpty() && !invalidDuration){
|
||||
val downloadItem = items[0]
|
||||
cut.alpha = 1f
|
||||
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
|
||||
|
|
@ -1355,8 +1366,10 @@ object UiUtil {
|
|||
}
|
||||
}else{
|
||||
cut.alpha = 0.3f
|
||||
cut.setOnClickListener {
|
||||
cutDisabledClicked()
|
||||
if (!invalidDuration) {
|
||||
cut.setOnClickListener {
|
||||
cutDisabledClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1549,7 +1562,8 @@ object UiUtil {
|
|||
if (items.size > 1 || items.first().url.isEmpty()) cut.isVisible = false
|
||||
else{
|
||||
val downloadItem = items[0]
|
||||
if (downloadItem.duration.isNotEmpty()){
|
||||
val invalidDuration = downloadItem.duration == "-1"
|
||||
if (downloadItem.duration.isNotEmpty() && !invalidDuration){
|
||||
cut.alpha = 1f
|
||||
if (downloadItem.downloadSections.isNotBlank()) cut.text = downloadItem.downloadSections
|
||||
val cutVideoListener = object : VideoCutListener {
|
||||
|
|
@ -1579,8 +1593,10 @@ object UiUtil {
|
|||
|
||||
}else{
|
||||
cut.alpha = 0.3f
|
||||
cut.setOnClickListener {
|
||||
cutDisabledClicked()
|
||||
if (!invalidDuration) {
|
||||
cut.setOnClickListener {
|
||||
cutDisabledClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
|
|
@ -88,6 +89,7 @@ class DownloadWorker(
|
|||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
queuedItems.collect { items ->
|
||||
if (isStopped) return@collect
|
||||
runningYTDLInstances.clear()
|
||||
val activeDownloads = dao.getActiveDownloadsList()
|
||||
activeDownloads.forEach {
|
||||
|
|
@ -120,7 +122,7 @@ class DownloadWorker(
|
|||
//update item if its incomplete
|
||||
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
||||
val status = dao.checkStatus(this.id)
|
||||
if (listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused).contains(status)){
|
||||
if (status == DownloadRepository.Status.Active){
|
||||
dao.updateWithoutUpsert(this)
|
||||
}
|
||||
}
|
||||
|
|
@ -158,9 +160,11 @@ class DownloadWorker(
|
|||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
val eventBus = EventBus.getDefault()
|
||||
|
||||
runCatching {
|
||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).ifEmpty { listOf("") }.first().toString(), "id" to downloadItem.id))
|
||||
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id))
|
||||
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
|
||||
notificationUtil.updateDownloadNotification(
|
||||
downloadItem.id.toInt(),
|
||||
|
|
@ -181,7 +185,7 @@ class DownloadWorker(
|
|||
var finalPaths : MutableList<String>?
|
||||
|
||||
if (noCache){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
||||
eventBus.post(WorkerProgress(100, "Scanning Files", downloadItem.id))
|
||||
val outputSequence = it.out.split("\n")
|
||||
finalPaths =
|
||||
outputSequence.asSequence()
|
||||
|
|
@ -206,16 +210,16 @@ class DownloadWorker(
|
|||
}
|
||||
}else{
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
eventBus.post(WorkerProgress(100, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
|
||||
try {
|
||||
finalPaths = withContext(Dispatchers.IO){
|
||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
|
||||
eventBus.post(WorkerProgress(p, "Moving file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
|
||||
}
|
||||
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }.toMutableList()
|
||||
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
|
||||
eventBus.post(WorkerProgress(100, "Moved file to ${FileUtil.formatPath(downloadLocation)}", downloadItem.id))
|
||||
}else{
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
|
|
@ -285,7 +289,7 @@ class DownloadWorker(
|
|||
|
||||
if (wasQuickDownloaded && createResultItem){
|
||||
runCatching {
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
|
||||
eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
|
||||
runBlocking {
|
||||
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
|
||||
if (res != null) {
|
||||
|
|
@ -304,50 +308,46 @@ class DownloadWorker(
|
|||
}
|
||||
|
||||
}.onFailure {
|
||||
if (this@DownloadWorker.isStopped) return@onFailure
|
||||
|
||||
FileUtil.deleteConfigFiles(request)
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
}
|
||||
if (it is YoutubeDL.CanceledException) {
|
||||
if (this@DownloadWorker.isStopped) return@onFailure
|
||||
if (it is YoutubeDL.CanceledException) return@onFailure
|
||||
|
||||
}else{
|
||||
if(it.message != null){
|
||||
if (logDownloads){
|
||||
logRepo.update(it.message!!, logItem.id)
|
||||
}else{
|
||||
logString.append("${it.message}\n")
|
||||
logItem.content = logString.toString()
|
||||
val logID = logRepo.insert(logItem)
|
||||
downloadItem.logID = logID
|
||||
}
|
||||
if(it.message != null){
|
||||
if (logDownloads){
|
||||
logRepo.update(it.message!!, logItem.id)
|
||||
}else{
|
||||
logString.append("${it.message}\n")
|
||||
logItem.content = logString.toString()
|
||||
val logID = logRepo.insert(logItem)
|
||||
downloadItem.logID = logID
|
||||
}
|
||||
|
||||
tempFileDir.delete()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
||||
}, 1000)
|
||||
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
|
||||
downloadItem.status = DownloadRepository.Status.Error.toString()
|
||||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
notificationUtil.createDownloadErrored(
|
||||
downloadItem.id,
|
||||
downloadItem.title.ifEmpty { downloadItem.url },
|
||||
it.message,
|
||||
downloadItem.logID,
|
||||
resources
|
||||
)
|
||||
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to it.toString(), "id" to downloadItem.id))
|
||||
}
|
||||
|
||||
tempFileDir.delete()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
||||
}, 1000)
|
||||
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
|
||||
downloadItem.status = DownloadRepository.Status.Error.toString()
|
||||
runBlocking {
|
||||
dao.update(downloadItem)
|
||||
}
|
||||
|
||||
notificationUtil.createDownloadErrored(
|
||||
downloadItem.id,
|
||||
downloadItem.title.ifEmpty { downloadItem.url },
|
||||
it.message,
|
||||
downloadItem.logID,
|
||||
resources
|
||||
)
|
||||
|
||||
eventBus.post(WorkerProgress(100, it.toString(), downloadItem.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -369,4 +369,10 @@ class DownloadWorker(
|
|||
const val TAG = "DownloadWorker"
|
||||
}
|
||||
|
||||
class WorkerProgress(
|
||||
val progress: Int,
|
||||
val output: String,
|
||||
val downloadItemID: Long
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ class ObserveSourceWorker(
|
|||
}
|
||||
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
|
|
@ -28,7 +27,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
|
@ -99,6 +98,8 @@ class TerminalDownloadWorker(
|
|||
System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
val eventBus = EventBus.getDefault()
|
||||
|
||||
kotlin.runCatching {
|
||||
if (logDownloads){
|
||||
runBlocking {
|
||||
|
|
@ -108,10 +109,7 @@ class TerminalDownloadWorker(
|
|||
|
||||
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
|
||||
runBlocking {
|
||||
line.chunked(10000).forEach {
|
||||
setProgress(workDataOf("progress" to progress.toInt(), "output" to it, "id" to itemId, "log" to logDownloads))
|
||||
Thread.sleep(100)
|
||||
}
|
||||
eventBus.post(DownloadWorker.WorkerProgress(progress.toInt(), line, itemId.toLong()))
|
||||
}
|
||||
|
||||
val title: String = command.take(65)
|
||||
|
|
@ -131,7 +129,7 @@ class TerminalDownloadWorker(
|
|||
//move file from internal to set download directory
|
||||
try {
|
||||
FileUtil.moveFile(File(FileUtil.getCachePath(context) + "/TERMINAL/" + itemId),context, downloadLocation!!, false){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p))
|
||||
eventBus.post(DownloadWorker.WorkerProgress(p, "", itemId.toLong()))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
|
|
|
|||
|
|
@ -105,42 +105,72 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:title="@string/app_name" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:translationZ="250dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="@+id/search_bar"
|
||||
app:layout_constraintStart_toStartOf="@+id/search_bar"
|
||||
app:layout_constraintTop_toTopOf="@+id/search_bar"
|
||||
app:shimmer_auto_start="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="30dp">
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_toolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu" />
|
||||
app:menu="@menu/main_menu">
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
<RelativeLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/recyclerViewHome"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -222,6 +252,10 @@
|
|||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_fabs"
|
||||
|
|
@ -238,6 +272,7 @@
|
|||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
|
|
|||
|
|
@ -91,148 +91,184 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="end"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_toolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:translationZ="250dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="@+id/search_bar"
|
||||
app:layout_constraintStart_toStartOf="@+id/search_bar"
|
||||
app:layout_constraintTop_toTopOf="@+id/search_bar"
|
||||
app:shimmer_auto_start="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="30dp">
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu">
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<RelativeLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="3"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="3"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_below="@id/recyclerViewHome"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="200dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="200dp">
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="200dp">
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="200dp">
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="3"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_fabs"
|
||||
|
|
@ -249,6 +285,7 @@
|
|||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
|
|
|||
|
|
@ -105,123 +105,153 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:title="@string/app_name" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:translationZ="250dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="@+id/search_bar"
|
||||
app:layout_constraintStart_toStartOf="@+id/search_bar"
|
||||
app:layout_constraintTop_toTopOf="@+id/search_bar"
|
||||
app:shimmer_auto_start="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="30dp">
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_toolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu" />
|
||||
app:menu="@menu/main_menu">
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<RelativeLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="2"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/recyclerViewHome"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="250dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="2"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_fabs"
|
||||
|
|
@ -237,6 +267,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
|
|
|
|||
|
|
@ -91,167 +91,203 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="end"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_toolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:translationZ="250dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="@+id/search_bar"
|
||||
app:layout_constraintStart_toStartOf="@+id/search_bar"
|
||||
app:layout_constraintTop_toTopOf="@+id/search_bar"
|
||||
app:shimmer_auto_start="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="30dp">
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu">
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<RelativeLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="4"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerViewHome"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:spanCount="4"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
/>
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_below="@id/recyclerViewHome"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/shimmer_results_framelayout"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="200dp"
|
||||
android:baselineAligned="false">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
android:baselineAligned="false">
|
||||
|
||||
</LinearLayout>
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="200dp">
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="200dp">
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="200dp">
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:weightSum="4"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
<include
|
||||
android:layout_weight="1"
|
||||
layout="@layout/result_card_shimmer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/home_fabs"
|
||||
|
|
@ -268,6 +304,7 @@
|
|||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
|
|
|||
|
|
@ -166,6 +166,23 @@
|
|||
app:layout_constraintVertical_bias="0.51"
|
||||
>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_resume"
|
||||
style="?attr/materialIconButtonFilledStyle"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/resume"
|
||||
android:layout_height="wrap_content"
|
||||
app:backgroundTint="?attr/colorSurface"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/output"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
|
||||
app:layout_constraintVertical_bias="0.51" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_delete"
|
||||
style="?attr/materialIconButtonFilledStyle"
|
||||
|
|
|
|||
|
|
@ -181,8 +181,7 @@
|
|||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/from_textinput_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
|
@ -209,8 +208,7 @@
|
|||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/to_textinput_edittext"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,29 @@
|
|||
android:orientation="horizontal"
|
||||
android:paddingTop="20dp">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:id="@+id/shimmer_loading_title"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_loading_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/loading"
|
||||
android:textSize="25sp" />
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -53,6 +76,28 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:visibility="gone"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
android:layout_marginEnd="20dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/shimmer_loading_title"
|
||||
android:id="@+id/shimmer_loading_subtitle"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/please_wait"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_subtitle"
|
||||
|
|
@ -114,6 +159,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingTop="10dp"
|
||||
android:text="@string/file_size"
|
||||
android:textStyle="bold"
|
||||
|
|
@ -124,12 +170,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/loadingItemsProgress"
|
||||
android:layout_width="match_parent"
|
||||
android:indeterminate="true"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/downloadMultipleRecyclerview"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
|
|
@ -141,13 +141,45 @@
|
|||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
app:menu="@menu/main_menu"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/search_hint" />
|
||||
android:layout_marginVertical="15dp"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:translationZ="250dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="@+id/search_bar"
|
||||
app:layout_constraintStart_toStartOf="@+id/search_bar"
|
||||
app:layout_constraintTop_toTopOf="@+id/search_bar"
|
||||
app:shimmer_auto_start="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="30dp">
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/search_hint"
|
||||
app:menu="@menu/main_menu" >
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
@ -167,6 +199,7 @@
|
|||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
|
|
|||
|
|
@ -1,112 +1,118 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout android:layout_width="match_parent"
|
||||
android:layout_height="230dp"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="10dp"
|
||||
app:cardMaxElevation="12dp"
|
||||
app:cardBackgroundColor="@color/grey"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_margin="10dp"
|
||||
<RelativeLayout android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@color/light_grey"
|
||||
android:textStyle="bold"
|
||||
android:shadowRadius="2"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowColor="@color/black" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@color/light_grey"
|
||||
android:textStyle="bold"
|
||||
android:shadowRadius="2"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowColor="@color/black" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:background="@color/light_grey"
|
||||
android:gravity="bottom"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:shadowRadius="1.5"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowColor="@color/black" />
|
||||
|
||||
<LinearLayout
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="bottom|right"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp">
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="10dp"
|
||||
app:cardMaxElevation="12dp"
|
||||
app:cardBackgroundColor="@color/grey"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_margin="10dp"
|
||||
>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
|
||||
android:id="@+id/download_music"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:backgroundTint="@color/light_grey"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
/>
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@color/light_grey"
|
||||
android:textStyle="bold"
|
||||
android:shadowRadius="2"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowColor="@color/black" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
|
||||
android:id="@+id/download_video"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:backgroundTint="@color/light_grey"
|
||||
<TextView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
/>
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@color/light_grey"
|
||||
android:textStyle="bold"
|
||||
android:shadowRadius="2"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowColor="@color/black" />
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:background="@color/light_grey"
|
||||
android:gravity="bottom"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:paddingLeft="20dp"
|
||||
android:textSize="15sp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:shadowRadius="1.5"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowColor="@color/black" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</RelativeLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="bottom|right"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
|
||||
android:id="@+id/download_music"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:backgroundTint="@color/light_grey"
|
||||
android:layout_marginLeft="10dp"
|
||||
/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
|
||||
android:id="@+id/download_video"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:backgroundTint="@color/light_grey"
|
||||
android:layout_marginLeft="10dp"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -22,38 +22,43 @@
|
|||
app:menu="@menu/select_playlist_menu"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/from_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="50dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:hint="@string/start">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/from_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number" />
|
||||
android:layout_marginEnd="10dp"
|
||||
android:hint="@string/start">
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/to_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/colon">
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/to_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
android:hint="@string/end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/colon">
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||
|
||||
|
|
|
|||
|
|
@ -378,4 +378,24 @@
|
|||
<string name="hour">Ora</string>
|
||||
<string name="no_download_fragments">Mos shkarkoni si Fragmente</string>
|
||||
<string name="no_download_fragments_summary">Mos përdorni skedarë .part dhe shkruani direkt në skedarin dalës</string>
|
||||
<string name="subtitles">Titrat</string>
|
||||
<string name="prevent_duplicate_downloads">Parandalo Shkarkimet Duplikate</string>
|
||||
<string name="prevent_duplicate_url_type">URL dhe Tipi i Shkarkimit</string>
|
||||
<string name="prevent_duplicate_config">Konfigurimi i plotë</string>
|
||||
<string name="copy_url">Kopjo URL</string>
|
||||
<string name="move_top">Vendose në fillim</string>
|
||||
<string name="move_bottom">Vendose në fund</string>
|
||||
<string name="scheduled">Të Planifikuar</string>
|
||||
<string name="clear_scheduled">Fshi të planifikuarit</string>
|
||||
<string name="minute">Minuta</string>
|
||||
<string name="second">Sekonda</string>
|
||||
<string name="milliseconds">Milisekonda</string>
|
||||
<string name="format_importance_audio">Rradha e rëndësisë së Formateve (Audio)</string>
|
||||
<string name="format_importance_video">Rradha e rëndësisë së Formateve (Video)</string>
|
||||
<string name="format_importance_note">Rregullo rëndësinë e elementit të formatit. Kjo porosi do të përdoret vetëm kur aplikacioni merr formatet në kartën e shkarkimit dhe zgjedh automatikisht formatin!</string>
|
||||
<string name="no_audio">Video pa audio</string>
|
||||
<string name="download_archive_folder">Skedari i arkivës së shkarkimeve</string>
|
||||
<string name="location">Vendi</string>
|
||||
<string name="enable_alarm_permission">Duhet të aktivizoni lejen SCHEDULE_EXACT_ALARM në cilësimet e aplikacionit.</string>
|
||||
<string name="process_downloads_background">Shkarkimet janë ende duke u ngarkuar. Vazhdoni t\'i përpunoni ato në sfond dhe të filloni shkarkimin?</string>
|
||||
</resources>
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
<string-array name="video_containers">
|
||||
<item>@string/defaultValue</item>
|
||||
<item>mp4</item>
|
||||
<item>webm</item>
|
||||
<item>mkv</item>
|
||||
<item>avi</item>
|
||||
<item>flv</item>
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
<string-array name="video_containers_values">
|
||||
<item>Default</item>
|
||||
<item>mp4</item>
|
||||
<item>webm</item>
|
||||
<item>mkv</item>
|
||||
<item>avi</item>
|
||||
<item>flv</item>
|
||||
|
|
|
|||
|
|
@ -401,5 +401,5 @@
|
|||
<string name="download_archive_folder">Download Archive Folder</string>
|
||||
<string name="location">Location</string>
|
||||
<string name="enable_alarm_permission">You need to enable the SCHEDULE_EXACT_ALARM permission in the app settings.</string>
|
||||
<string name="process_downloads_background">Downloads are still loading. Continue procesing them in the background and start downloading?</string>
|
||||
<string name="process_downloads_background">"Downloads are still loading. Continue processing them in the background and start downloading? "</string>
|
||||
</resources>
|
||||
|
|
@ -59,5 +59,4 @@
|
|||
<style name="PreferenceTheme" parent="PreferenceThemeOverlay">
|
||||
<item name="singleLineTitle">false</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
app:title="@string/quick_download" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue=""
|
||||
android:defaultValue="url_type"
|
||||
android:entries="@array/prevent_duplicate_downloads"
|
||||
android:entryValues="@array/prevent_duplicate_downloads_values"
|
||||
android:icon="@drawable/baseline_archive_24"
|
||||
|
|
|
|||
|
|
@ -59,14 +59,14 @@
|
|||
android:icon="@drawable/ic_textformat"
|
||||
app:key="file_name_template"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue="%(uploader)s - %(title)s"
|
||||
app:defaultValue="%(uploader)30B - %(title)120B"
|
||||
app:title="@string/file_name_template" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_textformat"
|
||||
app:key="file_name_template_audio"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue="%(uploader)s - %(title)s"
|
||||
app:defaultValue="%(uploader)30B - %(title)120B"
|
||||
app:title="@string/file_name_template" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@
|
|||
app:title="@string/video_format" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="avc|h264"
|
||||
android:defaultValue=""
|
||||
android:entries="@array/video_codec"
|
||||
android:entryValues="@array/video_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
|
|
@ -190,7 +190,7 @@
|
|||
app:title="@string/preferred_video_codec" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="m4a|mp4a|aac"
|
||||
android:defaultValue=""
|
||||
android:entries="@array/audio_codec"
|
||||
android:entryValues="@array/audio_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
|
|
|
|||
Loading…
Reference in a new issue