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