1.7.5
This commit is contained in:
parent
d7819aca98
commit
78efd276d8
99 changed files with 2665 additions and 793 deletions
|
|
@ -10,7 +10,7 @@ plugins {
|
|||
def properties = new Properties()
|
||||
def versionMajor = 1
|
||||
def versionMinor = 7
|
||||
def versionPatch = 4
|
||||
def versionPatch = 5
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def versionExt = ""
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||
implementation 'androidx.compose.ui:ui-android:1.6.2'
|
||||
implementation 'androidx.compose.ui:ui-android:1.6.5'
|
||||
testImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
||||
|
|
@ -196,6 +196,6 @@ dependencies {
|
|||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.0'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.1'
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads ORDER BY status")
|
||||
fun getAllDownloads() : PagingSource<Int, DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
||||
@Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused')")
|
||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
|
|
@ -52,7 +52,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','PausedReQueued')")
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','ActivePaused')")
|
||||
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
|
|
@ -61,32 +61,32 @@ 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','PausedReQueued')")
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused', 'Scheduled')")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused','PausedReQueued')")
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused')")
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused','PausedReQueued')")
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused')")
|
||||
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY id")
|
||||
fun getQueuedDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
||||
@Query("SELECT format FROM downloads WHERE status in('Queued','QueuedPaused')")
|
||||
fun getSelectedFormatFromQueued() : List<Format>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE downloadStartTime <= :currentTime and status in ('Queued', 'PausedReQueued') ORDER BY downloadStartTime, id LIMIT 20")
|
||||
fun getQueuedDownloadsThatAreNotScheduledChunked(currentTime: Long) : Flow<List<DownloadItem>>
|
||||
@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','PausedReQueued') ORDER BY downloadStartTime, id")
|
||||
@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")
|
||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
@Query("SELECT id FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY id")
|
||||
fun getQueuedDownloadsListIDs() : List<Long>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
|
|
@ -98,7 +98,6 @@ interface DownloadDao {
|
|||
|
||||
@Query("SELECT * FROM downloads WHERE status LIKE '%Paused%'")
|
||||
fun getPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
|
@ -106,6 +105,10 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloadsList() : List<DownloadItem>
|
||||
|
||||
|
||||
@Query("SELECT id from downloads WHERE status='Scheduled' ORDER BY downloadStartTime, id DESC")
|
||||
fun getScheduledDownloadIDs(): List<Long>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
||||
fun getSavedDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
|
@ -113,6 +116,10 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
||||
fun getSavedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status='Scheduled' ORDER BY downloadStartTime, id DESC")
|
||||
fun getScheduledDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||
fun getDownloadById(id: Long) : DownloadItem
|
||||
|
||||
|
|
@ -165,10 +172,13 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE status='Processing'")
|
||||
suspend fun deleteProcessing()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Scheduled'")
|
||||
suspend fun deleteScheduled()
|
||||
|
||||
@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','PausedReQueued')")
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status in('Queued','QueuedPaused','Active','ActivePaused')")
|
||||
suspend fun cancelActiveQueued()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||
|
|
@ -244,6 +254,22 @@ interface DownloadDao {
|
|||
}
|
||||
}
|
||||
|
||||
@Transaction
|
||||
suspend fun putAtBottomOfTheQueue(existingIDs: List<Long>){
|
||||
val downloads = getQueuedDownloadsListIDs()
|
||||
val newIDs = downloads.sortedByDescending { it }.take(existingIDs.size)
|
||||
|
||||
resetScheduleTimeForItems(existingIDs)
|
||||
existingIDs.forEach { updateDownloadID(it, -it) }
|
||||
downloads.filter { !existingIDs.contains(it) }.reversed().forEach {
|
||||
updateDownloadID(it, it + existingIDs.size)
|
||||
}
|
||||
|
||||
existingIDs.forEachIndexed { idx, it ->
|
||||
updateDownloadID(-it, newIDs[idx])
|
||||
}
|
||||
}
|
||||
|
||||
@Query("Update downloads set id=:newId where id=:id")
|
||||
suspend fun updateDownloadID(id: Long, newId: Long)
|
||||
|
||||
|
|
@ -251,6 +277,6 @@ interface DownloadDao {
|
|||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in (:statuses) ORDER BY id DESC")
|
||||
fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List<String>) : List<Long>
|
||||
|
||||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
fun getQueuedIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long>
|
||||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in('Scheduled') ORDER BY downloadStartTime, id")
|
||||
fun getScheduledIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long>
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdl.database.models
|
|||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
|
||||
@Entity(tableName = "downloads")
|
||||
data class DownloadItemSimple(
|
||||
|
|
@ -16,5 +17,8 @@ data class DownloadItemSimple(
|
|||
var format: Format,
|
||||
@ColumnInfo(defaultValue = "Queued")
|
||||
var status: String,
|
||||
var logID: Long?
|
||||
var logID: Long?,
|
||||
var type: DownloadViewModel.Type,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var downloadStartTime: Long,
|
||||
)
|
||||
|
|
@ -53,15 +53,23 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getSavedDownloads()}
|
||||
)
|
||||
val scheduledDownloads: Pager<Int, DownloadItemSimple> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
pagingSourceFactory = {downloadDao.getScheduledDownloads()}
|
||||
)
|
||||
|
||||
val activeDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.ActivePaused, Status.PausedReQueued).toListString())
|
||||
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 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, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing
|
||||
Active, ActivePaused, Queued, QueuedPaused, Error, Cancelled, Saved, Processing, Scheduled
|
||||
}
|
||||
|
||||
suspend fun insert(item: DownloadItem) : Long {
|
||||
|
|
@ -139,12 +147,20 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getErroredDownloadsList()
|
||||
}
|
||||
|
||||
fun getScheduledDownloadIDs() : List<Long> {
|
||||
return downloadDao.getScheduledDownloadIDs()
|
||||
}
|
||||
|
||||
suspend fun deleteCancelled(){
|
||||
val cancelled = getCancelledDownloads()
|
||||
downloadDao.deleteCancelled()
|
||||
deleteCache(cancelled)
|
||||
}
|
||||
|
||||
suspend fun deleteScheduled() {
|
||||
downloadDao.deleteScheduled()
|
||||
}
|
||||
|
||||
suspend fun deleteErrored(){
|
||||
val errored = getErroredDownloads()
|
||||
downloadDao.deleteErrored()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.deniscerri.ytdl.database.repository.DownloadRepository
|
|||
import com.deniscerri.ytdl.database.repository.HistoryRepository
|
||||
import com.deniscerri.ytdl.database.repository.ResultRepository
|
||||
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
|
||||
|
|
@ -65,12 +66,17 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
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>
|
||||
|
||||
private var bestVideoFormat : Format
|
||||
private var bestAudioFormat : Format
|
||||
|
|
@ -122,16 +128,21 @@ 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
|
||||
activeDownloads = repository.activeDownloads
|
||||
processingDownloads = repository.processingDownloads
|
||||
savedDownloads = repository.savedDownloads.flow
|
||||
scheduledDownloads = repository.scheduledDownloads.flow
|
||||
cancelledDownloads = repository.cancelledDownloads.flow
|
||||
erroredDownloads = repository.erroredDownloads.flow
|
||||
viewModelScope.launch(Dispatchers.IO){
|
||||
|
|
@ -652,6 +663,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.deleteCancelled()
|
||||
}
|
||||
|
||||
fun deleteScheduled() = viewModelScope.launch(Dispatchers.IO) {
|
||||
val scheduledIds = repository.getScheduledDownloadIDs()
|
||||
scheduledIds.forEach {
|
||||
WorkManager.getInstance(application).cancelAllWorkByTag(it.toString())
|
||||
}
|
||||
repository.deleteScheduled()
|
||||
}
|
||||
|
||||
fun deleteErrored() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteErrored()
|
||||
}
|
||||
|
|
@ -679,11 +698,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
fun getCancelled() : List<DownloadItem> {
|
||||
return repository.getCancelledDownloads()
|
||||
}
|
||||
|
||||
fun getPausedDownloads() : List<DownloadItem> {
|
||||
return repository.getPausedDownloads()
|
||||
}
|
||||
|
||||
fun getErrored() : List<DownloadItem> {
|
||||
return repository.getErroredDownloads()
|
||||
}
|
||||
|
|
@ -714,9 +728,65 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun startDownloadWorker(list: List<DownloadItem>){
|
||||
repository.startDownloadWorker(list, application)
|
||||
}
|
||||
|
||||
suspend fun putAtTopOfQueue(ids: List<Long>) = CoroutineScope(Dispatchers.IO).launch{
|
||||
dbManager.downloadDao.putAtTopOfTheQueue(ids)
|
||||
repository.startDownloadWorker(emptyList(), application)
|
||||
val downloads = dao.getQueuedDownloadsListIDs()
|
||||
val lastID = ids.maxOf { it }
|
||||
ids.forEach { dao.updateDownloadID(it, -it) }
|
||||
val newIDs = downloads.take(ids.size)
|
||||
|
||||
//other ids that need to move around
|
||||
val takenPositions = mutableListOf<Long>()
|
||||
for (dID in downloads.filter { !ids.contains(it) && it < lastID }.reversed()){
|
||||
val newID = downloads.last { !newIDs.contains(it) && !takenPositions.contains(it) && it <= lastID }
|
||||
takenPositions.add(newID)
|
||||
dao.updateDownloadID(dID, newID)
|
||||
}
|
||||
|
||||
ids.forEachIndexed { idx, it ->
|
||||
dao.updateDownloadID(-it, newIDs[idx])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun putAtBottomOfQueue(ids: List<Long>) = CoroutineScope(Dispatchers.IO).launch{
|
||||
val downloads = dao.getQueuedDownloadsListIDs()
|
||||
ids.forEach { dao.updateDownloadID(it, -it)}
|
||||
val newIDs = downloads.sortedByDescending { it }.take(ids.size)
|
||||
|
||||
//other ids that need to move around
|
||||
val takenPositions = mutableListOf<Long>()
|
||||
for (dID in downloads.filter { !ids.contains(it) }){
|
||||
val newID = downloads.first { !newIDs.contains(it) && !takenPositions.contains(it) }
|
||||
takenPositions.add(newID)
|
||||
dao.updateDownloadID(dID, newID)
|
||||
}
|
||||
|
||||
ids.reversed().forEachIndexed { idx, it ->
|
||||
dao.updateDownloadID(-it, newIDs[idx])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun putAtPosition(current: Long, id: Long) = CoroutineScope(Dispatchers.IO).launch {
|
||||
val downloads = dao.getQueuedDownloadsListIDs()
|
||||
dao.updateDownloadID(current, -current)
|
||||
|
||||
if (current > id){
|
||||
for (dID in downloads.filter { it in id until current }.reversed()){
|
||||
val index = downloads.indexOf(dID)
|
||||
dao.updateDownloadID(dID, downloads[index + 1])
|
||||
}
|
||||
}else{
|
||||
for (dID in downloads.filter { it in (current + 1)..id }){
|
||||
val index = downloads.indexOf(dID)
|
||||
dao.updateDownloadID(dID, downloads[index - 1])
|
||||
}
|
||||
}
|
||||
dao.updateDownloadID(-current, id)
|
||||
}
|
||||
|
||||
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
|
|
@ -734,9 +804,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val existingDownloads = mutableListOf<Long>()
|
||||
val existingHistory = mutableListOf<Long>()
|
||||
|
||||
var ignoreDuplicate = ign
|
||||
if (sharedPreferences.getBoolean("download_archive", false)) ignoreDuplicate = true
|
||||
|
||||
//if scheduler is on
|
||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
||||
|
|
@ -747,41 +814,94 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
||||
}
|
||||
|
||||
val downloadArchive = runCatching { File(FileUtil.getDownloadArchivePath(application)).useLines { it.toList() } }.getOrElse { listOf() }
|
||||
.map { it.split(" ")[1] }
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||
if (! listOf(DownloadRepository.Status.ActivePaused, DownloadRepository.Status.Scheduled).toListString().contains(it.status))
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
var alreadyExists = false
|
||||
|
||||
if(!ignoreDuplicate){
|
||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
d.logID = null
|
||||
d.customFileNameTemplate = it.customFileNameTemplate
|
||||
d.status = DownloadRepository.Status.Queued.toString()
|
||||
d.toString() == it.toString()
|
||||
}
|
||||
|
||||
if (existingDownload != null){
|
||||
it.status = DownloadRepository.Status.Saved.toString()
|
||||
val id = runBlocking {
|
||||
repository.insert(it)
|
||||
}
|
||||
alreadyExists = true
|
||||
existingDownloads.add(id)
|
||||
}else{
|
||||
//check if downloaded and file exists
|
||||
val history = withContext(Dispatchers.IO){
|
||||
historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||
val checkDuplicate = sharedPreferences.getString("prevent_duplicate_downloads", "")!!
|
||||
if (checkDuplicate.isNotEmpty() && !ign){
|
||||
when(checkDuplicate){
|
||||
"download_archive" -> {
|
||||
if (downloadArchive.any { d -> it.url.contains(d) }){
|
||||
alreadyExists = true
|
||||
if (it.id == 0L) {
|
||||
it.status = DownloadRepository.Status.Processing.toString()
|
||||
val id = runBlocking {
|
||||
repository.insert(it)
|
||||
}
|
||||
it.id = id
|
||||
}
|
||||
existingDownloads.add(it.id)
|
||||
}
|
||||
}
|
||||
"url_type" -> {
|
||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
d.logID = null
|
||||
d.customFileNameTemplate = it.customFileNameTemplate
|
||||
d.status = DownloadRepository.Status.Queued.toString()
|
||||
d.toString() == it.toString()
|
||||
}
|
||||
|
||||
val existingHistoryItem = history.firstOrNull {
|
||||
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
||||
}
|
||||
if (existingDownload != null){
|
||||
it.status = DownloadRepository.Status.Processing.toString()
|
||||
val id = runBlocking {
|
||||
repository.insert(it)
|
||||
}
|
||||
alreadyExists = true
|
||||
existingDownloads.add(id)
|
||||
}else{
|
||||
//check if downloaded and file exists
|
||||
val history = withContext(Dispatchers.IO){
|
||||
historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||
}
|
||||
|
||||
if (existingHistoryItem != null){
|
||||
alreadyExists = true
|
||||
existingHistory.add(existingHistoryItem.id)
|
||||
val existingHistoryItem = history.firstOrNull {
|
||||
h -> h.type == it.type
|
||||
}
|
||||
|
||||
if (existingHistoryItem != null){
|
||||
alreadyExists = true
|
||||
existingHistory.add(existingHistoryItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
"config" -> {
|
||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
d.logID = null
|
||||
d.customFileNameTemplate = it.customFileNameTemplate
|
||||
d.status = DownloadRepository.Status.Queued.toString()
|
||||
d.toString() == it.toString()
|
||||
}
|
||||
|
||||
if (existingDownload != null){
|
||||
it.status = DownloadRepository.Status.Processing.toString()
|
||||
val id = runBlocking {
|
||||
repository.insert(it)
|
||||
}
|
||||
alreadyExists = true
|
||||
existingDownloads.add(id)
|
||||
}else{
|
||||
//check if downloaded and file exists
|
||||
val history = withContext(Dispatchers.IO){
|
||||
historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||
}
|
||||
|
||||
val existingHistoryItem = history.firstOrNull {
|
||||
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
||||
}
|
||||
|
||||
if (existingHistoryItem != null){
|
||||
alreadyExists = true
|
||||
existingHistory.add(existingHistoryItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -792,7 +912,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.insert(it)
|
||||
}
|
||||
it.id = id
|
||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||
}else if (listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.Scheduled).toListString().contains(it.status)){
|
||||
withContext(Dispatchers.IO){
|
||||
repository.update(it)
|
||||
}
|
||||
|
|
@ -839,7 +959,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
fun getQueuedCollectedFileSize() : Long {
|
||||
return dbManager.downloadDao.getSelectedFormatFromQueued().sumOf { it.filesize }
|
||||
return dbManager.downloadDao.getSelectedFormatFromQueued().filter { it.filesize > 10 }.sumOf { it.filesize }
|
||||
}
|
||||
|
||||
fun getTotalSize(status: List<DownloadRepository.Status>) : LiveData<Int> {
|
||||
|
|
@ -862,6 +982,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.getProcessingDownloads().apply {
|
||||
if (timeInMillis > 0){
|
||||
this.forEach {
|
||||
it.status = DownloadRepository.Status.Scheduled.toString()
|
||||
it.downloadStartTime = timeInMillis
|
||||
}
|
||||
}
|
||||
|
|
@ -999,8 +1120,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return dao.getIDsBetweenTwoItems(item1, item2, statuses)
|
||||
}
|
||||
|
||||
fun getQueuedIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long> {
|
||||
return dao.getQueuedIDsBetweenTwoItems(item1, item2)
|
||||
fun getScheduledIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long> {
|
||||
return dao.getScheduledIDsBetweenTwoItems(item1, item2)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import androidx.work.Constraints
|
|||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
|
||||
|
|
@ -88,10 +90,6 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
|
||||
Calendar.getInstance().apply {
|
||||
timeInMillis = it.startsTime
|
||||
val date = Calendar.getInstance()
|
||||
set(Calendar.DAY_OF_MONTH, date.get(Calendar.DAY_OF_MONTH))
|
||||
set(Calendar.MONTH, date.get(Calendar.MONTH))
|
||||
set(Calendar.YEAR, date.get(Calendar.YEAR))
|
||||
|
||||
if (it.everyCategory != ObserveSourcesRepository.EveryCategory.HOUR){
|
||||
val hourMin = Calendar.getInstance()
|
||||
|
|
@ -131,7 +129,7 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
.addTag("observeSources")
|
||||
.addTag(it.id.toString())
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(System.currentTimeMillis() - timeInMillis, TimeUnit.MILLISECONDS)
|
||||
.setInitialDelay(timeInMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
|
||||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
|
|
|
|||
|
|
@ -645,7 +645,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
|
||||
fun scrollToTop() {
|
||||
recyclerView!!.scrollToPosition(0)
|
||||
(searchBar!!.parent as AppBarLayout).setExpanded(true, true)
|
||||
runCatching { (searchBar!!.parent as AppBarLayout).setExpanded(true, true) }
|
||||
}
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
|
|
|
|||
|
|
@ -116,11 +116,6 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
onItemClickListener.onOutputClick(item)
|
||||
}
|
||||
|
||||
|
||||
// PAUSE BUTTON ----------------------------------
|
||||
val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
|
||||
if (pauseButton.hasOnClickListeners()) pauseButton.setOnClickListener(null)
|
||||
|
||||
// CANCEL BUTTON ----------------------------------
|
||||
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_delete)
|
||||
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
|
||||
|
|
@ -130,64 +125,18 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
when(DownloadRepository.Status.valueOf(item.status)){
|
||||
DownloadRepository.Status.Active -> {
|
||||
progressBar.isIndeterminate = true
|
||||
|
||||
val fromRadius: Int = dp(activity.resources, 30f)
|
||||
val toRadius: Int = dp(activity.resources, 15f)
|
||||
val animator = ValueAnimator.ofInt(fromRadius, toRadius)
|
||||
animator.setDuration(500)
|
||||
.addUpdateListener { animation ->
|
||||
val value = animation.animatedValue as Int
|
||||
pauseButton.cornerRadius = value
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.pause)
|
||||
pauseButton.isEnabled = true
|
||||
pauseButton.tag = ActiveDownloadAction.Pause
|
||||
}
|
||||
animator.start()
|
||||
|
||||
cancelButton.visibility = View.GONE
|
||||
cancelButton.isEnabled = true
|
||||
}
|
||||
DownloadRepository.Status.ActivePaused -> {
|
||||
progressBar.isIndeterminate = false
|
||||
|
||||
val fromRadius: Int = dp(activity.resources, 15f)
|
||||
val toRadius: Int = dp(activity.resources, 30f)
|
||||
val animator = ValueAnimator.ofInt(fromRadius, toRadius)
|
||||
animator.setDuration(500)
|
||||
.addUpdateListener { animation ->
|
||||
val value = animation.animatedValue as Int
|
||||
pauseButton.cornerRadius = value
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.resume)
|
||||
pauseButton.tag = ActiveDownloadAction.Resume
|
||||
pauseButton.isEnabled = true
|
||||
}
|
||||
animator.start()
|
||||
cancelButton.visibility = View.VISIBLE
|
||||
}
|
||||
DownloadRepository.Status.PausedReQueued -> {
|
||||
progressBar.isIndeterminate = true
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.ic_refresh)
|
||||
pauseButton.contentDescription = activity.getString(R.string.please_wait)
|
||||
pauseButton.tag = null
|
||||
pauseButton.isEnabled = false
|
||||
cancelButton.visibility = View.GONE
|
||||
cancelButton.isEnabled = true
|
||||
output.text = activity.getString(R.string.exo_download_paused)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
pauseButton.setOnClickListener {
|
||||
if (pauseButton.tag == ActiveDownloadAction.Pause){
|
||||
onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Pause, position)
|
||||
}else{
|
||||
onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Resume, position)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
interface OnItemClickListener {
|
||||
fun onCancelClick(itemID: Long)
|
||||
fun onPauseClick(itemID: Long, action: ActiveDownloadAction, position: Int)
|
||||
fun onOutputClick(item: DownloadItem)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,19 @@ package com.deniscerri.ytdl.ui.adapter
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.media3.exoplayer.offline.Download
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
|
@ -18,6 +23,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
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.util.Extensions.loadThumbnail
|
||||
import com.deniscerri.ytdl.util.Extensions.popup
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
|
|
@ -42,7 +48,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
val cardView: MaterialCardView
|
||||
|
||||
init {
|
||||
cardView = itemView.findViewById(R.id.active_download_card_view)
|
||||
cardView = itemView.findViewById(R.id.download_card_view)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +64,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
card.popup()
|
||||
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
// THUMBNAIL ----------------------------------
|
||||
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
|
||||
|
|
@ -78,10 +84,26 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
}
|
||||
itemTitle.text = title.ifEmpty { item.url }
|
||||
|
||||
//DOWNLOAD TYPE -----------------------------
|
||||
val type = card.findViewById<TextView>(R.id.download_type)
|
||||
when(item.type){
|
||||
DownloadViewModel.Type.audio -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_music_formatcard, 0, 0, 0
|
||||
)
|
||||
DownloadViewModel.Type.video -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_video_formatcard, 0, 0, 0
|
||||
)
|
||||
else -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_terminal_formatcard, 0, 0, 0
|
||||
)
|
||||
}
|
||||
|
||||
val formatDetailsChip = card.findViewById<TextView>(R.id.format_note)
|
||||
val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note.uppercase()
|
||||
|
||||
val codec = card.findViewById<TextView>(R.id.codec)
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
|
|
@ -90,56 +112,61 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText != "" && codecText != "none"){
|
||||
formatDetailsText.append(" \t •\t $codecText")
|
||||
}
|
||||
|
||||
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize")
|
||||
|
||||
formatDetailsChip.text = formatDetailsText
|
||||
|
||||
// PAUSE BUTTON ----------------------------------
|
||||
val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
|
||||
if (pauseButton.hasOnClickListeners()) pauseButton.setOnClickListener(null)
|
||||
|
||||
// CANCEL BUTTON ----------------------------------
|
||||
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_delete)
|
||||
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
|
||||
cancelButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)}
|
||||
|
||||
if (item.status == DownloadRepository.Status.ActivePaused.toString()){
|
||||
progressBar.isIndeterminate = false
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.start)
|
||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
|
||||
cancelButton.visibility = View.VISIBLE
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec.visibility = View.GONE
|
||||
}else{
|
||||
progressBar.isIndeterminate = true
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.pause)
|
||||
cancelButton.visibility = View.GONE
|
||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause
|
||||
codec.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
pauseButton.setOnClickListener {
|
||||
if (pauseButton.tag == ActiveDownloadAdapter.ActiveDownloadAction.Pause){
|
||||
onItemClickListener.onPauseClick(item.id,
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||
if (progressBar.progress == 0) progressBar.isIndeterminate = false
|
||||
cancelButton.visibility = View.VISIBLE
|
||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
|
||||
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
|
||||
else fileSize.text = fileSizeReadable
|
||||
|
||||
val menu = card.findViewById<View>(R.id.options)
|
||||
menu.setOnClickListener {
|
||||
val popup = PopupMenu(activity, it)
|
||||
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
|
||||
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
|
||||
|
||||
val pause = popup.menu[0]
|
||||
val resume = popup.menu[1]
|
||||
|
||||
if (item.status == DownloadRepository.Status.ActivePaused.toString()){
|
||||
pause.isVisible = false
|
||||
resume.isVisible = true
|
||||
}else{
|
||||
onItemClickListener.onPauseClick(item.id,
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||
progressBar.isIndeterminate = true
|
||||
cancelButton.visibility = View.GONE
|
||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause
|
||||
pause.isVisible = true
|
||||
resume.isVisible = false
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener { m ->
|
||||
when(m.itemId){
|
||||
R.id.pause -> {
|
||||
onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
|
||||
if (progressBar.progress == 0) progressBar.isIndeterminate = false
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.resume -> {
|
||||
onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
|
||||
progressBar.isIndeterminate = true
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.cancel -> {
|
||||
onItemClickListener.onCancelClick(item.id)
|
||||
popup.dismiss()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
popup.show()
|
||||
|
||||
}
|
||||
|
||||
progressBar.isIndeterminate = item.status != DownloadRepository.Status.ActivePaused.toString()
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onCardClick()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.view.ViewGroup
|
|||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
|
@ -76,6 +77,8 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
}
|
||||
itemTitle.text = title
|
||||
|
||||
card.findViewById<TextView>(R.id.download_type).isVisible = false
|
||||
|
||||
// Format Note ----------------------------------
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note.isNotEmpty()){
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItemSimple
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.loadThumbnail
|
||||
import com.deniscerri.ytdl.util.Extensions.popup
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
|
|
@ -79,6 +80,20 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
}
|
||||
itemTitle.text = title.ifEmpty { item.url }
|
||||
|
||||
//DOWNLOAD TYPE -----------------------------
|
||||
val type = card.findViewById<TextView>(R.id.download_type)
|
||||
when(item.type){
|
||||
DownloadViewModel.Type.audio -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_music_formatcard, 0,0,0
|
||||
)
|
||||
DownloadViewModel.Type.video -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_video_formatcard, 0,0,0
|
||||
)
|
||||
else -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_terminal_formatcard, 0,0,0
|
||||
)
|
||||
}
|
||||
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
btn.backgroundTintList = MaterialColors.getColorStateList(activity, R.attr.colorSurface, ContextCompat.getColorStateList(activity, android.R.color.transparent)!!)
|
||||
}else{
|
||||
thumbnail.alpha = 1f
|
||||
thumbnail.colorFilter = null
|
||||
btn.backgroundTintList = MaterialColors.getColorStateList(activity, R.attr.colorPrimaryContainer, ContextCompat.getColorStateList(activity, android.R.color.transparent)!!)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
|
||||
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdl.util.Extensions.calculateNextTime
|
||||
import com.deniscerri.ytdl.util.Extensions.calculateNextTimeForObserving
|
||||
import com.deniscerri.ytdl.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
|
|
@ -76,7 +76,7 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
|
||||
//INFO
|
||||
val info = card.findViewById<Chip>(R.id.info)
|
||||
val nextTime = item.calculateNextTime()
|
||||
val nextTime = item.calculateNextTimeForObserving()
|
||||
val c = Calendar.getInstance()
|
||||
c.timeInMillis = nextTime
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
&& oldItem.status == newItem.status
|
||||
&& oldItem.retryMissingDownloads == newItem.retryMissingDownloads
|
||||
&& oldItem.runCount == newItem.runCount
|
||||
&& oldItem.everyCategory == newItem.everyCategory
|
||||
&& oldItem.calculateNextTimeForObserving() == newItem.calculateNextTimeForObserving()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
package com.deniscerri.ytdl.ui.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItemSimple
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.loadThumbnail
|
||||
import com.deniscerri.ytdl.util.Extensions.popup
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity, private var itemTouchHelper: ItemTouchHelper) : PagingDataAdapter<DownloadItemSimple, QueuedDownloadAdapter.ViewHolder>(
|
||||
DIFF_CALLBACK
|
||||
) {
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
val checkedItems: MutableSet<Long> = mutableSetOf()
|
||||
var inverted: Boolean
|
||||
var showDragHandle: Boolean
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
this.inverted = false
|
||||
this.showDragHandle = false
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val cardView: MaterialCardView
|
||||
init {
|
||||
cardView = itemView.findViewById(R.id.download_card_view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val cardView = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.queued_download_card, parent, false)
|
||||
return ViewHolder(cardView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
|
||||
if (item == null) return
|
||||
card.tag = item.id.toString()
|
||||
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
//DRAG HANDLE
|
||||
val dragView = card.findViewById<View>(R.id.drag_view)
|
||||
dragView.isVisible = showDragHandle
|
||||
card.findViewById<View>(R.id.drag_view).setOnTouchListener { view, motionEvent ->
|
||||
view.performClick()
|
||||
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN){
|
||||
itemTouchHelper.startDrag(holder)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// THUMBNAIL ----------------------------------
|
||||
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
|
||||
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) }
|
||||
|
||||
val duration = card.findViewById<TextView>(R.id.duration)
|
||||
duration.text = item.duration
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
var title = item.title
|
||||
if (title.length > 100) {
|
||||
title = title.substring(0, 40) + "..."
|
||||
}
|
||||
itemTitle.text = title.ifEmpty { item.url }
|
||||
|
||||
//DOWNLOAD TYPE -----------------------------
|
||||
val type = card.findViewById<TextView>(R.id.download_type)
|
||||
when(item.type){
|
||||
DownloadViewModel.Type.audio -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_music_formatcard, 0,0,0
|
||||
)
|
||||
DownloadViewModel.Type.video -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_video_formatcard, 0,0,0
|
||||
)
|
||||
else -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_terminal_formatcard, 0,0,0
|
||||
)
|
||||
}
|
||||
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note.uppercase()
|
||||
|
||||
val codec = card.findViewById<TextView>(R.id.codec)
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec.visibility = View.GONE
|
||||
}else{
|
||||
codec.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
|
||||
else fileSize.text = fileSizeReadable
|
||||
|
||||
val menu = card.findViewById<View>(R.id.options)
|
||||
menu.setOnClickListener {
|
||||
val popup = PopupMenu(activity, it)
|
||||
popup.menuInflater.inflate(R.menu.queued_download_menu, popup.menu)
|
||||
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
|
||||
|
||||
popup.setOnMenuItemClickListener { m ->
|
||||
when(m.itemId){
|
||||
R.id.cancel -> {
|
||||
onItemClickListener.onQueuedCancelClick(item.id)
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.move_top -> {
|
||||
onItemClickListener.onMoveQueuedItemToTop(item.id)
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.move_bottom -> {
|
||||
onItemClickListener.onMoveQueuedItemToBottom(item.id)
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.copy_url -> {
|
||||
UiUtil.copyLinkToClipBoard(activity, item.url)
|
||||
popup.dismiss()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
popup.show()
|
||||
|
||||
}
|
||||
|
||||
if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) {
|
||||
card.isChecked = true
|
||||
card.strokeWidth = 5
|
||||
} else {
|
||||
card.isChecked = false
|
||||
card.strokeWidth = 0
|
||||
}
|
||||
card.findViewById<View>(R.id.card_content).setOnClickListener {
|
||||
if (checkedItems.size > 0 || inverted) {
|
||||
checkCard(card, item.id, position)
|
||||
} else {
|
||||
onItemClickListener.onQueuedCardClick(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
card.findViewById<View>(R.id.card_content).setOnLongClickListener {
|
||||
checkCard(card, item.id, position)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clearCheckedItems() {
|
||||
inverted = false
|
||||
checkedItems.clear()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkAll() {
|
||||
checkedItems.clear()
|
||||
inverted = true
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkMultipleItems(list: List<Long>){
|
||||
checkedItems.clear()
|
||||
inverted = false
|
||||
checkedItems.addAll(list)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun invertSelected() {
|
||||
inverted = !inverted
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getSelectedObjectsCount(totalSize: Int) : Int{
|
||||
return if (inverted){
|
||||
totalSize - checkedItems.size
|
||||
}else{
|
||||
checkedItems.size
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun toggleShowDragHandle(){
|
||||
showDragHandle = !showDragHandle
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun checkCard(card: MaterialCardView, itemID: Long, position: Int) {
|
||||
if (card.isChecked) {
|
||||
card.strokeWidth = 0
|
||||
if (inverted) checkedItems.add(itemID)
|
||||
else checkedItems.remove(itemID)
|
||||
} else {
|
||||
card.strokeWidth = 5
|
||||
if (inverted) checkedItems.remove(itemID)
|
||||
else checkedItems.add(itemID)
|
||||
}
|
||||
|
||||
card.isChecked = !card.isChecked
|
||||
onItemClickListener.onQueuedCardSelect(card.isChecked, position)
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onQueuedCancelClick(itemID: Long)
|
||||
fun onMoveQueuedItemToTop(itemID: Long)
|
||||
fun onMoveQueuedItemToBottom(itemID: Long)
|
||||
fun onQueuedCardClick(itemID: Long)
|
||||
fun onQueuedCardSelect(isChecked: Boolean, position: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemSimple> = object : DiffUtil.ItemCallback<DownloadItemSimple>() {
|
||||
override fun areItemsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
|
||||
val ranged = arrayListOf(oldItem.id, newItem.id)
|
||||
return ranged[0] == ranged[1]
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
|
||||
return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
package com.deniscerri.ytdl.ui.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItemSimple
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.loadThumbnail
|
||||
import com.deniscerri.ytdl.util.Extensions.popup
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class ScheduledDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<DownloadItemSimple, ScheduledDownloadAdapter.ViewHolder>(
|
||||
DIFF_CALLBACK
|
||||
) {
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
val checkedItems: MutableSet<Long> = mutableSetOf()
|
||||
var inverted: Boolean
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
this.inverted = false
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||
val mainView: LinearLayout
|
||||
init {
|
||||
mainView = itemView.findViewById(R.id.scheduled_download_card_view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val cardView = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.scheduled_download_card, parent, false)
|
||||
return ViewHolder(cardView, onItemClickListener)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val mainView = holder.mainView
|
||||
val card = mainView.findViewById<MaterialCardView>(R.id.download_card_view)
|
||||
card.popup()
|
||||
|
||||
if (item == null) return
|
||||
card.tag = item.id.toString()
|
||||
|
||||
//Scheduled Time
|
||||
val time = mainView.findViewById<TextView>(R.id.scheduled_time)
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = item.downloadStartTime
|
||||
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
// THUMBNAIL ----------------------------------
|
||||
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
|
||||
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) }
|
||||
|
||||
val duration = card.findViewById<TextView>(R.id.duration)
|
||||
duration.text = item.duration
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
var title = item.title
|
||||
if (title.length > 100) {
|
||||
title = title.substring(0, 40) + "..."
|
||||
}
|
||||
itemTitle.text = title.ifEmpty { item.url }
|
||||
|
||||
//DOWNLOAD TYPE -----------------------------
|
||||
val type = card.findViewById<TextView>(R.id.download_type)
|
||||
when(item.type){
|
||||
DownloadViewModel.Type.audio -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_music_formatcard, 0,0,0
|
||||
)
|
||||
DownloadViewModel.Type.video -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_video_formatcard, 0,0,0
|
||||
)
|
||||
else -> type.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
R.drawable.ic_terminal_formatcard, 0,0,0
|
||||
)
|
||||
}
|
||||
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note.uppercase()
|
||||
|
||||
val codec = card.findViewById<TextView>(R.id.codec)
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec.visibility = View.GONE
|
||||
}else{
|
||||
codec.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
|
||||
else fileSize.text = fileSizeReadable
|
||||
|
||||
// ACTION BUTTON ----------------------------------
|
||||
val actionButton = card.findViewById<MaterialButton>(R.id.action_button)
|
||||
if (actionButton.hasOnClickListeners()) actionButton.setOnClickListener(null)
|
||||
|
||||
actionButton.setIconResource(R.drawable.ic_downloads)
|
||||
actionButton.contentDescription = activity.getString(R.string.download)
|
||||
|
||||
actionButton.setOnClickListener {
|
||||
onItemClickListener.onActionButtonClick(item.id)
|
||||
}
|
||||
if ((checkedItems.contains(item.id) && !inverted) || (!checkedItems.contains(item.id) && inverted)) {
|
||||
card.isChecked = true
|
||||
card.strokeWidth = 5
|
||||
} else {
|
||||
card.isChecked = false
|
||||
card.strokeWidth = 0
|
||||
}
|
||||
card.setOnClickListener {
|
||||
if (checkedItems.size > 0 || inverted) {
|
||||
checkCard(card, item.id, position)
|
||||
} else {
|
||||
onItemClickListener.onCardClick(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
card.setOnLongClickListener {
|
||||
checkCard(card, item.id, position)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun clearCheckedItems() {
|
||||
inverted = false
|
||||
checkedItems.clear()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkAll() {
|
||||
checkedItems.clear()
|
||||
inverted = true
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkMultipleItems(list: List<Long>){
|
||||
checkedItems.clear()
|
||||
inverted = false
|
||||
checkedItems.addAll(list)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun invertSelected() {
|
||||
inverted = !inverted
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun getSelectedObjectsCount(totalSize: Int) : Int{
|
||||
return if (inverted){
|
||||
totalSize - checkedItems.size
|
||||
}else{
|
||||
checkedItems.size
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun checkCard(card: MaterialCardView, itemID: Long, position: Int) {
|
||||
if (card.isChecked) {
|
||||
card.strokeWidth = 0
|
||||
if (inverted) checkedItems.add(itemID)
|
||||
else checkedItems.remove(itemID)
|
||||
} else {
|
||||
card.strokeWidth = 5
|
||||
if (inverted) checkedItems.remove(itemID)
|
||||
else checkedItems.add(itemID)
|
||||
}
|
||||
|
||||
card.isChecked = !card.isChecked
|
||||
onItemClickListener.onCardSelect(card.isChecked, position)
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onActionButtonClick(itemID: Long)
|
||||
fun onCardClick(itemID: Long)
|
||||
fun onCardSelect(isChecked: Boolean, position: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemSimple> = object : DiffUtil.ItemCallback<DownloadItemSimple>() {
|
||||
override fun areItemsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
|
||||
val ranged = arrayListOf(oldItem.id, newItem.id)
|
||||
return ranged[0] == ranged[1]
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
|
||||
return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import android.view.inputmethod.InputMethodManager
|
|||
import android.widget.*
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
|
|
@ -24,12 +25,13 @@ import com.deniscerri.ytdl.util.InfoUtil
|
|||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class AddExtraCommandsDialog(private val item: DownloadItem?, private val callback: ExtraCommandsListener) : BottomSheetDialogFragment() {
|
||||
class AddExtraCommandsDialog(private val item: DownloadItem? = null, private val callback: ExtraCommandsListener? = null) : BottomSheetDialogFragment() {
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
|
|
@ -64,14 +66,17 @@ class AddExtraCommandsDialog(private val item: DownloadItem?, private val callba
|
|||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (callback == null){
|
||||
this.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
val behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
|||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -66,6 +67,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.configure_download_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ 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.elevation.SurfaceColors
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
|
@ -57,7 +58,7 @@ import java.util.*
|
|||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls : String?, private var chapters: List<ChapterItem>?, private val listener: VideoCutListener) : BottomSheetDialogFragment() {
|
||||
class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private val urls : String? = null, private var chapters: List<ChapterItem>? = null, private val listener: VideoCutListener? = null) : BottomSheetDialogFragment() {
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var player: ExoPlayer
|
||||
|
|
@ -82,6 +83,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
private lateinit var resetBtn : Button
|
||||
private lateinit var chipGroup : ChipGroup
|
||||
private lateinit var suggestedLabel : View
|
||||
private lateinit var item: DownloadItem
|
||||
|
||||
private var timeSeconds by Delegates.notNull<Int>()
|
||||
private lateinit var selectedCuts: MutableList<String>
|
||||
|
|
@ -98,6 +100,14 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.cut_video_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
|
||||
if (_item == null){
|
||||
this.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
item = _item
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
@ -251,9 +261,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
rewindBtn.setOnClickListener {
|
||||
try {
|
||||
val seconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
||||
val endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100
|
||||
val startValue = (seconds.toFloat() / endTimestamp) * 100
|
||||
player.seekTo((((startValue * timeSeconds) / 100) * 1000).toLong())
|
||||
player.seekTo((seconds * 1000).toLong())
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
|
|
@ -269,12 +277,15 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
private fun initCutSection(){
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth("0:00")
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(item.duration)
|
||||
|
||||
rangeSlider.setOnTouchListener { v, event -> // Handle touch events here
|
||||
rangeSlider.valueFrom = 0f
|
||||
rangeSlider.valueTo = timeSeconds.toFloat()
|
||||
rangeSlider.setValues(0F, timeSeconds.toFloat())
|
||||
rangeSlider.setOnTouchListener { _, event -> // Handle touch events here
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
updateFromSlider()
|
||||
|
|
@ -287,11 +298,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
// Return 'false' to allow the event to continue propagating or 'true' to consume it
|
||||
false
|
||||
}
|
||||
rangeSlider.performClick()
|
||||
rangeSlider.addOnChangeListener { rangeSlider, fl, b ->
|
||||
|
||||
rangeSlider.setOnDragListener { view, dragEvent ->
|
||||
updateFromSlider()
|
||||
false
|
||||
}
|
||||
|
||||
fromTextInput.editText!!.setOnKeyListener(object : View.OnKeyListener {
|
||||
|
|
@ -300,29 +308,25 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
if ((event!!.action == KeyEvent.ACTION_DOWN) &&
|
||||
(keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
|
||||
var startTimestamp = (rangeSlider.valueFrom.toInt() * timeSeconds) / 100
|
||||
val endTimestamp = (rangeSlider.valueTo.toInt() * timeSeconds) / 100
|
||||
var startTimestamp = rangeSlider.valueFrom.toInt()
|
||||
val endTimestamp = rangeSlider.valueTo.toInt()
|
||||
|
||||
fromTextInput.editText!!.clearFocus()
|
||||
var seconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
||||
val endSeconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
|
||||
|
||||
var startValue = (seconds.toFloat() / endTimestamp) * 100
|
||||
val endValue = (endSeconds.toFloat() / endTimestamp) * 100
|
||||
|
||||
if (seconds == 0) {
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
||||
}else if (startValue > 100){
|
||||
}else if (seconds > endSeconds){
|
||||
startTimestamp = 0
|
||||
seconds = 0
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
||||
startValue = 0F
|
||||
}
|
||||
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(fromTextInput.editText!!.text.toString())
|
||||
|
||||
rangeSlider.setValues(startValue, endValue)
|
||||
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||
rangeSlider.setValues(seconds.toFloat(), endSeconds.toFloat())
|
||||
okBtn.isEnabled = seconds != 0 || endSeconds != timeSeconds
|
||||
try {
|
||||
player.seekTo(seconds.toLong() * 1000)
|
||||
player.play()
|
||||
|
|
@ -345,28 +349,26 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
toTextInput.editText!!.clearFocus()
|
||||
val startSeconds = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
||||
val seconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
|
||||
var endSeconds = convertStringToTimestamp(toTextInput.editText!!.text.toString())
|
||||
|
||||
val startValue = (startSeconds.toFloat() / endTimestamp) * 100
|
||||
var endValue = (seconds.toFloat() / endTimestamp) * 100
|
||||
if (endValue > 100F){
|
||||
if (endSeconds > timeSeconds){
|
||||
endTimestamp = timeSeconds
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||
endValue = 100F
|
||||
endSeconds = timeSeconds
|
||||
}
|
||||
|
||||
if (seconds == 0) {
|
||||
if (endSeconds == 0) {
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||
}else if (endValue <= rangeSlider.valueFrom.toInt()){
|
||||
}else if (endSeconds <= rangeSlider.valueFrom.toInt()){
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||
}
|
||||
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(toTextInput.editText!!.text.toString())
|
||||
|
||||
rangeSlider.setValues(startValue, endValue)
|
||||
okBtn.isEnabled = startValue != 0F || endValue != 100F
|
||||
rangeSlider.setValues(startSeconds.toFloat(), endSeconds.toFloat())
|
||||
okBtn.isEnabled = startSeconds != 0 || endSeconds != timeSeconds
|
||||
try {
|
||||
player.seekTo((seconds.toLong() - 4L) * 1000)
|
||||
player.seekTo((endSeconds.toLong() - 2L) * 1000)
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
|
||||
|
|
@ -400,20 +402,32 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
private fun updateFromSlider(){
|
||||
val values = rangeSlider.values
|
||||
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
|
||||
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
|
||||
val startTimestamp = values[0].toInt()
|
||||
val endTimestamp = values[1].toInt()
|
||||
|
||||
val startTimestampString = startTimestamp.toStringDuration(Locale.US)
|
||||
val endTimestampString = endTimestamp.toStringDuration(Locale.US)
|
||||
|
||||
var draggedFromBeginning = true
|
||||
if (toTextInput.editText!!.text.toString() != endTimestampString){
|
||||
draggedFromBeginning = false
|
||||
}
|
||||
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestampString)
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
|
||||
|
||||
|
||||
okBtn.isEnabled = values[0] != 0F || values[1] != 100F
|
||||
okBtn.isEnabled = values[0] != 0F || values[1] != timeSeconds.toFloat()
|
||||
|
||||
val startpos = (startTimestamp * 1000).toLong()
|
||||
try {
|
||||
player.seekTo((startTimestamp * 1000).toLong())
|
||||
if (draggedFromBeginning){
|
||||
player.seekTo(startpos)
|
||||
}else{
|
||||
var endpos = (endTimestamp * 1000).toLong() - 1500
|
||||
if (endpos < (startTimestamp * 1000).toLong()) endpos = startpos
|
||||
player.seekTo(endpos)
|
||||
}
|
||||
player.play()
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
|
|
@ -452,7 +466,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
newCutBtn.setOnClickListener {
|
||||
cutSection.visibility = View.VISIBLE
|
||||
cutListSection.visibility = View.GONE
|
||||
rangeSlider.setValues(0F, 100F)
|
||||
rangeSlider.setValues(0F, timeSeconds.toFloat())
|
||||
player.seekTo(0)
|
||||
suggestedChips.children.apply {
|
||||
this.forEach {
|
||||
|
|
@ -464,7 +478,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
resetBtn.setOnClickListener {
|
||||
chipGroup.removeAllViews()
|
||||
listener.onChangeCut(emptyList())
|
||||
listener?.onChangeCut(emptyList())
|
||||
player.stop()
|
||||
dismiss()
|
||||
}
|
||||
|
|
@ -484,20 +498,17 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
val startTimestamp = convertStringToTimestamp(timestamp.split("-")[0].replace(";", ""))
|
||||
val endTimestamp = convertStringToTimestamp(timestamp.split("-")[1].replace(";", ""))
|
||||
|
||||
val startingValue = ((startTimestamp.toFloat() / timeSeconds) * 100).toInt()
|
||||
val endingValue = ((endTimestamp.toFloat() / timeSeconds) * 100).toInt()
|
||||
|
||||
val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
||||
chip.text = timestamp
|
||||
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
|
||||
chip.isCheckedIconVisible = false
|
||||
chipGroup.addView(chip)
|
||||
selectedCuts.add(chip.text.toString())
|
||||
listener.onChangeCut(selectedCuts)
|
||||
listener?.onChangeCut(selectedCuts)
|
||||
|
||||
chip.setOnClickListener {
|
||||
if (chip.isChecked) {
|
||||
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
|
||||
rangeSlider.setValues(startTimestamp.toFloat(), endTimestamp.toFloat())
|
||||
player.prepare()
|
||||
player.seekTo((startTimestamp * 1000).toLong())
|
||||
player.play()
|
||||
|
|
@ -513,7 +524,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
player.pause()
|
||||
chipGroup.removeView(chip)
|
||||
selectedCuts.remove(chip.text.toString())
|
||||
listener.onChangeCut(selectedCuts)
|
||||
listener?.onChangeCut(selectedCuts)
|
||||
if (selectedCuts.isEmpty()){
|
||||
player.stop()
|
||||
dismiss()
|
||||
|
|
@ -539,21 +550,18 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
if (! selectedCuts.contains(timestamp))
|
||||
selectedCuts.add(timestamp)
|
||||
|
||||
listener.onChangeCut(selectedCuts)
|
||||
listener?.onChangeCut(selectedCuts)
|
||||
if (chapter.start_time == 0L && chapter.end_time == 0L) {
|
||||
chip.isEnabled = false
|
||||
}else{
|
||||
val startTimestamp = chapter.start_time.toInt()
|
||||
val endTimestamp = chapter.end_time.toInt()
|
||||
|
||||
val startingValue = ((startTimestamp.toFloat() / timeSeconds) * 100).toInt()
|
||||
val endingValue = ((endTimestamp.toFloat() / timeSeconds) * 100).toInt()
|
||||
|
||||
chip.setOnClickListener {
|
||||
if (chip.isChecked) {
|
||||
rangeSlider.setValues(startingValue.toFloat(), endingValue.toFloat())
|
||||
rangeSlider.setValues(startTimestamp.toFloat(), endTimestamp.toFloat())
|
||||
player.prepare()
|
||||
player.seekTo((((startingValue * timeSeconds) / 100) * 1000).toLong())
|
||||
player.seekTo(((startTimestamp) * 1000).toLong())
|
||||
player.play()
|
||||
}else {
|
||||
player.seekTo(0)
|
||||
|
|
@ -570,7 +578,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
player.pause()
|
||||
chipGroup.removeView(chip)
|
||||
selectedCuts.remove(chip.text.toString())
|
||||
listener.onChangeCut(selectedCuts)
|
||||
listener?.onChangeCut(selectedCuts)
|
||||
if (selectedCuts.isEmpty()){
|
||||
player.stop()
|
||||
dismiss()
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
|
|
@ -127,6 +128,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
super.setupDialog(dialog, style)
|
||||
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
@ -313,6 +315,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
val item: DownloadItem = getDownloadItem()
|
||||
item.status = DownloadRepository.Status.Scheduled.toString()
|
||||
item.downloadStartTime = it.timeInMillis
|
||||
if (item.videoPreferences.alsoDownloadAsAudio){
|
||||
val itemsToQueue = mutableListOf<DownloadItem>()
|
||||
|
|
|
|||
|
|
@ -80,8 +80,6 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
|
|||
if (type != DownloadViewModel.Type.command){
|
||||
type = DownloadViewModel.Type.command
|
||||
}
|
||||
|
||||
format = downloadViewModel.getFormat(allFormats, DownloadViewModel.Type.command)
|
||||
}
|
||||
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -92,6 +93,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, private var formats: List<List<Format>>, private val listener: OnFormatClickListener) : BottomSheetDialogFragment() {
|
||||
class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>? = null, private var _formats: List<List<Format>>? = null, private val _listener: OnFormatClickListener? = null) : BottomSheetDialogFragment() {
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
|
|
@ -62,6 +62,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
|
||||
private var hasGenericFormats: Boolean = false
|
||||
|
||||
private lateinit var items: List<DownloadItem?>
|
||||
private lateinit var formats: List<List<Format>>
|
||||
private lateinit var listener: OnFormatClickListener
|
||||
|
||||
enum class FormatSorting {
|
||||
filesize, container, codec, id
|
||||
}
|
||||
|
|
@ -87,6 +91,15 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
view = LayoutInflater.from(context).inflate(R.layout.format_select_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
if (_items == null){
|
||||
this.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
items = _items
|
||||
formats = _formats!!
|
||||
listener = _listener!!
|
||||
|
||||
sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
|
||||
filterBy = FormatCategory.valueOf(sharedPreferences.getString("format_filter", "ALL")!!)
|
||||
filterBtn = view.findViewById(R.id.format_filter)
|
||||
|
|
@ -368,9 +381,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
FormatSorting.codec -> {
|
||||
val codecOrder = resources.getStringArray(R.array.video_codec_values).toMutableList()
|
||||
codecOrder.removeFirst()
|
||||
chosenFormats.groupBy { format -> codecOrder.indexOfFirst { format.vcodec.startsWith(it) } }
|
||||
chosenFormats.groupBy { format -> codecOrder.indexOfFirst { format.vcodec.matches("^(${it})(.+)?$".toRegex()) } }
|
||||
|
||||
.flatMap {
|
||||
it.value.sortedBy { l -> l.filesize }
|
||||
it.value.sortedByDescending { l -> l.filesize }
|
||||
}
|
||||
}
|
||||
FormatSorting.filesize -> chosenFormats
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
|
|
@ -128,6 +129,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
super.setupDialog(dialog, style)
|
||||
view = LayoutInflater.from(context).inflate(R.layout.observe_sources_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import com.google.android.material.button.MaterialButton
|
|||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
@ -123,6 +124,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
|
||||
dialog.setOnShowListener {
|
||||
val behavior = BottomSheetBehavior.from(dialogView.parent as View)
|
||||
|
|
@ -613,7 +615,7 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.PausedReQueued.toString()
|
||||
item.status = DownloadRepository.Status.Queued.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.google.android.material.bottomappbar.BottomAppBar
|
|||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -77,6 +78,7 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
|||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.select_playlist_items, null)
|
||||
dialog.setContentView(view)
|
||||
dialog.window?.navigationBarColor = SurfaceColors.SURFACE_1.getColor(requireActivity())
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
|
|||
|
|
@ -1,32 +1,55 @@
|
|||
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.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
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
|
||||
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.google.android.material.button.MaterialButton
|
||||
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.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
|
||||
|
|
@ -35,15 +58,17 @@ import kotlinx.coroutines.runBlocking
|
|||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, OnClickListener {
|
||||
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
||||
private lateinit var activeRecyclerView : RecyclerView
|
||||
private lateinit var activeDownloads : ActiveDownloadAdapter
|
||||
lateinit var downloadItem: DownloadItem
|
||||
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var pauseResume: MaterialButton
|
||||
private lateinit var pause: ExtendedFloatingActionButton
|
||||
private lateinit var resume: ExtendedFloatingActionButton
|
||||
private lateinit var noResults: RelativeLayout
|
||||
private lateinit var workManager: WorkManager
|
||||
|
||||
|
|
@ -57,6 +82,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
return fragmentView
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
@ -65,75 +91,73 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
workManager = WorkManager.getInstance(requireContext())
|
||||
|
||||
activeDownloads =
|
||||
ActiveDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
)
|
||||
|
||||
activeDownloads = ActiveDownloadAdapter(this,requireActivity())
|
||||
activeRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
activeRecyclerView.forceFastScrollMode()
|
||||
activeRecyclerView.adapter = activeDownloads
|
||||
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
pauseResume = view.findViewById(R.id.pause_resume)
|
||||
|
||||
pause = view.findViewById(R.id.pause)
|
||||
pause.isVisible = false
|
||||
resume = view.findViewById(R.id.resume)
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
|
||||
pauseResume.setOnClickListener {
|
||||
if (pauseResume.text == requireContext().getString(R.string.pause)){
|
||||
lifecycleScope.launch {
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
pauseResume.isEnabled = false
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
pauseResume.isEnabled = true
|
||||
// pause queued
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}.forEach {
|
||||
it.status = DownloadRepository.Status.QueuedPaused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}else{
|
||||
lifecycleScope.launch {
|
||||
pauseResume.isEnabled = false
|
||||
|
||||
val active = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}
|
||||
// pause active
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}.forEach {
|
||||
cancelItem(it.id.toInt())
|
||||
it.status = DownloadRepository.Status.ActivePaused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
val toQueue = active.filter { it.status == DownloadRepository.Status.ActivePaused.toString() }.toMutableList()
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
pause.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
downloadViewModel.queueDownloads(listOf())
|
||||
}
|
||||
|
||||
val queuedItems = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
queuedItems.map {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
pauseResume.isEnabled = true
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.startDownloadWorker(listOf())
|
||||
}
|
||||
|
||||
val queuedItems = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
queuedItems.map {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
resume.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,10 +184,23 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeAndActivePausedDownloadsCount.collectLatest {
|
||||
noResults.isVisible = it == 0
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||
delay(200)
|
||||
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
|
||||
pause.isVisible = it > 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.pausedDownloadsCount.collectLatest {
|
||||
resume.isVisible = it > 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,19 +208,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
downloadViewModel.activeDownloads.collectLatest {
|
||||
delay(100)
|
||||
activeDownloads.submitList(it)
|
||||
|
||||
if (it.size > 1){
|
||||
pauseResume.visibility = View.VISIBLE
|
||||
if (it.all { l -> l.status == DownloadRepository.Status.ActivePaused.toString() }){
|
||||
pauseResume.text = requireContext().getString(R.string.resume)
|
||||
}else{
|
||||
pauseResume.text = requireContext().getString(R.string.pause)
|
||||
}
|
||||
}else{
|
||||
pauseResume.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,46 +235,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
cancelDownload(itemID)
|
||||
}
|
||||
|
||||
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
when(action){
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
item.status = DownloadRepository.Status.ActivePaused.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.PausedReQueued.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued().filter { it.status != DownloadRepository.Status.Queued.toString() }.forEach {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOutputClick(item: DownloadItem) {
|
||||
if (item.logID != null && item.logID != 0L) {
|
||||
val bundle = Bundle()
|
||||
|
|
@ -279,7 +264,4 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(p0: View?) {
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import android.content.DialogInterface
|
|||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -15,6 +17,7 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.inflate
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
|
|
@ -69,7 +72,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
}
|
||||
|
||||
val fragments = mutableListOf(ActiveDownloadsFragment(), QueuedDownloadsFragment(), CancelledDownloadsFragment(), ErroredDownloadsFragment(), SavedDownloadsFragment())
|
||||
val fragments = mutableListOf(ActiveDownloadsFragment(), QueuedDownloadsFragment(), ScheduledDownloadsFragment(), CancelledDownloadsFragment(), ErroredDownloadsFragment(), SavedDownloadsFragment())
|
||||
|
||||
fragmentAdapter = DownloadListFragmentAdapter(
|
||||
childFragmentManager,
|
||||
|
|
@ -84,9 +87,10 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
when (position) {
|
||||
0 -> tab.text = getString(R.string.running)
|
||||
1 -> tab.text = getString(R.string.in_queue)
|
||||
2 -> tab.text = getString(R.string.cancelled)
|
||||
3 -> tab.text = getString(R.string.errored)
|
||||
4 -> tab.text = getString(R.string.saved)
|
||||
2 -> tab.text = getString(R.string.scheduled)
|
||||
3 -> tab.text = getString(R.string.cancelled)
|
||||
4 -> tab.text = getString(R.string.errored)
|
||||
5 -> tab.text = getString(R.string.saved)
|
||||
}
|
||||
}.attach()
|
||||
|
||||
|
|
@ -105,6 +109,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
tabLayout.selectTab(tabLayout.getTabAt(position))
|
||||
initMenu()
|
||||
}
|
||||
})
|
||||
mainActivity.hideBottomNavigation()
|
||||
|
|
@ -133,32 +138,41 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.cancelledDownloadsCount.collectLatest {
|
||||
downloadViewModel.scheduledDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(2)?.apply {
|
||||
createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.erroredDownloadsCount.collectLatest {
|
||||
downloadViewModel.cancelledDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(3)?.apply {
|
||||
createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.savedDownloadsCount.collectLatest {
|
||||
downloadViewModel.erroredDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(4)?.apply {
|
||||
removeBadge()
|
||||
if (it > 0) createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.savedDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(5)?.apply {
|
||||
removeBadge()
|
||||
if (it > 0) createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun initMenu() {
|
||||
|
||||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
try{
|
||||
when(m.itemId){
|
||||
|
|
@ -172,6 +186,11 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
downloadViewModel.deleteCancelled()
|
||||
}
|
||||
}
|
||||
R.id.clear_scheduled -> {
|
||||
showDeleteDialog {
|
||||
downloadViewModel.deleteScheduled()
|
||||
}
|
||||
}
|
||||
R.id.clear_errored -> {
|
||||
showDeleteDialog {
|
||||
downloadViewModel.deleteErrored()
|
||||
|
|
@ -185,11 +204,12 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
R.id.copy_urls -> {
|
||||
lifecycleScope.launch {
|
||||
val tabStatus = mapOf(
|
||||
0 to listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused, DownloadRepository.Status.PausedReQueued),
|
||||
1 to listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.Queued),
|
||||
2 to listOf(DownloadRepository.Status.Cancelled),
|
||||
3 to listOf(DownloadRepository.Status.Error),
|
||||
4 to listOf(DownloadRepository.Status.Saved),
|
||||
0 to listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused),
|
||||
1 to listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused),
|
||||
2 to listOf(DownloadRepository.Status.Scheduled),
|
||||
3 to listOf(DownloadRepository.Status.Cancelled),
|
||||
4 to listOf(DownloadRepository.Status.Error),
|
||||
5 to listOf(DownloadRepository.Status.Saved),
|
||||
)
|
||||
tabStatus[tabLayout.selectedTabPosition]?.apply {
|
||||
val urls = withContext(Dispatchers.IO){
|
||||
|
|
@ -233,9 +253,4 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DownloadQueueActivity"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,26 @@
|
|||
package com.deniscerri.ytdl.ui.downloads
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
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
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -31,7 +35,7 @@ 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.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdl.ui.adapter.QueuedDownloadAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
|
||||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
|
|
@ -39,6 +43,7 @@ import com.deniscerri.ytdl.util.FileUtil
|
|||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
|
@ -51,15 +56,16 @@ import kotlinx.coroutines.runBlocking
|
|||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||
class QueuedDownloadsFragment : Fragment(), QueuedDownloadAdapter.OnItemClickListener {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
private lateinit var queuedRecyclerView : RecyclerView
|
||||
private lateinit var adapter : GenericDownloadAdapter
|
||||
private lateinit var adapter : QueuedDownloadAdapter
|
||||
private lateinit var noResults : RelativeLayout
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var fileSize: TextView
|
||||
private lateinit var dragHandleToggle: TextView
|
||||
private var totalSize: Int = 0
|
||||
private var actionMode : ActionMode? = null
|
||||
|
||||
|
|
@ -75,21 +81,20 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
return fragmentView
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@SuppressLint("SetTextI18n", "RestrictedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
fileSize = view.findViewById(R.id.filesize)
|
||||
adapter =
|
||||
GenericDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
)
|
||||
dragHandleToggle = view.findViewById(R.id.drag)
|
||||
val itemTouchHelper = ItemTouchHelper(queuedDragDropHelper)
|
||||
adapter = QueuedDownloadAdapter(this, requireActivity(), itemTouchHelper)
|
||||
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
queuedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
queuedRecyclerView.forceFastScrollMode()
|
||||
queuedRecyclerView.adapter = adapter
|
||||
queuedRecyclerView.enableFastScroll()
|
||||
itemTouchHelper.attachToRecyclerView(queuedRecyclerView)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("queued")){
|
||||
|
|
@ -126,83 +131,12 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused)).observe(viewLifecycleOwner){
|
||||
totalSize = it
|
||||
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
|
||||
noResults.isVisible = it == 0
|
||||
dragHandleToggle.isVisible = it > 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionButtonClick(itemID: Long) {
|
||||
removeItem(itemID)
|
||||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
sheet.hide()
|
||||
removeItem(it.id)
|
||||
},
|
||||
downloadItem = {
|
||||
downloadViewModel.deleteDownload(it.id)
|
||||
it.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(it.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", it),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(it)),
|
||||
Pair("type", it.type)
|
||||
))
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(downloadItem.id)
|
||||
downloadItem.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(downloadItem.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
val now = System.currentTimeMillis()
|
||||
actionMode?.apply {
|
||||
if (selectedObjects == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
this.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.download).isVisible = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems.toList(), adapter.inverted, now)
|
||||
}
|
||||
|
||||
this.menu.findItem(R.id.up).isVisible = position > 0
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
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())
|
||||
}
|
||||
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
dragHandleToggle.setOnClickListener {
|
||||
adapter.toggleShowDragHandle()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +189,8 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
lifecycleScope.launch {
|
||||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueuedIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last())
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(
|
||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused).toListString())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
|
|
@ -324,6 +259,17 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
}
|
||||
true
|
||||
}
|
||||
R.id.down -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
adapter.clearCheckedItems()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.putAtBottomOfQueue(selectedObjects)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.copy_urls -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
|
|
@ -420,4 +366,186 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
var movedToNewPositionID = 0L
|
||||
private val queuedDragDropHelper: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val fromPosition = viewHolder.bindingAdapterPosition
|
||||
val toPosition = target.bindingAdapterPosition
|
||||
movedToNewPositionID = target.itemView.tag.toString().toLong()
|
||||
adapter.notifyItemMoved(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(
|
||||
viewHolder: RecyclerView.ViewHolder?,
|
||||
actionState: Int
|
||||
) {
|
||||
super.onSelectedChanged(viewHolder, actionState)
|
||||
if (ItemTouchHelper.ACTION_STATE_DRAG == actionState) {
|
||||
/**
|
||||
* Change alpha, scale and elevation on drag.
|
||||
*/
|
||||
(viewHolder?.itemView as? MaterialCardView)?.also {
|
||||
AnimatorSet().apply {
|
||||
this.duration = 100L
|
||||
this.interpolator = AccelerateDecelerateInterpolator()
|
||||
|
||||
playTogether(
|
||||
UiUtil.getAlphaAnimator(it, 0.7f),
|
||||
UiUtil.getScaleXAnimator(it, 1.02f),
|
||||
UiUtil.getScaleYAnimator(it, 1.02f),
|
||||
UiUtil.getElevationAnimator(it, R.dimen.elevation_6dp)
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearView(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
) {
|
||||
super.clearView(recyclerView, viewHolder)
|
||||
/**
|
||||
* Clear alpha, scale and elevation after drag/swipe
|
||||
*/
|
||||
(viewHolder.itemView as? MaterialCardView)?.also {
|
||||
AnimatorSet().apply {
|
||||
this.duration = 100L
|
||||
this.interpolator = AccelerateDecelerateInterpolator()
|
||||
|
||||
playTogether(
|
||||
UiUtil.getAlphaAnimator(it, 1f),
|
||||
UiUtil.getScaleXAnimator(it, 1f),
|
||||
UiUtil.getScaleYAnimator(it, 1f),
|
||||
UiUtil.getElevationAnimator(it, R.dimen.elevation_2dp)
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
|
||||
downloadViewModel.putAtPosition(viewHolder.itemView.tag.toString().toLong(), movedToNewPositionID)
|
||||
}
|
||||
|
||||
override fun isLongPressDragEnabled(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveQueuedItemToTop(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.putAtTopOfQueue(listOf(itemID))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveQueuedItemToBottom(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.putAtBottomOfQueue(listOf(itemID))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueuedCardClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
sheet.hide()
|
||||
removeItem(it.id)
|
||||
},
|
||||
downloadItem = {
|
||||
downloadViewModel.deleteDownload(it.id)
|
||||
it.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(it.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", it),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(it)),
|
||||
Pair("type", it.type)
|
||||
)
|
||||
)
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(downloadItem.id)
|
||||
downloadItem.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(downloadItem.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueuedCardSelect(isChecked: Boolean, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
val now = System.currentTimeMillis()
|
||||
actionMode?.apply {
|
||||
if (selectedObjects == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
this.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.download).isVisible = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems.toList(), adapter.inverted, now)
|
||||
}
|
||||
|
||||
this.menu.findItem(R.id.up).isVisible = position > 0
|
||||
this.menu.findItem(R.id.down).isVisible = position < totalSize
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
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())
|
||||
}
|
||||
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueuedCancelClick(itemID: Long) {
|
||||
cancelDownload(itemID)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelItem(id: Int){
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
package com.deniscerri.ytdl.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
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.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
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.RecyclerView
|
||||
import androidx.work.WorkManager
|
||||
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.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdl.ui.adapter.ScheduledDownloadAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
|
||||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemClickListener {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
private lateinit var scheduledRecyclerView : RecyclerView
|
||||
private lateinit var preferences : SharedPreferences
|
||||
private lateinit var adapter : ScheduledDownloadAdapter
|
||||
private lateinit var noResults : RelativeLayout
|
||||
private var actionMode : ActionMode? = null
|
||||
private var totalSize = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
||||
activity = getActivity()
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
adapter =
|
||||
ScheduledDownloadAdapter(
|
||||
this,
|
||||
requireActivity()
|
||||
)
|
||||
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
scheduledRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
scheduledRecyclerView.forceFastScrollMode()
|
||||
scheduledRecyclerView.adapter = adapter
|
||||
scheduledRecyclerView.enableFastScroll()
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("scheduled")){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(scheduledRecyclerView)
|
||||
}
|
||||
scheduledRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.scheduledDownloads.collectLatest {
|
||||
adapter.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.scheduledDownloadsCount.collectLatest {
|
||||
totalSize = it
|
||||
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onActionButtonClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
runCatching {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
item.downloadStartTime = 0
|
||||
downloadViewModel.queueDownloads(listOf(item), true)
|
||||
}
|
||||
}.onFailure {
|
||||
Toast.makeText(requireContext(), it.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
sheet.hide()
|
||||
removeItem(it, sheet)
|
||||
},
|
||||
downloadItem = {
|
||||
downloadViewModel.deleteDownload(it.id)
|
||||
it.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(it.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", it),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(it)),
|
||||
Pair("type", it.type)
|
||||
)
|
||||
)
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(downloadItem.id)
|
||||
downloadItem.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(downloadItem.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCardSelect(isChecked: Boolean, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
actionMode?.apply {
|
||||
if (selectedObjects == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
this.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if (selectedObjects == 2){
|
||||
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getScheduledIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last())
|
||||
}
|
||||
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isChecked) {
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
}else{
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0){
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun removeItem(item: DownloadItem, bottomSheet: BottomSheetDialog?){
|
||||
bottomSheet?.hide()
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
|
||||
|
||||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.queued_menu_context, menu)
|
||||
mode.title = "${adapter.getSelectedObjectsCount(totalSize)} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(
|
||||
mode: ActionMode?,
|
||||
menu: Menu?
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(
|
||||
mode: ActionMode?,
|
||||
item: MenuItem?
|
||||
): Boolean {
|
||||
return when (item!!.itemId) {
|
||||
R.id.select_between -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getScheduledIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
adapter.checkMultipleItems(idsInMiddle)
|
||||
actionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
|
||||
}
|
||||
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.delete_results -> {
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
adapter.clearCheckedItems()
|
||||
downloadViewModel.deleteAllWithID(selectedObjects)
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
R.id.download -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
adapter.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.resetScheduleTimeForItemsAndStartDownload(selectedObjects)
|
||||
}
|
||||
actionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
adapter.checkAll()
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
adapter.invertSelected()
|
||||
val selectedObjects = adapter.getSelectedObjectsCount(totalSize)
|
||||
actionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
R.id.copy_urls -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
val urls = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getURLsByIds(selectedObjects)
|
||||
}
|
||||
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
|
||||
actionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
actionMode = null
|
||||
adapter.clearCheckedItems()
|
||||
}
|
||||
|
||||
suspend fun getSelectedIDs() : List<Long>{
|
||||
return if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems.toList(), listOf(
|
||||
DownloadRepository.Status.Scheduled))
|
||||
}
|
||||
}else{
|
||||
adapter.checkedItems.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
downloadViewModel.deleteDownload(deletedItem.id)
|
||||
Snackbar.make(scheduledRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
ItemTouchHelper.RIGHT -> {
|
||||
onActionButtonClick(itemID)
|
||||
adapter.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChildDraw(
|
||||
c: Canvas,
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
dX: Float,
|
||||
dY: Float,
|
||||
actionState: Int,
|
||||
isCurrentlyActive: Boolean
|
||||
) {
|
||||
RecyclerViewSwipeDecorator.Builder(
|
||||
requireContext(),
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
.addSwipeLeftBackgroundColor(Color.RED)
|
||||
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
|
||||
.addSwipeRightBackgroundColor(
|
||||
MaterialColors.getColor(
|
||||
requireContext(),
|
||||
R.attr.colorOnSurfaceInverse,Color.TRANSPARENT
|
||||
)
|
||||
)
|
||||
.addSwipeRightActionIcon(R.drawable.ic_refresh)
|
||||
.create()
|
||||
.decorate()
|
||||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder!!,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -107,6 +107,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
|
||||
val useCookiesPref = preferences.getBoolean("use_cookies", false)
|
||||
useCookies.isChecked = useCookiesPref
|
||||
useCookies.jumpDrawablesToCurrentState()
|
||||
newCookie.isEnabled = useCookiesPref
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ class DownloadLogFragment : Fragment() {
|
|||
if (logItem != null){
|
||||
if (logItem.content.isNotBlank()) {
|
||||
content.setText(logItem.content, TextView.BufferType.SPANNABLE)
|
||||
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)
|
||||
}
|
||||
if (!bottomAppBar.menu.children.first { it.itemId == R.id.scroll_down }.isVisible){
|
||||
content.scrollTo(0, content.height)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
|
|
@ -14,6 +16,8 @@ import com.deniscerri.ytdl.R
|
|||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
|
@ -25,6 +29,16 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
|||
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
findPreference<Preference>("prevent_duplicate_downloads")?.apply {
|
||||
//TODO transitioning code, delete after couple releases
|
||||
if (preferences.getBoolean("download_archive", false)){
|
||||
preferences.edit(commit = true){
|
||||
putBoolean("download_archive", false).apply()
|
||||
putString("prevent_duplicate_downloads", "download_archive")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val rememberDownloadType = findPreference<SwitchPreferenceCompat>("remember_download_type")
|
||||
val downloadType = findPreference<ListPreference>("preferred_download_type")
|
||||
downloadType?.isEnabled = rememberDownloadType?.isChecked == false
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import android.app.Activity
|
|||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
|
|
@ -112,15 +114,22 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
commandPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
accessAllFiles!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.parse("package:" + requireContext().packageName)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
if(VERSION.SDK_INT >= 30){
|
||||
accessAllFiles!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||
val uri = Uri.parse("package:" + requireContext().packageName)
|
||||
intent.data = uri
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
if (noFragments!!.isChecked) {
|
||||
editor.putBoolean("keep_cache", false).apply()
|
||||
keepFragments!!.isChecked = false
|
||||
keepFragments!!.isEnabled = false
|
||||
}
|
||||
noFragments!!.setOnPreferenceChangeListener { _, newValue ->
|
||||
if(newValue as Boolean){
|
||||
editor.putBoolean("keep_cache", false).apply()
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
showTerminalShareIcon!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { pref: Preference?, _: Any ->
|
||||
val packageManager = requireContext().packageManager
|
||||
val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdlnis.terminalShareAlias")
|
||||
val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdl.terminalShareAlias")
|
||||
if ((pref as SwitchPreferenceCompat).isChecked){
|
||||
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.models.LogItem
|
||||
|
|
@ -15,6 +16,7 @@ class CrashListener(private val context: Context) : Thread.UncaughtExceptionHand
|
|||
|
||||
override fun uncaughtException(p0: Thread, p1: Throwable) {
|
||||
p1.message?.apply {
|
||||
Log.e("ERROR", this)
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
createLog(this@apply)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.R.color
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
|
|
@ -9,7 +10,9 @@ import android.graphics.Bitmap
|
|||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Outline
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.OvalShape
|
||||
import android.media.MediaMetadataRetriever
|
||||
|
|
@ -27,6 +30,7 @@ import android.widget.ImageView
|
|||
import android.widget.TextView
|
||||
import androidx.annotation.Px
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.withStarted
|
||||
|
|
@ -335,10 +339,13 @@ object Extensions {
|
|||
return cookie.toString()
|
||||
}
|
||||
|
||||
fun ObserveSourcesItem.calculateNextTime() : Long {
|
||||
fun ObserveSourcesItem.calculateNextTimeForObserving() : Long {
|
||||
val item = this
|
||||
val now = System.currentTimeMillis()
|
||||
Calendar.getInstance().apply {
|
||||
if (item.everyCategory != com.deniscerri.ytdl.database.repository.ObserveSourcesRepository.EveryCategory.HOUR){
|
||||
timeInMillis = item.startsTime
|
||||
|
||||
if (item.everyCategory != EveryCategory.HOUR){
|
||||
val hourMin = Calendar.getInstance()
|
||||
hourMin.timeInMillis = item.everyTime
|
||||
|
||||
|
|
@ -346,38 +353,40 @@ object Extensions {
|
|||
set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||
}
|
||||
|
||||
when(item.everyCategory){
|
||||
EveryCategory.HOUR -> {
|
||||
add(Calendar.HOUR, item.everyNr)
|
||||
}
|
||||
EveryCategory.DAY -> {
|
||||
add(Calendar.DAY_OF_MONTH, item.everyNr)
|
||||
}
|
||||
EveryCategory.WEEK -> {
|
||||
item.weeklyConfig?.apply {
|
||||
if (this.weekDays.isEmpty()){
|
||||
add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}else{
|
||||
var weekDayNr = get(Calendar.DAY_OF_WEEK) - 1
|
||||
if (weekDayNr == 0) weekDayNr = 7
|
||||
val followingWeekDay = this.weekDays.firstOrNull { it > weekDayNr }
|
||||
if (followingWeekDay == null){
|
||||
add(Calendar.DAY_OF_MONTH, this.weekDays.minBy { it } + (7 - weekDayNr))
|
||||
item.everyNr--
|
||||
}else{
|
||||
add(Calendar.DAY_OF_MONTH, followingWeekDay.toInt() - weekDayNr)
|
||||
}
|
||||
|
||||
if (item.everyNr > 1){
|
||||
while (timeInMillis < now){
|
||||
when(item.everyCategory){
|
||||
EveryCategory.HOUR -> {
|
||||
add(Calendar.HOUR, item.everyNr)
|
||||
}
|
||||
EveryCategory.DAY -> {
|
||||
add(Calendar.DAY_OF_MONTH, item.everyNr)
|
||||
}
|
||||
EveryCategory.WEEK -> {
|
||||
item.weeklyConfig?.apply {
|
||||
if (this.weekDays.isEmpty()){
|
||||
add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}else{
|
||||
var weekDayNr = get(Calendar.DAY_OF_WEEK) - 1
|
||||
if (weekDayNr == 0) weekDayNr = 7
|
||||
val followingWeekDay = this.weekDays.firstOrNull { it > weekDayNr }
|
||||
if (followingWeekDay == null){
|
||||
add(Calendar.DAY_OF_MONTH, this.weekDays.minBy { it } + (7 - weekDayNr))
|
||||
item.everyNr--
|
||||
}else{
|
||||
add(Calendar.DAY_OF_MONTH, followingWeekDay.toInt() - weekDayNr)
|
||||
}
|
||||
|
||||
if (item.everyNr > 1){
|
||||
add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EveryCategory.MONTH -> {
|
||||
add(Calendar.MONTH, item.everyNr)
|
||||
item.monthlyConfig?.apply {
|
||||
set(Calendar.DAY_OF_MONTH, this.everyMonthDay)
|
||||
EveryCategory.MONTH -> {
|
||||
add(Calendar.MONTH, item.everyNr)
|
||||
item.monthlyConfig?.apply {
|
||||
set(Calendar.DAY_OF_MONTH, this.everyMonthDay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -392,7 +401,14 @@ object Extensions {
|
|||
drawable!!.intrinsicWidth,
|
||||
drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||
)
|
||||
|
||||
val paint = Paint()
|
||||
val colorValue = TypedValue()
|
||||
context.theme.resolveAttribute(android.R.attr.colorActivatedHighlight, colorValue, true)
|
||||
paint.setColorFilter(PorterDuffColorFilter(colorValue.data, PorterDuff.Mode.SRC_IN))
|
||||
|
||||
val canvas = Canvas(bitmap)
|
||||
DrawableCompat.setTint(drawable, colorValue.data)
|
||||
drawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
|
|
|
|||
|
|
@ -763,6 +763,14 @@ class InfoUtil(private val context: Context) {
|
|||
format.put("filesize", size)
|
||||
}catch (ignored: Exception){}
|
||||
}
|
||||
|
||||
if (format.has("filesize_approx")){
|
||||
if (format.get("filesize_approx") == "None"){
|
||||
format.remove("filesize_approx")
|
||||
format.put("filesize_approx", 0)
|
||||
}
|
||||
}
|
||||
|
||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||
|
||||
if (formatProper.format_note == null) continue
|
||||
|
|
@ -1089,7 +1097,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("download_archive", false)){
|
||||
if (sharedPreferences.getString("prevent_duplicate_downloads", "")!! == "download_archive"){
|
||||
request.addOption("--download-archive", FileUtil.getDownloadArchivePath(context))
|
||||
}
|
||||
|
||||
|
|
@ -1152,7 +1160,7 @@ class InfoUtil(private val context: Context) {
|
|||
if (embedMetadata){
|
||||
request.addOption("--embed-metadata")
|
||||
request.addOption("--parse-metadata", "artist:(?P<meta_album_artist>[^,]+)")
|
||||
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist,uploader)s:%(album_artist)s")
|
||||
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist,uploader|)s:%(album_artist)s")
|
||||
|
||||
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
|
||||
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
|
||||
|
|
@ -1166,21 +1174,29 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
|
||||
if (thumbnailFormat == "jpg"){
|
||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||
}
|
||||
|
||||
if (downloadItem.audioPreferences.embedThumb){
|
||||
request.addOption("--embed-thumbnail")
|
||||
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
|
||||
|
||||
val thumbnailConfig = StringBuilder("")
|
||||
val cropConfig = """-vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"""""
|
||||
if (thumbnailFormat == "jpg") thumbnailConfig.append("""--ppa "ThumbnailsConvertor:-qmin 1 -q:v 1"""")
|
||||
if (cropThumb){
|
||||
if (thumbnailFormat == "jpg") {
|
||||
thumbnailConfig.deleteCharAt(thumbnailConfig.length - 1)
|
||||
thumbnailConfig.append(""" $cropConfig""")
|
||||
}
|
||||
else thumbnailConfig.append("""--ppa "ThumbnailsConvertor:$cropConfig""")
|
||||
}
|
||||
|
||||
if (thumbnailConfig.isNotBlank()){
|
||||
runCatching {
|
||||
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
|
||||
val configData = "--ppa \"ThumbnailsConvertor:-vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||
config.writeText(configData)
|
||||
config.writeText(thumbnailConfig.toString())
|
||||
request.addOption("--config", config.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (filenameTemplate.isNotBlank()){
|
||||
|
|
@ -1220,19 +1236,20 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
//format logic
|
||||
var videoF = downloadItem.format.format_id
|
||||
var audioF = downloadItem.videoPreferences.audioFormatIDs.joinToString("+") { f ->
|
||||
var altAudioF = ""
|
||||
var audioF = downloadItem.videoPreferences.audioFormatIDs.map { f ->
|
||||
val format = downloadItem.allFormats.find { it.format_id == f }
|
||||
format?.run {
|
||||
if (this.format_id.contains("-")) {
|
||||
if (!this.lang.isNullOrBlank() && this.lang != "None") {
|
||||
"ba[format_id~='^(${this.format_id.split("-")[0]})'][language^=${this.lang}]"
|
||||
} else {
|
||||
"${this.format_id}/${this.format_id.split("-")[0]}"
|
||||
altAudioF = this.format_id.split("-")[0]
|
||||
this.format_id
|
||||
}
|
||||
} else this.format_id
|
||||
} ?: f
|
||||
|
||||
}.ifBlank { "ba" }
|
||||
}.joinToString("+").ifBlank { "ba" }
|
||||
val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!!
|
||||
if (downloadItem.videoPreferences.removeAudio) audioF = ""
|
||||
|
||||
|
|
@ -1244,13 +1261,18 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
val defaultFormats = context.resources.getStringArray(R.array.video_formats_values)
|
||||
val usingGenericFormat = defaultFormats.contains(videoF) || downloadItem.allFormats.isEmpty() || downloadItem.allFormats == getGenericVideoFormats(context.resources)
|
||||
var hasGenericResulutionFormat = ""
|
||||
if (!usingGenericFormat){
|
||||
// with audio removed
|
||||
if (audioF.isBlank()){
|
||||
f.append("$videoF/bv/b")
|
||||
}else{
|
||||
//with audio
|
||||
f.append("$videoF+$audioF/$videoF+ba/$videoF/b")
|
||||
f.append("$videoF+$audioF/")
|
||||
if (altAudioF.isNotBlank()){
|
||||
f.append("$videoF+$altAudioF/")
|
||||
}
|
||||
f.append("$videoF+ba/$videoF/b")
|
||||
|
||||
if (audioF.count("+") > 0){
|
||||
request.addOption("--audio-multistreams")
|
||||
|
|
@ -1263,7 +1285,8 @@ class InfoUtil(private val context: Context) {
|
|||
videoF = "wv"
|
||||
if (audioF == "ba") audioF = "wa"
|
||||
}else if (defaultFormats.contains(videoF)) {
|
||||
videoF = "bv[height<="+videoF.split("_")[0].dropLast(1)+"]"
|
||||
hasGenericResulutionFormat = videoF.split("_")[0].dropLast(1)
|
||||
videoF = "bv"
|
||||
}
|
||||
|
||||
val preferredFormatIDs = sharedPreferences.getString("format_id", "").toString()
|
||||
|
|
@ -1331,6 +1354,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
StringBuilder().apply {
|
||||
if (hasGenericResulutionFormat.isNotBlank()) append(",res:${hasGenericResulutionFormat}")
|
||||
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
|
||||
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
||||
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
|
||||
|
|
@ -1369,7 +1393,9 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
|
||||
if (downloadItem.videoPreferences.removeAudio){
|
||||
request.addOption("--ppa", "ffmpeg:-an")
|
||||
request.addOption("--use-postprocessor", "FFmpegCopyStream")
|
||||
request.addOption("--ppa", "CopyStream:-c copy -an")
|
||||
|
||||
}
|
||||
|
||||
request.addOption("-P", downDir.absolutePath)
|
||||
|
|
@ -1458,7 +1484,15 @@ 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")
|
||||
val acodecPreference = sharedPreferences.getString("audio_codec", "m4a|mp4a|aac")!!.run {
|
||||
if (this.isEmpty()){
|
||||
resources.getString(R.string.defaultValue)
|
||||
}else{
|
||||
val audioCodecs = resources.getStringArray(R.array.audio_codec)
|
||||
val audioCodecsValues = resources.getStringArray(R.array.audio_codec_values)
|
||||
audioCodecs[audioCodecsValues.indexOf(this)]
|
||||
}
|
||||
}
|
||||
audioFormats.forEachIndexed { idx, it -> formats.add(Format(audioFormatsValues[idx], containerPreference!!,"",acodecPreference!!, "",0, it)) }
|
||||
audioFormats.reverse()
|
||||
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"",resources.getString(R.string.preferred_format_id), "",1, it)) }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import android.app.TaskStackBuilder
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
|
@ -243,19 +244,18 @@ class NotificationUtil(var context: Context) {
|
|||
}
|
||||
|
||||
val bitmap = iconType.toBitmap(context)
|
||||
val bigPictureStyle = NotificationCompat.BigPictureStyle()
|
||||
bigPictureStyle.bigLargeIcon(bitmap)
|
||||
if (Build.VERSION.SDK_INT >= 31){
|
||||
bigPictureStyle.showBigPictureWhenCollapsed(true)
|
||||
}
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${res.getString(R.string.downloaded)} $title")
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(bitmap)
|
||||
.setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString())
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||
.setContentText(title)
|
||||
.setStyle(bigPictureStyle)
|
||||
.setStyle(NotificationCompat.BigTextStyle()
|
||||
.bigText("""
|
||||
$title
|
||||
${filepath?.joinToString("\n")}
|
||||
""".trimIndent()))
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
|
|
@ -404,7 +404,7 @@ class NotificationUtil(var context: Context) {
|
|||
)
|
||||
|
||||
try {
|
||||
notificationBuilder.setProgress(100, progress, progress == 0)
|
||||
notificationBuilder.setProgress(100, progress, (progress == 0 || progress == 100))
|
||||
.setContentTitle(title)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
.clearActions()
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipData.Item
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Canvas
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.Editable
|
||||
|
|
@ -26,10 +32,13 @@ import android.widget.EditText
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.DimenRes
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.isVisible
|
||||
|
|
@ -39,6 +48,7 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
|
|
@ -77,6 +87,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
||||
import com.google.android.material.textfield.TextInputLayout.EndIconMode
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -696,11 +708,11 @@ object UiUtil {
|
|||
true
|
||||
}
|
||||
}
|
||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused -> {
|
||||
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) download!!.visibility = View.GONE
|
||||
else{
|
||||
download!!.text = context.getString(R.string.download_now)
|
||||
}
|
||||
DownloadRepository.Status.Queued -> {
|
||||
download!!.visibility = View.GONE
|
||||
}
|
||||
DownloadRepository.Status.Scheduled -> {
|
||||
download!!.text = context.getString(R.string.download_now)
|
||||
}
|
||||
else -> {
|
||||
download?.setOnLongClickListener {
|
||||
|
|
@ -1803,9 +1815,10 @@ object UiUtil {
|
|||
}
|
||||
}
|
||||
|
||||
sheet.setOnDismissListener {
|
||||
sheet.setOnDismissListener { _ ->
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.deleteProcessing()
|
||||
downloadViewModel.deleteAllWithID(it.downloadItems)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2023,7 +2036,10 @@ object UiUtil {
|
|||
builder.setTitle(context.getString(R.string.piped_instance))
|
||||
val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null)
|
||||
val editText = view.findViewById<EditText>(R.id.filename_edittext)
|
||||
view.findViewById<TextInputLayout>(R.id.filename).hint = context.getString(R.string.piped_instance)
|
||||
view.findViewById<TextInputLayout>(R.id.filename).apply {
|
||||
hint = context.getString(R.string.piped_instance)
|
||||
endIconMode = END_ICON_NONE
|
||||
}
|
||||
editText.setText(currentInstance)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(view)
|
||||
|
|
@ -2142,4 +2158,28 @@ object UiUtil {
|
|||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
|
||||
fun getAlphaAnimator(view: View, alphaTo: Float): Animator {
|
||||
return ObjectAnimator.ofFloat(view, View.ALPHA, view.alpha, alphaTo)
|
||||
}
|
||||
|
||||
fun getScaleXAnimator(view: View, scaleTo: Float): Animator {
|
||||
return ObjectAnimator.ofFloat(view, View.SCALE_X, view.scaleX, scaleTo)
|
||||
}
|
||||
|
||||
fun getScaleYAnimator(view: View, scaleTo: Float): Animator {
|
||||
return ObjectAnimator.ofFloat(view, View.SCALE_Y, view.scaleX, scaleTo)
|
||||
}
|
||||
|
||||
fun getElevationAnimator(view: MaterialCardView, @DimenRes elevationTo: Int): Animator {
|
||||
val valueFrom = view.cardElevation
|
||||
val valueTo = view.context.resources.getDimensionPixelSize(elevationTo).toFloat()
|
||||
|
||||
return ValueAnimator.ofFloat(valueFrom, valueTo).apply {
|
||||
addUpdateListener {
|
||||
view.cardElevation = it.animatedValue as? Float ?: return@addUpdateListener
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,10 +64,11 @@ class DownloadWorker(
|
|||
val alarmScheduler = AlarmScheduler(context)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val time = System.currentTimeMillis() + 6000
|
||||
val queuedItems = dao.getQueuedDownloadsThatAreNotScheduledChunked(time)
|
||||
val queuedItems = dao.getQueuedScheduledDownloadsUntil(time)
|
||||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||
|
||||
// this is needed for observe sources call, so it wont create result items
|
||||
val createResultItem = inputData.getBoolean("createResultItem", true)
|
||||
|
||||
val confTmp = Configuration(context.resources.configuration)
|
||||
|
|
@ -176,21 +177,31 @@ class DownloadWorker(
|
|||
}
|
||||
val wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||
runBlocking {
|
||||
var finalPaths : List<String>?
|
||||
var finalPaths : MutableList<String>?
|
||||
|
||||
if (noCache){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
||||
finalPaths = it.out.split("\n")
|
||||
.asSequence()
|
||||
val outputSequence = it.out.split("\n")
|
||||
finalPaths =
|
||||
outputSequence.asSequence()
|
||||
.filter { it.startsWith("'/storage") }
|
||||
.map { it.removePrefix("'") }
|
||||
.map { it.removeSuffix("\n") }
|
||||
.map { it.removeSuffix("'") }
|
||||
.sortedBy { File(it).lastModified() }
|
||||
.toList()
|
||||
.map { it.removeSuffix("\n") }
|
||||
.map { it.removeSurrounding("'", "'") }
|
||||
.toMutableList()
|
||||
|
||||
finalPaths.addAll(
|
||||
outputSequence.asSequence()
|
||||
.filter { it.startsWith("[SplitChapters]") && it.contains("Destination: ") }
|
||||
.map { it.split("Destination: ")[1] }
|
||||
.map { it.removeSuffix("\n") }
|
||||
.toList()
|
||||
)
|
||||
|
||||
finalPaths.sortBy { File(it).lastModified() }
|
||||
finalPaths = finalPaths.distinct().toMutableList()
|
||||
FileUtil.scanMedia(finalPaths, context)
|
||||
if (finalPaths.isEmpty()){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}else{
|
||||
//move file from internal to set download directory
|
||||
|
|
@ -200,15 +211,15 @@ class DownloadWorker(
|
|||
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))
|
||||
}
|
||||
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }
|
||||
}.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))
|
||||
}else{
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
}
|
||||
}catch (e: Exception){
|
||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||
finalPaths = mutableListOf(context.getString(R.string.unfound_file))
|
||||
e.printStackTrace()
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -223,7 +234,7 @@ class DownloadWorker(
|
|||
add("description")
|
||||
add("txt")
|
||||
}
|
||||
finalPaths = finalPaths?.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }
|
||||
finalPaths = finalPaths?.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }?.toMutableList()
|
||||
FileUtil.deleteConfigFiles(request)
|
||||
|
||||
//put download in history
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.deniscerri.ytdl.database.repository.DownloadRepository
|
|||
import com.deniscerri.ytdl.database.repository.HistoryRepository
|
||||
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdl.database.repository.ResultRepository
|
||||
import com.deniscerri.ytdl.util.Extensions.calculateNextTime
|
||||
import com.deniscerri.ytdl.util.Extensions.calculateNextTimeForObserving
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
|
|
@ -210,7 +210,7 @@ class ObserveSourceWorker(
|
|||
.addTag("observeSources")
|
||||
.addTag(sourceID.toString())
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(System.currentTimeMillis() - item.calculateNextTime(), TimeUnit.MILLISECONDS)
|
||||
.setInitialDelay(item.calculateNextTimeForObserving() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
|
||||
.setInputData(Data.Builder().putLong("id", sourceID).build())
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ import com.yausername.youtubedl_android.YoutubeDL
|
|||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
|
@ -140,29 +142,23 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
}
|
||||
|
||||
return runBlocking {
|
||||
if (logDownloads) logRepo.update(it.out, logItem.id)
|
||||
dao.updateLog(it.out, itemId.toLong())
|
||||
dao.delete(itemId.toLong())
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
|
||||
Result.success()
|
||||
}
|
||||
if (logDownloads) logRepo.update(it.out, logItem.id)
|
||||
dao.updateLog(it.out, itemId.toLong())
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
delay(1000)
|
||||
dao.delete(itemId.toLong())
|
||||
Result.success()
|
||||
}.onFailure {
|
||||
return runBlocking {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (it.message != null){
|
||||
if (logDownloads) logRepo.update(it.message!!, logItem.id)
|
||||
dao.updateLog(it.message!!, itemId.toLong())
|
||||
dao.delete(itemId.toLong())
|
||||
}
|
||||
}
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
Result.failure()
|
||||
if (it.message != null){
|
||||
if (logDownloads) logRepo.update(it.message!!, logItem.id)
|
||||
dao.updateLog(it.message!!, itemId.toLong())
|
||||
}
|
||||
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
delay(1000)
|
||||
dao.delete(itemId.toLong())
|
||||
Result.failure()
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
5
app/src/main/res/drawable/baseline_downloading_red.xml
Normal file
5
app/src/main/res/drawable/baseline_downloading_red.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#db2e2e" android:pathData="M18.32,4.26C16.84,3.05 15.01,2.25 13,2.05v2.02c1.46,0.18 2.79,0.76 3.9,1.62L18.32,4.26zM19.93,11h2.02c-0.2,-2.01 -1,-3.84 -2.21,-5.32L18.31,7.1C19.17,8.21 19.75,9.54 19.93,11zM18.31,16.9l1.43,1.43c1.21,-1.48 2.01,-3.32 2.21,-5.32h-2.02C19.75,14.46 19.17,15.79 18.31,16.9zM13,19.93v2.02c2.01,-0.2 3.84,-1 5.32,-2.21l-1.43,-1.43C15.79,19.17 14.46,19.75 13,19.93zM13,12V7h-2v5H7l5,5l5,-5H13zM11,19.93v2.02c-5.05,-0.5 -9,-4.76 -9,-9.95s3.95,-9.45 9,-9.95v2.02C7.05,4.56 4,7.92 4,12S7.05,19.44 11,19.93z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_filter_alt_24.xml
Normal file
5
app/src/main/res/drawable/baseline_filter_alt_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_layers_24.xml
Normal file
5
app/src/main/res/drawable/baseline_layers_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_more_vert_24.xml
Normal file
5
app/src/main/res/drawable/baseline_more_vert_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_downloads_red.xml
Normal file
5
app/src/main/res/drawable/ic_downloads_red.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#db2e2e" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
|
||||
</vector>
|
||||
4
app/src/main/res/drawable/ic_drag_handle.xml
Normal file
4
app/src/main/res/drawable/ic_drag_handle.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?android:colorAccent" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,9H4v2h16V9zM4,15h16v-2H4V15z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_search_red.xml
Normal file
5
app/src/main/res/drawable/ic_search_red.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#db2e2e" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_terminal_formatcard.xml
Normal file
5
app/src/main/res/drawable/ic_terminal_formatcard.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="19dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_terminal_red.xml
Normal file
5
app/src/main/res/drawable/ic_terminal_red.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#db2e2e" android:pathData="M20,4H4C2.89,4 2,4.9 2,6v12c0,1.1 0.89,2 2,2h16c1.1,0 2,-0.9 2,-2V6C22,4.9 21.11,4 20,4zM20,18H4V8h16V18zM18,17h-6v-2h6V17zM7.5,17l-1.41,-1.41L8.67,13l-2.59,-2.59L7.5,9l4,4L7.5,17z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_video_formatcard.xml
Normal file
5
app/src/main/res/drawable/ic_video_formatcard.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="19dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M17,10.5V7c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1v-3.5l4,4v-11l-4,4z"/>
|
||||
</vector>
|
||||
|
|
@ -166,23 +166,6 @@
|
|||
app:layout_constraintVertical_bias="0.51"
|
||||
>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_pause"
|
||||
style="?attr/materialIconButtonFilledStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/pause"
|
||||
android:layoutAnimation="@anim/popup_enter"
|
||||
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_toStartOf="@id/active_download_delete"
|
||||
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"
|
||||
|
|
@ -191,7 +174,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:backgroundTint="?attr/colorSurface"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/baseline_delete_24"
|
||||
app:icon="@drawable/baseline_close_24"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/output"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView android:id="@+id/active_download_card_view"
|
||||
<com.google.android.material.card.MaterialCardView android:id="@+id/download_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
|
|
@ -12,66 +13,53 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:cardCornerRadius="10dp"
|
||||
android:layout_marginVertical="10dp"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
app:trackColor="#000"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:alpha="0.3"
|
||||
android:scaleY="200"/>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="80dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/image_frame"
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/downloads_image_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="?attr/colorSurfaceVariant"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
android:elevation="10dp"
|
||||
app:layout_constraintStart_toStartOf="@id/image_view"
|
||||
app:layout_constraintEnd_toEndOf="@id/image_view"
|
||||
app:layout_constraintBottom_toBottomOf="@id/image_view"
|
||||
app:layout_constraintTop_toTopOf="@id/image_view"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:alpha="0.6"
|
||||
android:scaleY="200"
|
||||
app:trackColor="#000" />
|
||||
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:background="?attr/colorSurfaceVariant"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/download_item_data"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/active_download_pause"
|
||||
app:layout_constraintHorizontal_weight="0.7"
|
||||
app:layout_constraintStart_toEndOf="@+id/image_frame"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintEnd_toStartOf="@+id/options"
|
||||
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
|
|
@ -91,8 +79,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -100,74 +86,118 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:drawableStartCompat="@drawable/ic_music_formatcard"
|
||||
app:drawableTint="@color/white"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_pause"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/pause"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/exomedia_ic_pause_white"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/active_download_delete"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_delete"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/options"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/Remove"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/baseline_delete_24"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:padding="16dp"
|
||||
android:tintMode="src_in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="download_item_data,active_download_delete" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/baseline_more_vert_24"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
|||
|
|
@ -109,6 +109,44 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_type"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
|
|
@ -150,24 +188,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -9,18 +9,6 @@
|
|||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/pause_resume"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="30dp"
|
||||
android:layout_gravity="end"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="@string/resume"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/download_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -30,12 +18,39 @@
|
|||
android:scrollbars="none"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:paddingBottom="100dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/pause"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/pause"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/pause"
|
||||
app:icon="@drawable/exomedia_ic_pause_white"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/resume"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/resume"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/resume"
|
||||
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
|
||||
<include layout="@layout/no_results"
|
||||
android:visibility="gone"/>
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -11,19 +11,36 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="30dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:visibility="gone"
|
||||
android:text="@string/file_size"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize"
|
||||
android:layout_width="0dp"
|
||||
android:visibility="gone"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="30dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="@string/file_size"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toStartOf="@+id/drag"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/drag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
app:drawableStartCompat="@drawable/ic_drag_handle"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/download_recyclerview"
|
||||
|
|
|
|||
|
|
@ -76,9 +76,9 @@
|
|||
android:paddingEnd="10dp"
|
||||
android:scrollbars="none"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="1.5"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="1"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="bold" />
|
||||
|
|
|
|||
251
app/src/main/res/layout/queued_download_card.xml
Normal file
251
app/src/main/res/layout/queued_download_card.xml
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView android:id="@+id/download_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:checkedIcon="@null"
|
||||
app:strokeColor="?attr/colorPrimary"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/drag_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:clickable="false"
|
||||
android:visibility="gone"
|
||||
android:focusable="false"
|
||||
android:paddingVertical="16dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingStart="0dp"
|
||||
android:tintMode="src_in"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_drag_handle"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/card_content"
|
||||
android:layout_width="0dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/drag_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/downloads_image_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="?attr/colorSurfaceVariant"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintHorizontal_weight="0.3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:background="#80000000"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/downloads_image_view"
|
||||
app:layout_constraintEnd_toEndOf="@+id/downloads_image_view" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/download_item_data"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_weight="0.7"
|
||||
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toStartOf="@+id/options"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="title,options" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/barrier">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_type"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/options"
|
||||
style="?attr/materialIconButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:padding="0dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:icon="@drawable/baseline_more_vert_24"
|
||||
app:iconTint="?attr/colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
18
app/src/main/res/layout/scheduled_download_card.xml
Normal file
18
app/src/main/res/layout/scheduled_download_card.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:id="@+id/scheduled_download_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scheduled_time"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingTop="10dp"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<include layout="@layout/download_card" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -66,6 +66,7 @@
|
|||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:paddingHorizontal="15dp"
|
||||
android:paddingBottom="15dp"
|
||||
android:scrollbars="none"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
|
|
|||
27
app/src/main/res/menu/active_downloads_minified.xml
Normal file
27
app/src/main/res/menu/active_downloads_minified.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<group>
|
||||
<item
|
||||
android:id="@+id/pause"
|
||||
android:title="@string/pause"
|
||||
android:icon="@drawable/exomedia_ic_pause_white"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/resume"
|
||||
android:title="@string/resume"
|
||||
android:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:showAsAction="ifRoom" />
|
||||
</group>
|
||||
|
||||
<item
|
||||
android:id="@+id/cancel"
|
||||
android:title="@string/cancel"
|
||||
android:icon="@drawable/baseline_close_24"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
||||
|
||||
|
|
@ -15,6 +15,12 @@
|
|||
android:title="@string/clear_cancelled"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/clear_scheduled"
|
||||
android:icon="@drawable/ic_delete_all"
|
||||
android:title="@string/clear_scheduled"
|
||||
app:showAsAction="never" />
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/clear_errored"
|
||||
|
|
|
|||
24
app/src/main/res/menu/queued_download_menu.xml
Normal file
24
app/src/main/res/menu/queued_download_menu.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<group>
|
||||
|
||||
<item
|
||||
android:id="@+id/cancel"
|
||||
android:title="@string/cancel" />
|
||||
|
||||
<item
|
||||
android:id="@+id/move_top"
|
||||
android:title="@string/move_top" />
|
||||
|
||||
<item
|
||||
android:id="@+id/move_bottom"
|
||||
android:title="@string/move_bottom" />
|
||||
|
||||
</group>
|
||||
|
||||
<item
|
||||
android:id="@+id/copy_url"
|
||||
android:icon="@drawable/ic_copy"
|
||||
android:title="@string/copy_url" />
|
||||
</menu>
|
||||
|
|
@ -20,7 +20,13 @@
|
|||
android:id="@+id/up"
|
||||
android:icon="@drawable/ic_arrow_up"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="Move Up" />
|
||||
android:title="@string/move_top" />
|
||||
|
||||
<item
|
||||
android:id="@+id/down"
|
||||
android:icon="@drawable/ic_arrow_up"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/move_bottom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/download"
|
||||
|
|
|
|||
|
|
@ -353,8 +353,7 @@
|
|||
<string name="month">شهر</string>
|
||||
<string name="bitrate">معدّل البتات</string>
|
||||
<string name="not_deleted">غير المحذوفة</string>
|
||||
<string name="download_archive_summary">استخدام ملف أرشيف التنزيل الخاص بـ yt-dlp لمنع التنزيل المكرر بدلًا من الطريقة الافتراضية</string>
|
||||
<string name="download_archive">أرشيف التنزيل</string>
|
||||
<string name="archive"> أرشيف التنزيل [استخدام ملف أرشيف التنزيل الخاص بـ yt-dlp لمنع التنزيل المكرر بدلًا من الطريقة الافتراضية]</string>
|
||||
<string name="on_time">في</string>
|
||||
<string name="retry_missing_downloads">إعادة المحاولة للتنزيلات المفقودة</string>
|
||||
<string name="save_auto_subs">حفظ الترجمات التلقائية</string>
|
||||
|
|
|
|||
|
|
@ -351,14 +351,13 @@
|
|||
<string name="bitrate">Bit Sürəti</string>
|
||||
<string name="after">Sonra</string>
|
||||
<string name="not_deleted">Silinməyib</string>
|
||||
<string name="download_archive">Yükləmə Arxivi</string>
|
||||
<string name="archive">Yükləmə Arxivi [Dublikat yükləmələrə mane olmaq üçün daxili funksionallıq əvəzinə, yt-dlp-nin yükləmə arxiv faylın istifadə et]</string>
|
||||
<string name="on_time">Aç</string>
|
||||
<string name="observe_sources">Mənbələrə Nəzarət Et</string>
|
||||
<string name="confirm_delete_sources_desc">Bütün mənbələri həmişəlik sil</string>
|
||||
<string name="starts">Başlayır</string>
|
||||
<string name="occurrences">proses</string>
|
||||
<string name="save_auto_subs">Avtomatik Titirləri Saxla</string>
|
||||
<string name="download_archive_summary">Dublikat yükləmələrə mane olmaq üçün daxili funksionallıq əvəzinə, yt-dlp-nin yükləmə arxiv faylın istifadə et</string>
|
||||
<string name="text_size">Mətn Ölçüsü</string>
|
||||
<string name="add">Əlavə et</string>
|
||||
<string name="swipe_gestures_download_card">Yükləmə Kartında Sürüşdürmə Jestləri</string>
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@
|
|||
<string name="retry_missing_downloads">Opakovat zmeškané stahování</string>
|
||||
<string name="save_auto_subs">Uložit automatické titulky</string>
|
||||
<string name="not_deleted">Neodstraněno</string>
|
||||
<string name="download_archive">Stáhnout archiv</string>
|
||||
<string name="archive">Stáhnout archiv [Použije archivní soubor yt-dlp pro zabránění duplicitního stahování místo vestavěné funkce]</string>
|
||||
<string name="observe_sources">Sledovat zdroje</string>
|
||||
<string name="new_source">Nový zfroj</string>
|
||||
<string name="confirm_delete_sources_desc">Trvale odstraní všechny zdroje</string>
|
||||
|
|
@ -358,7 +358,6 @@
|
|||
<string name="on_time">Zapnout</string>
|
||||
<string name="month">Měsíc</string>
|
||||
<string name="bitrate">Rychlost přenosu</string>
|
||||
<string name="download_archive_summary">Použije archivní soubor yt-dlp pro zabránění duplicitního stahování místo vestavěné funkce</string>
|
||||
<string name="show_download_count">Zobrazení počtů na obrazovce stahovací fronty</string>
|
||||
<string name="thumbnail_format">Formát miniatur</string>
|
||||
<string name="get_new_uploads">Získat pouze nové nahrávky</string>
|
||||
|
|
|
|||
|
|
@ -353,8 +353,7 @@
|
|||
<string name="bitrate">Tasa de datos</string>
|
||||
<string name="save_auto_subs">Guardar subtítulos automáticos</string>
|
||||
<string name="not_deleted">No eliminado</string>
|
||||
<string name="download_archive">Descargar archivo</string>
|
||||
<string name="download_archive_summary">Utilice el archivo de descarga de yt-dlp para evitar descargas duplicadas en lugar de la funcionalidad integrada</string>
|
||||
<string name="archive">Descargar archivo [Utilice el archivo de descarga de yt-dlp para evitar descargas duplicadas en lugar de la funcionalidad integrada]</string>
|
||||
<string name="on_time">En</string>
|
||||
<string name="week">Semana</string>
|
||||
<string name="confirm_delete_sources_desc">Borrar todas las fuentes, permanentemente</string>
|
||||
|
|
|
|||
|
|
@ -350,14 +350,13 @@
|
|||
<string name="bitrate">बिटरेट</string>
|
||||
<string name="save_auto_subs">स्वचालित उपशीर्षक सहेजें</string>
|
||||
<string name="not_deleted">हटाया नहीं गया</string>
|
||||
<string name="download_archive">डाउनलोड संग्रह</string>
|
||||
<string name="archive">डाउनलोड संग्रह [डुप्लिकेट डाउनलोड को रोकने के लिए अंतर्निहित कार्यक्षमता के बजाय yt-dlp की डाउनलोड संग्रह फ़ाइल का उपयोग करें]</string>
|
||||
<string name="on_time">नियत समय पर</string>
|
||||
<string name="delete_all_sources">सभी स्रोत हटाएँ</string>
|
||||
<string name="force_ipv4">IPv4 को बाध्य करें</string>
|
||||
<string name="never">कभी नहीं</string>
|
||||
<string name="copy_urls">URL कॉपी करें</string>
|
||||
<string name="occurrences">घटनाएँ</string>
|
||||
<string name="download_archive_summary">डुप्लिकेट डाउनलोड को रोकने के लिए अंतर्निहित कार्यक्षमता के बजाय yt-dlp की डाउनलोड संग्रह फ़ाइल का उपयोग करें</string>
|
||||
<string name="retry_missing_downloads">गुम हुए डाउनलोड पुनः प्रयास करें</string>
|
||||
<string name="thumbnail_format">थंबनेल प्रारूप</string>
|
||||
<string name="get_new_uploads">केवल नए अपलोड प्राप्त करें</string>
|
||||
|
|
|
|||
|
|
@ -352,8 +352,7 @@
|
|||
<string name="bitrate">Bitráta</string>
|
||||
<string name="save_auto_subs">Feliratok automatikus mentése</string>
|
||||
<string name="not_deleted">Nincs törölve</string>
|
||||
<string name="download_archive">Archívum letöltése</string>
|
||||
<string name="download_archive_summary">Használja az yt-dlp letöltési archívum fájlját a duplikált letöltések megakadályozására a beépített funkció helyett</string>
|
||||
<string name="archive">Archívum letöltése [Használja az yt-dlp letöltési archívum fájlját a duplikált letöltések megakadályozására a beépített funkció helyett]</string>
|
||||
<string name="on_time">Be</string>
|
||||
<string name="occurrences">események</string>
|
||||
<string name="day">Nap</string>
|
||||
|
|
|
|||
|
|
@ -342,10 +342,9 @@
|
|||
<string name="retry_missing_downloads">Coba Kembali Unduhan yang Hilang</string>
|
||||
<string name="bitrate">Laju Bit</string>
|
||||
<string name="save_auto_subs">Simpan Takarir (Subtitle) Otomatis</string>
|
||||
<string name="download_archive">Arsip Unduhan</string>
|
||||
<string name="archive">Arsip Unduhan [Gunakan berkas arsip unduhan yt-dlp untuk mencegah unduhan duplikat alih-alih menggunakan fungsionalitas bawaan]</string>
|
||||
<string name="observe_sources">Amati Sumber</string>
|
||||
<string name="confirm_delete_sources_desc">Hapus semua sumber, secara permanen</string>
|
||||
<string name="download_archive_summary">Gunakan berkas arsip unduhan yt-dlp untuk mencegah unduhan duplikat alih-alih menggunakan fungsionalitas bawaan</string>
|
||||
<string name="day">Hari</string>
|
||||
<string name="not_deleted">Tidak Dihapus</string>
|
||||
<string name="changelog">Log Perubahan</string>
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@
|
|||
<string name="bitrate">Velocità di trasmissione</string>
|
||||
<string name="save_auto_subs">Salva i Sottotitoli Automatici</string>
|
||||
<string name="not_deleted">Non Eliminato</string>
|
||||
<string name="download_archive">Scarica Archivio</string>
|
||||
<string name="archive">Scarica Archivio [Utilizza l\'archivio download di yt-dlp per evitare download duplicati invece della funzionalità integrata]</string>
|
||||
<string name="on_time">Acceso</string>
|
||||
<string name="no_download_fragments">Non scaricare come Frammenti</string>
|
||||
<string name="no_download_fragments_summary">Non usare i file .part e scrivi direttamente nel file di output</string>
|
||||
|
|
@ -366,7 +366,6 @@
|
|||
<string name="confirm_delete_sources_desc">Cancella tutti i sorgenti, permanentemente</string>
|
||||
<string name="copy_urls">Copia URLs</string>
|
||||
<string name="never">Mai</string>
|
||||
<string name="download_archive_summary">Utilizza l\'archivio download di yt-dlp per evitare download duplicati invece della funzionalità integrata</string>
|
||||
<string name="swipe_gestures_download_card">Gestualità di Scorrimento nella download card</string>
|
||||
<string name="get_new_uploads">Ricevi Solo Nuovi Upload</string>
|
||||
<string name="also_download_audio">Scarica Anche come Audio</string>
|
||||
|
|
|
|||
|
|
@ -345,9 +345,8 @@
|
|||
<string name="week">週</string>
|
||||
<string name="retry_missing_downloads">ダウンロード失敗時に再試行</string>
|
||||
<string name="not_deleted">未削除</string>
|
||||
<string name="download_archive">ダウンロード記録</string>
|
||||
<string name="archive">ダウンロード記録 [内蔵の機能ではなく yt-dlp のダウンロード記録ファイルを使い重複ダウンロードを防止]</string>
|
||||
<string name="save_auto_subs">自動字幕を保存</string>
|
||||
<string name="download_archive_summary">内蔵の機能ではなく yt-dlp のダウンロード記録ファイルを使い重複ダウンロードを防止</string>
|
||||
<string name="force_ipv4_desc">すべての接続を IPv4 で行う</string>
|
||||
<string name="observe_sources">指定先を監視</string>
|
||||
<string name="new_source">新しい監視先</string>
|
||||
|
|
|
|||
|
|
@ -337,8 +337,7 @@
|
|||
<string name="retry_missing_downloads">누락된 다운로드 재시도</string>
|
||||
<string name="save_auto_subs">자동 자막 저장</string>
|
||||
<string name="not_deleted">삭제되지 않음</string>
|
||||
<string name="download_archive">아카이브 다운로드</string>
|
||||
<string name="download_archive_summary">내장된 기능 대신 yt-dlp의 다운로드 아카이브를 사용하여 중복된 다운로드를 방지합니다</string>
|
||||
<string name="archive">아카이브 다운로드 [내장된 기능 대신 yt-dlp의 다운로드 아카이브를 사용하여 중복된 다운로드를 방지합니다]</string>
|
||||
<string name="preferred_audio_language">선호하는 오디오 언어</string>
|
||||
<string name="changed_path_for_everyone_to">모든 항목의 다운로드 경로를 바꾸기:</string>
|
||||
<string name="dismiss">무시</string>
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@
|
|||
<string name="retry_missing_downloads">ਗੁੰਮ ਹੋਏ ਡਾਉਨਲੋਡਸ ਲਈ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ</string>
|
||||
<string name="bitrate">ਬਿੱਟਰੇਟ</string>
|
||||
<string name="not_deleted">ਮਿਟਾਇਆ ਨਹੀਂ ਗਿਆ</string>
|
||||
<string name="download_archive">ਡਾਊਨਲੋਡ ਆਰਕਾਈਵ</string>
|
||||
<string name="archive">ਡਾਊਨਲੋਡ ਆਰਕਾਈਵ [ਬਿਲਟ-ਇਨ ਫੰਕਸ਼ਨੈਲਿਟੀ ਦੀ ਬਜਾਏ ਡੁਪਲੀਕੇਟ ਡਾਉਨਲੋਡਸ ਨੂੰ ਰੋਕਣ ਲਈ yt-dlp ਦੀ ਡਾਉਨਲੋਡ ਆਰਕਾਈਵ ਫਾਈਲ ਦੀ ਵਰਤੋਂ ਕਰੋ]</string>
|
||||
<string name="on_time">\'ਤੇ</string>
|
||||
<string name="observe_sources">ਸਰੋਤਾਂ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ</string>
|
||||
<string name="confirm_delete_sources_desc">ਸਾਰੇ ਸਰੋਤਾਂ ਨੂੰ ਪੱਕੇ ਤੌਰ \'ਤੇ ਮਿਟਾਓ</string>
|
||||
|
|
@ -358,7 +358,6 @@
|
|||
<string name="never">ਕਦੇ ਨਹੀਂ</string>
|
||||
<string name="ends">ਖਤਮ ਹੁੰਦਾ ਹੈ</string>
|
||||
<string name="save_auto_subs">ਸਵੈਚਲਿਤ ਸਬਟਾਈਟਲਾਂ ਨੂੰ ਸੇਵ ਕਰੋ</string>
|
||||
<string name="download_archive_summary">ਬਿਲਟ-ਇਨ ਫੰਕਸ਼ਨੈਲਿਟੀ ਦੀ ਬਜਾਏ ਡੁਪਲੀਕੇਟ ਡਾਉਨਲੋਡਸ ਨੂੰ ਰੋਕਣ ਲਈ yt-dlp ਦੀ ਡਾਉਨਲੋਡ ਆਰਕਾਈਵ ਫਾਈਲ ਦੀ ਵਰਤੋਂ ਕਰੋ</string>
|
||||
<string name="swipe_gestures_download_card">ਡਾਉਨਲੋਡ ਕਾਰਡ ਵਿੱਚ ਸਵਾਈਪ ਇਸ਼ਾਰੇ</string>
|
||||
<string name="thumbnail_format">ਥੰਮਨੇਲ ਫਾਰਮੈਟ</string>
|
||||
<string name="text_size">ਟੈਕਸਟ ਦਾ ਆਕਾਰ</string>
|
||||
|
|
|
|||
|
|
@ -354,8 +354,7 @@
|
|||
<string name="set_time">Ustaw czas</string>
|
||||
<string name="starts">Uruchom</string>
|
||||
<string name="retry_missing_downloads">Spróbuj pobrać ponownie</string>
|
||||
<string name="download_archive">Pobierz archiwum</string>
|
||||
<string name="download_archive_summary">Użyj pliku archiwum pobierania yt-dlp, aby zapobiec duplikatom pobierania zamiast wbudowanej funkcjonalności</string>
|
||||
<string name="archive">Pobierz archiwum [Użyj pliku archiwum pobierania yt-dlp, aby zapobiec duplikatom pobierania zamiast wbudowanej funkcjonalności]</string>
|
||||
<string name="month">Miesiąc</string>
|
||||
<string name="not_deleted">Nie usunięte</string>
|
||||
<string name="save_auto_subs">Zapisz automatyczne napisy</string>
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@
|
|||
<string name="month">Mês(es)</string>
|
||||
<string name="bitrate">Taxa de bits</string>
|
||||
<string name="not_deleted">Não excluído</string>
|
||||
<string name="download_archive">Arquivar downloads</string>
|
||||
<string name="archive">Arquivar downloads [Usar o arquivamento de downloads do yt-dlp para evitar downloads duplicados]</string>
|
||||
<string name="observe_sources">Observar Origens</string>
|
||||
<string name="delete_all_sources">Excluir todas as origens</string>
|
||||
<string name="confirm_delete_sources_desc">Excluir todas as origens permanentemente</string>
|
||||
|
|
@ -356,7 +356,6 @@
|
|||
<string name="occurrences">ocorrências</string>
|
||||
<string name="day">Dia(s)</string>
|
||||
<string name="save_auto_subs">Salvar legendas automáticas</string>
|
||||
<string name="download_archive_summary">Usar o arquivamento de downloads do yt-dlp para evitar downloads duplicados</string>
|
||||
<string name="retry_missing_downloads">Repetir downloads ausentes</string>
|
||||
<string name="force_ipv4_desc">Efetuar todas as conexões com IPv4</string>
|
||||
<string name="thumbnail_format">Formato da miniatura</string>
|
||||
|
|
|
|||
|
|
@ -357,8 +357,7 @@
|
|||
<string name="month">Mês</string>
|
||||
<string name="set_time">Definir tempo</string>
|
||||
<string name="on_time">Em</string>
|
||||
<string name="download_archive">Arquivo de descarga</string>
|
||||
<string name="download_archive_summary">Utilizar ficheiro de descarga yt-dlp para evitar duplicação de descargas</string>
|
||||
<string name="archive">Arquivo de descarga [Utilizar ficheiro de descarga yt-dlp para evitar duplicação de descargas]</string>
|
||||
<string name="swipe_gestures_download_card">Gestos de deslizar no cartão de descarga</string>
|
||||
<string name="show_download_count">Mostrar contagens no ecrã da Fila de descarga</string>
|
||||
<string name="get_new_uploads">Obter apenas novos envios</string>
|
||||
|
|
|
|||
|
|
@ -346,7 +346,6 @@
|
|||
<string name="on_time">Вкл</string>
|
||||
<string name="day">День</string>
|
||||
<string name="occurrences">случаев</string>
|
||||
<string name="download_archive_summary">Используйте архивный файл загрузок yt-dlp, чтобы предотвратить дублирование загрузок вместо встроенной функции</string>
|
||||
<string name="observe_sources">Источники загрузки</string>
|
||||
<string name="new_source">Новый источник</string>
|
||||
<string name="confirm_delete_sources_desc">Удалить все источники навсегда</string>
|
||||
|
|
@ -358,7 +357,7 @@
|
|||
<string name="bitrate">Битрейт</string>
|
||||
<string name="save_auto_subs">Сохранить автоматически субтитры</string>
|
||||
<string name="not_deleted">Не удалено</string>
|
||||
<string name="download_archive">Скачать архив</string>
|
||||
<string name="archive">Скачать архив [Используйте архивный файл загрузок yt-dlp, чтобы предотвратить дублирование загрузок вместо встроенной функции]</string>
|
||||
<string name="swipe_gestures_download_card">Жесты смахивания в карточке загрузок</string>
|
||||
<string name="show_download_count">Показывать кол-во контента на экране очереди загрузки</string>
|
||||
<string name="thumbnail_format">Формат миниатюр</string>
|
||||
|
|
|
|||
|
|
@ -345,8 +345,7 @@
|
|||
<string name="retry_missing_downloads">Opakovať zmeškané sťahovania</string>
|
||||
<string name="save_auto_subs">Uložiť automatické titulky</string>
|
||||
<string name="not_deleted">Nevymazané</string>
|
||||
<string name="download_archive">Stiahnuť archív</string>
|
||||
<string name="download_archive_summary">Použije archívny súbor yt-dlp namiesto vstavanej funkcie, aby sa zabránilo duplicitnému sťahovaniu</string>
|
||||
<string name="archive">Stiahnuť archív [Použije archívny súbor yt-dlp namiesto vstavanej funkcie, aby sa zabránilo duplicitnému sťahovaniu]</string>
|
||||
<string name="copy_urls">Kopírovať adresy URL</string>
|
||||
<string name="observe_sources">Sledovať zdroje</string>
|
||||
<string name="new_source">Nový zdroj</string>
|
||||
|
|
|
|||
|
|
@ -357,8 +357,7 @@
|
|||
<string name="ends">Mbaron</string>
|
||||
<string name="day">Ditë</string>
|
||||
<string name="month">Muaj</string>
|
||||
<string name="download_archive">Arkiva e Shkarkimeve</string>
|
||||
<string name="download_archive_summary">Përdor skedarin e arkivës të yt-dlp për të parandaluar shkarkimet duplicate në vend të funksionalitetit të aplikacionit</string>
|
||||
<string name="archive">Arkiva e Shkarkimeve [Përdor skedarin e arkivës të yt-dlp për të parandaluar shkarkimet duplicate]</string>
|
||||
<string name="swipe_gestures_download_card">Gjestet e rrëshqitjes në kartën e shkarkimit</string>
|
||||
<string name="show_download_count">Shfaq numrat në faqen e rradhës së shkarkimeve</string>
|
||||
<string name="also_download_audio">Shkarko Gjithashtu si Audio</string>
|
||||
|
|
|
|||
|
|
@ -357,8 +357,7 @@
|
|||
<string name="every">Сваки</string>
|
||||
<string name="after">Након</string>
|
||||
<string name="not_deleted">Није избрисано</string>
|
||||
<string name="download_archive_summary">Користите yt-dlp архиву преузимања да бисте спречили дупла преузимања, уместо уграђене функционалности</string>
|
||||
<string name="download_archive">Архива преузимања</string>
|
||||
<string name="archive">Архива преузимања [Користите yt-dlp архиву преузимања да бисте спречили дупла преузимања, уместо уграђене функционалности]</string>
|
||||
<string name="show_download_count">Прикажи бројеве на екрану редоследа за преузимање</string>
|
||||
<string name="get_new_uploads">Добијај само нова отпремања</string>
|
||||
<string name="also_download_audio">Такође преузми као аудио снимак</string>
|
||||
|
|
|
|||
|
|
@ -356,12 +356,11 @@
|
|||
<string name="bitrate">Bit hızı</string>
|
||||
<string name="save_auto_subs">Otomatik Altyazıları Kaydet</string>
|
||||
<string name="not_deleted">Silinmedi</string>
|
||||
<string name="download_archive_summary">Yerleşik işlevsellik yerine yinelenen indirmeleri önlemek için yt-dlp\'nin indirme arşiv dosyasını kullanın</string>
|
||||
<string name="every">Herhangi</string>
|
||||
<string name="never">Asla</string>
|
||||
<string name="on_time">Çalışmakta</string>
|
||||
<string name="day">Gün</string>
|
||||
<string name="download_archive">İndirme Arşivi</string>
|
||||
<string name="archive">İndirme Arşivi [Yerleşik işlevsellik yerine yinelenen indirmeleri önlemek için yt-dlp\'nin indirme arşiv dosyasını kullanın]</string>
|
||||
<string name="thumbnail_format">Küçük Resim Formatı</string>
|
||||
<string name="get_new_uploads">Yalnızca Yeni Yüklemeleri Alın</string>
|
||||
<string name="also_download_audio">Ayrıca Ses Olarak İndir</string>
|
||||
|
|
|
|||
|
|
@ -357,8 +357,7 @@
|
|||
<string name="month">Місяць</string>
|
||||
<string name="not_deleted">Не видалено</string>
|
||||
<string name="save_auto_subs">Зберегти автоматично субтитри</string>
|
||||
<string name="download_archive">Архів завантажень</string>
|
||||
<string name="download_archive_summary">Використовуйте файл архіву завантажень yt-dlp, щоб запобігти повторним завантаженням замість вбудованої функції</string>
|
||||
<string name="archive">Архів завантажень [Використовуйте файл архіву завантажень yt-dlp, щоб запобігти повторним завантаженням замість вбудованої функції]</string>
|
||||
<string name="swipe_gestures_download_card">Жести пальцем у картці завантаження</string>
|
||||
<string name="show_download_count">Показати підрахунки на екрані черги завантаження</string>
|
||||
<string name="thumbnail_format">Формат мініатюр</string>
|
||||
|
|
|
|||
|
|
@ -336,8 +336,7 @@
|
|||
<string name="force_ipv4_desc">Thực hiện tất cả các kết nối qua IPv4</string>
|
||||
<string name="copy_urls">Sao chép URL</string>
|
||||
<string name="save_auto_subs">Lưu phụ đề tự động</string>
|
||||
<string name="download_archive_summary">Sử dụng tải xuống tệp lưu trữ của yt-dlp để ngăn tải xuống trùng lặp thay vì chức năng tích hợp sẵn</string>
|
||||
<string name="download_archive">Tải xuống lưu trữ</string>
|
||||
<string name="archive">Tải xuống lưu trữ [Sử dụng tải xuống tệp lưu trữ của yt-dlp để ngăn tải xuống trùng lặp thay vì chức năng tích hợp sẵn]</string>
|
||||
<string name="dismiss">Bỏ qua</string>
|
||||
<string name="changed_path_for_everyone_to">Đã thay đổi đường dẫn tải xuống của mọi mục thành:</string>
|
||||
<string name="all">Tất cả</string>
|
||||
|
|
|
|||
|
|
@ -352,13 +352,12 @@
|
|||
<string name="ends">结束</string>
|
||||
<string name="after">后</string>
|
||||
<string name="day">天</string>
|
||||
<string name="download_archive_summary">使用 yt-dlp 的下载存档文件而非应用的内置功能来防止重复下载</string>
|
||||
<string name="month">月</string>
|
||||
<string name="retry_missing_downloads">重试缺少的下载</string>
|
||||
<string name="not_deleted">未删除</string>
|
||||
<string name="bitrate">比特率</string>
|
||||
<string name="save_auto_subs">保存自动字幕</string>
|
||||
<string name="download_archive">下载存档</string>
|
||||
<string name="archive">下载存档 [使用 yt-dlp 的下载存档文件而非应用的内置功能来防止重复下载]</string>
|
||||
<string name="also_download_audio">同样下载为音频</string>
|
||||
<string name="wrap_text">换行显示过长文本</string>
|
||||
<string name="text_size">文本尺寸</string>
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@
|
|||
<string name="never">從不</string>
|
||||
<string name="after">之後</string>
|
||||
<string name="occurrences">次數</string>
|
||||
<string name="download_archive">下載存檔</string>
|
||||
<string name="archive">下載存檔 [使用 yt-dlp 的下載存檔檔案以防止重複下載,而非使用內建功能]</string>
|
||||
<string name="not_deleted">未刪除</string>
|
||||
<string name="save_auto_subs">儲存自動字幕</string>
|
||||
<string name="day">天</string>
|
||||
|
|
@ -356,7 +356,6 @@
|
|||
<string name="month">月</string>
|
||||
<string name="retry_missing_downloads">重試遺漏的下載</string>
|
||||
<string name="bitrate">比特率</string>
|
||||
<string name="download_archive_summary">使用 yt-dlp 的下載存檔檔案以防止重複下載,而非使用內建功能</string>
|
||||
<string name="on_time">準</string>
|
||||
<string name="force_ipv4_desc">使所有連線均使用 IPv4</string>
|
||||
</resources>
|
||||
|
|
@ -25,14 +25,18 @@
|
|||
<item>@string/defaultValue</item>
|
||||
<item>mp4</item>
|
||||
<item>mkv</item>
|
||||
<item>webm</item>
|
||||
<item>avi</item>
|
||||
<item>flv</item>
|
||||
<item>mov</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_containers_values">
|
||||
<item>Default</item>
|
||||
<item>mp4</item>
|
||||
<item>mkv</item>
|
||||
<item>webm</item>
|
||||
<item>avi</item>
|
||||
<item>flv</item>
|
||||
<item>mov</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="video_formats">
|
||||
|
|
@ -116,6 +120,7 @@
|
|||
<item>Azərbaycanca</item>
|
||||
<item>বাংলা</item>
|
||||
<item>বাংলা (India)</item>
|
||||
<item>български</item>
|
||||
<item>Босански</item>
|
||||
<item>Deutsch</item>
|
||||
<item>Ἑλληνική</item>
|
||||
|
|
@ -159,6 +164,7 @@
|
|||
<item>az</item>
|
||||
<item>bn</item>
|
||||
<item>bn-IN</item>
|
||||
<item>bg</item>
|
||||
<item>bs</item>
|
||||
<item>de</item>
|
||||
<item>el</item>
|
||||
|
|
@ -1084,6 +1090,7 @@
|
|||
<item>@string/downloads</item>
|
||||
<item>@string/in_queue</item>
|
||||
<item>@string/cancelled</item>
|
||||
<item>@string/scheduled</item>
|
||||
<item>@string/errored</item>
|
||||
<item>@string/saved</item>
|
||||
<item>@string/command_templates</item>
|
||||
|
|
@ -1095,6 +1102,7 @@
|
|||
<item>history</item>
|
||||
<item>queued</item>
|
||||
<item>cancelled</item>
|
||||
<item>scheduled</item>
|
||||
<item>errored</item>
|
||||
<item>saved</item>
|
||||
<item>templates</item>
|
||||
|
|
@ -1396,4 +1404,18 @@
|
|||
<item>jpg</item>
|
||||
</array>
|
||||
|
||||
<string-array name="prevent_duplicate_downloads">
|
||||
<item>@string/disabled</item>
|
||||
<item>@string/archive</item>
|
||||
<item>@string/prevent_duplicate_url_type</item>
|
||||
<item>@string/prevent_duplicate_config</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="prevent_duplicate_downloads_values">
|
||||
<item></item>
|
||||
<item>download_archive</item>
|
||||
<item>url_type</item>
|
||||
<item>config</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,2 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
<resources>
|
||||
<dimen name="elevation_6dp">6dp</dimen>
|
||||
<dimen name="elevation_2dp">2dp</dimen>
|
||||
</resources>
|
||||
|
|
@ -360,8 +360,8 @@
|
|||
<string name="bitrate">Bitrate</string>
|
||||
<string name="save_auto_subs">Save Automatic Subtitles</string>
|
||||
<string name="not_deleted">Not Deleted</string>
|
||||
<string name="download_archive">Download Archive</string>
|
||||
<string name="download_archive_summary">Use yt-dlp\'s download archive file to prevent duplicate downloads instead of the built in functionality</string>
|
||||
<string name="archive">Download Archive [Use yt-dlp\'s download archive file to prevent duplicate downloads instead of the built in functionality]</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="swipe_gestures_download_card">Swipe Gestures in download card</string>
|
||||
<string name="show_download_count">Show counts in the download queue screen</string>
|
||||
<string name="thumbnail_format">Thumbnail Format</string>
|
||||
|
|
@ -382,4 +382,13 @@
|
|||
<string name="display_over_apps">Display Over Apps</string>
|
||||
<string name="display_over_apps_summary">Make the download card always display on top. Turn this on if the currently running app closes when the download card shows up</string>
|
||||
<string name="hour">Hour</string>
|
||||
<string name="subtitles">Subtitles</string>
|
||||
<string name="prevent_duplicate_downloads">Prevent Duplicate Downloads</string>
|
||||
<string name="prevent_duplicate_url_type"><![CDATA[URL & Download Type]]></string>
|
||||
<string name="prevent_duplicate_config">Full Configuration</string>
|
||||
<string name="copy_url">Copy URL</string>
|
||||
<string name="move_top">Move to the Top</string>
|
||||
<string name="move_bottom">Move to the Bottom</string>
|
||||
<string name="scheduled">Scheduled</string>
|
||||
<string name="clear_scheduled">Clear Scheduled</string>
|
||||
</resources>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<shortcut
|
||||
android:shortcutId="search"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:icon="@drawable/ic_search_red"
|
||||
android:shortcutShortLabel="@string/search"
|
||||
android:shortcutDisabledMessage="@string/search">
|
||||
<intent
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
<shortcut
|
||||
android:shortcutId="downloads"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_downloads"
|
||||
android:icon="@drawable/ic_downloads_red"
|
||||
android:shortcutShortLabel="@string/downloads"
|
||||
android:shortcutDisabledMessage="@string/downloads">
|
||||
<intent
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
<shortcut
|
||||
android:shortcutId="queue"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/baseline_downloading_24"
|
||||
android:icon="@drawable/baseline_downloading_red"
|
||||
android:shortcutShortLabel="@string/download_queue"
|
||||
android:shortcutDisabledMessage="@string/download_queue">
|
||||
<intent
|
||||
|
|
@ -51,7 +51,7 @@
|
|||
<shortcut
|
||||
android:shortcutId="newTemplate"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/baseline_downloading_24"
|
||||
android:icon="@drawable/ic_terminal_red"
|
||||
android:shortcutShortLabel="@string/new_template"
|
||||
android:shortcutDisabledMessage="@string/new_template">
|
||||
<intent
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!--
|
||||
TODO: Use <include> and <exclude> to control what is backed up.
|
||||
Use <include> and <exclude> to control what is backed up.
|
||||
The domain can be file, database, sharedpref, external or root.
|
||||
Examples:
|
||||
|
||||
|
|
|
|||
|
|
@ -27,14 +27,15 @@
|
|||
android:key="quick_download"
|
||||
app:summary="@string/quick_download_summary"
|
||||
app:title="@string/quick_download" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue=""
|
||||
android:entries="@array/prevent_duplicate_downloads"
|
||||
android:entryValues="@array/prevent_duplicate_downloads_values"
|
||||
android:icon="@drawable/baseline_archive_24"
|
||||
android:key="download_archive"
|
||||
app:summary="@string/download_archive_summary"
|
||||
app:title="@string/download_archive" />
|
||||
app:key="prevent_duplicate_downloads"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/prevent_duplicate_downloads" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/baseline_invert_colors_24"
|
||||
android:icon="@drawable/baseline_layers_24"
|
||||
android:key="display_over_apps"
|
||||
app:summary="@string/display_over_apps_summary"
|
||||
app:title="@string/display_over_apps" />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<locale android:name="az"/>
|
||||
<locale android:name="bn"/>
|
||||
<locale android:name="bn-IN"/>
|
||||
<locale android:name="bg"/>
|
||||
<locale android:name="bs"/>
|
||||
<locale android:name="de"/>
|
||||
<locale android:name="el"/>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
android:defaultValue="ALL"
|
||||
android:entries="@array/format_filtering"
|
||||
android:entryValues="@array/format_filtering_values"
|
||||
android:icon="@drawable/baseline_align_horizontal_left_24"
|
||||
android:icon="@drawable/baseline_filter_alt_24"
|
||||
app:key="format_filter"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_filter" />
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ buildscript {
|
|||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '8.1.1' apply false
|
||||
id 'com.android.library' version '8.1.1' apply false
|
||||
id 'com.android.application' version '8.3.1' apply false
|
||||
id 'com.android.library' version '8.3.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10" apply false
|
||||
id "org.jetbrains.kotlin.plugin.parcelize" version "1.8.10" apply false
|
||||
id 'com.android.test' version '8.1.1' apply false
|
||||
id 'com.android.test' version '8.3.1' apply false
|
||||
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
|
||||
}
|
||||
|
||||
|
|
|
|||
54
fastlane/metadata/android/en-US/changelogs/10750.txt
Normal file
54
fastlane/metadata/android/en-US/changelogs/10750.txt
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
## What's New
|
||||
|
||||
- Added AVI / FLV / MOV video container options
|
||||
- Fixed app not showing the current download's command if it was a command type download when coming from multiple download sheet
|
||||
- Added release year from description as metadata for audio downloads
|
||||
- Set progress bar as interterminate when it reached 100% so people wont think it froze (its ffmpeg working, and it doesnt have a progress callback)
|
||||
- Fix app crashing when trying to toggle on Show Terminal in share sheet
|
||||
- Fix app crashing when going on landscape when you open the format list
|
||||
- Fixed app not disabling the keep fragments toggle if you toggled the dont download as fragments
|
||||
- Fixed app not fetching format list when some formats had None as a filesize
|
||||
- Fixed app only showing the download type icon in the finished notification only when you expanded it. Now its a popping orange
|
||||
- Showed navigation bar color when opening the download card
|
||||
- Showed stylized codec name for generic audio formats
|
||||
- Fixed app crashing when going on landscape when you open the multiple download card
|
||||
- Fixed app crashing when you tapped the home button twice on foldable mode
|
||||
- Fixed observe sources spamming and running every second
|
||||
- Swapped around some icons
|
||||
- Added new Language BULGARIAN
|
||||
- Showed the download path in the finished notification
|
||||
- Fixed calculating the next time observe sources should run
|
||||
- Added a scheduled section in the download queue so that they dont stay in the same spot with queued items that are expected to run soon. You can see their ETA there for each item
|
||||
|
||||
|
||||
## Duplicate checking
|
||||
|
||||
For a while the app had its own duplicate checking system when it checked the whole configuration with current history items or active downloads.
|
||||
Since this system was too precise and even a slight change in options will consider it a new download and not an exact replica and most people were confused why the app allowed them to still download
|
||||
So i created 3 methods of duplicate checking
|
||||
|
||||
- download archive -> uses the .txt file of yt-dlp to check if any url has been downloaded before or not
|
||||
- url and type -> checks download history and running downloads to check if any item with the same url and type was downloaded
|
||||
- full config -> the good ol method
|
||||
|
||||
----
|
||||
|
||||
- Removed paused button for each active download item. It didnt make sense. If you paused one item, the other items will continue to run anyway so what was the point. Instead i added a floating action button to pause and resume the whole queue
|
||||
- Removed the cookie toggle throbbing every time you entered the page
|
||||
- Slight changes to album_arist metadata parsing
|
||||
- Fixed app downloading music file instead when using M4A music format
|
||||
- Fixed app showing the grey deleted filter on present items
|
||||
|
||||
|
||||
## Reordering download queue items
|
||||
|
||||
Now you can toggle the drag and drop mode in the queued tab to reorder your items with ease. Also you can now move multiple items to the top and to the bottom of the queue. or a single item
|
||||
|
||||
-------
|
||||
|
||||
- removed the plus icon in the piped instance dialog.
|
||||
- combined the thumbnail cropper and resizer commands in audio downloads
|
||||
- fixed app not removing audio on some pre-merged formats like in tiktok or instagram
|
||||
- removed the -f bv[height<=...] and instead moved to -S res:... due to some problems when trying to quick download an item
|
||||
- fix terminal sometimes not showing the finishing line of output or error
|
||||
- added colors to app shortcut icons
|
||||
Loading…
Reference in a new issue