1.7.9.3 pre
This commit is contained in:
parent
3307118d7f
commit
56b503a46b
49 changed files with 1016 additions and 553 deletions
|
|
@ -1,11 +1,13 @@
|
|||
# YTDLnis Changelog
|
||||
|
||||
> # 1.7.9.2
|
||||
> # 1.7.9.2 (2024-09)
|
||||
|
||||
# What's Changed
|
||||
|
||||
- newpipe extractor general fixes
|
||||
- fixed newpipe formats not working
|
||||
- removed piped because its more annoying than anything, it doesnt work currently
|
||||
*shortest changelog wr*
|
||||
|
||||
|
||||
> # 1.7.9.1 (2024-09)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if (isBeta) {
|
|||
|
||||
android {
|
||||
namespace 'com.deniscerri.ytdl'
|
||||
compileSdk 34
|
||||
compileSdk 35
|
||||
|
||||
try {
|
||||
def propertiesFile = rootProject.file("keystore.properties")
|
||||
|
|
@ -44,8 +44,8 @@ android {
|
|||
}catch(IOException ignored){}
|
||||
defaultConfig {
|
||||
applicationId "com.deniscerri.ytdl"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
minSdk 24
|
||||
targetSdk 35
|
||||
versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${versionExt}"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
@ -135,14 +135,15 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs_nio:2.1.0"
|
||||
implementation "com.github.yausername.youtubedl-android:library:$youtubedlAndroidVer"
|
||||
implementation "com.github.yausername.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
|
||||
implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs_nio:2.1.2"
|
||||
// implementation "com.github.yausername.youtubedl-android:library:$youtubedlAndroidVer"
|
||||
// implementation "com.github.yausername.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
|
||||
// implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer"
|
||||
|
||||
// implementation "io.github.junkfood02.youtubedl-android:library:0.16.1"
|
||||
// implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.16.1"
|
||||
// implementation "io.github.junkfood02.youtubedl-android:aria2c:0.16.1"
|
||||
|
||||
implementation "io.github.junkfood02.youtubedl-android:library:0.17.1"
|
||||
implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.17.1"
|
||||
implementation "io.github.junkfood02.youtubedl-android:aria2c:0.17.1"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appCompatVer"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
|
|
@ -154,7 +155,7 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.2.1'
|
||||
implementation 'androidx.compose.ui:ui-android:1.6.8'
|
||||
implementation 'androidx.compose.ui:ui-android:1.7.4'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
testImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "junit:junit:$junitVer"
|
||||
|
|
@ -176,10 +177,10 @@ dependencies {
|
|||
implementation "androidx.room:room-runtime:$roomVer"
|
||||
implementation "androidx.room:room-ktx:$roomVer"
|
||||
ksp "androidx.room:room-compiler:$roomVer"
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.3.0'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:3.3.2'
|
||||
implementation "androidx.room:room-paging:$roomVer"
|
||||
androidTestImplementation "androidx.room:room-testing:$roomVer"
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.6'
|
||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
||||
androidTestImplementation 'com.google.truth:truth:1.1.5'
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ dependencies {
|
|||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.4"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.6"
|
||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||
|
||||
// For media playback using ExoPlayer
|
||||
|
|
@ -207,8 +208,8 @@ dependencies {
|
|||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.1'
|
||||
implementation 'androidx.compose.material3:material3-android:1.3.0'
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
implementation("org.greenrobot:eventbus:3.3.1")
|
||||
implementation("com.github.teamnewpipe:NewPipeExtractor:0.24.2")
|
||||
implementation("com.github.teamnewpipe:newpipeextractor:v0.24.2")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.widget.CheckBox
|
||||
|
|
@ -51,6 +52,7 @@ import com.deniscerri.ytdl.ui.downloads.DownloadQueueMainFragment
|
|||
import com.deniscerri.ytdl.ui.downloads.HistoryFragment
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
||||
import com.deniscerri.ytdl.util.CrashListener
|
||||
import com.deniscerri.ytdl.util.Extensions.extractURL
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.NavbarUtil.applyNavBarStyle
|
||||
|
|
@ -183,7 +185,7 @@ class MainActivity : BaseActivity() {
|
|||
(navigationView as NavigationBarView).getOrCreateBadge(R.id.historyFragment)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||
downloadViewModel.activePausedDownloadsCount.collectLatest {
|
||||
if (it == 0) {
|
||||
activeDownloadsBadge.isVisible = false
|
||||
activeDownloadsBadge.clearNumber()
|
||||
|
|
@ -438,7 +440,7 @@ class MainActivity : BaseActivity() {
|
|||
textBuilder.append(c.toChar())
|
||||
}
|
||||
val bundle = Bundle()
|
||||
bundle.putString("url", textBuilder.toString())
|
||||
bundle.putString("url", textBuilder.toString().extractURL())
|
||||
navController.popBackStack(R.id.homeFragment, true)
|
||||
navController.navigate(
|
||||
R.id.homeFragment,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status = 'Paused' ORDER BY CASE WHEN status = 'Active' THEN 0 ELSE 1 END")
|
||||
fun getActiveAndPausedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Paused'")
|
||||
fun getPausedDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Paused'")
|
||||
fun getPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
fun getProcessingDownloads() : Flow<List<DownloadItemConfigureMultiple>>
|
||||
|
|
@ -79,10 +88,11 @@ interface DownloadDao {
|
|||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued', 'Scheduled')")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
@Query("UPDATE downloads SET status='Queued' where status = 'Active'")
|
||||
suspend fun resetActiveToQueued()
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued')")
|
||||
@Query("UPDATE downloads SET status='Queued', downloadStartTime = -1 where status in ('Paused')")
|
||||
suspend fun resetPausedToQueued()
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued', 'Paused')")
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued')")
|
||||
|
|
@ -95,7 +105,11 @@ interface DownloadDao {
|
|||
@Query("SELECT format FROM downloads WHERE status='Queued'")
|
||||
fun getSelectedFormatFromQueued() : List<Format>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE downloadStartTime <= :currentTime and status in ('Queued', 'Scheduled') ORDER BY downloadStartTime, id LIMIT 20")
|
||||
@Query("""
|
||||
SELECT * FROM downloads
|
||||
WHERE downloadStartTime <= :currentTime and status in ('Queued', 'Scheduled')
|
||||
ORDER BY downloadStartTime, id
|
||||
""")
|
||||
fun getQueuedScheduledDownloadsUntil(currentTime: Long) : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
|
||||
|
|
@ -187,7 +201,7 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE id in (:list)")
|
||||
suspend fun deleteAllWithIDs(list: List<Long>)
|
||||
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status in('Queued','Active', 'Scheduled')")
|
||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status in('Queued','Active', 'Scheduled', 'Paused')")
|
||||
suspend fun cancelActiveQueued()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||
|
|
@ -200,7 +214,11 @@ interface DownloadDao {
|
|||
suspend fun updateAll(list: List<DownloadItem>) : List<DownloadItem> {
|
||||
val toReturn = mutableListOf<DownloadItem>()
|
||||
list.forEach {
|
||||
it.id = update(it)
|
||||
if (it.id > 0) {
|
||||
update(it)
|
||||
}else{
|
||||
it.id = insert(it)
|
||||
}
|
||||
toReturn.add(it)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,23 +11,23 @@ import kotlinx.coroutines.flow.Flow
|
|||
@Dao
|
||||
interface HistoryDao {
|
||||
|
||||
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:format||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
|
||||
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
|
||||
"CASE WHEN :sort = 'ASC' THEN id END ASC," +
|
||||
"CASE WHEN :sort = 'DESC' THEN id END DESC," +
|
||||
"CASE WHEN :sort = '' THEN id END DESC ")
|
||||
fun getHistorySortedByID(query : String, format : String, site : String, sort : String) : List<HistoryItem>
|
||||
fun getHistorySortedByID(query : String, type : String, site : String, sort : String) : List<HistoryItem>
|
||||
|
||||
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:format||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
|
||||
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
|
||||
"CASE WHEN :sort = 'ASC' THEN title END ASC," +
|
||||
"CASE WHEN :sort = 'DESC' THEN title END DESC," +
|
||||
"CASE WHEN :sort = '' THEN title END DESC ")
|
||||
fun getHistorySortedByTitle(query : String, format : String, site : String, sort : String) : List<HistoryItem>
|
||||
fun getHistorySortedByTitle(query : String, type : String, site : String, sort : String) : List<HistoryItem>
|
||||
|
||||
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:format||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
|
||||
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
|
||||
"CASE WHEN :sort = 'ASC' THEN author END ASC," +
|
||||
"CASE WHEN :sort = 'DESC' THEN author END DESC," +
|
||||
"CASE WHEN :sort = '' THEN author END DESC ")
|
||||
fun getHistorySortedByAuthor(query : String, format : String, site : String, sort : String) : List<HistoryItem>
|
||||
fun getHistorySortedByAuthor(query : String, type : String, site : String, sort : String) : List<HistoryItem>
|
||||
|
||||
|
||||
@Query("SELECT * FROM history")
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
pagingSourceFactory = {downloadDao.getAllDownloads()}
|
||||
)
|
||||
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads().distinctUntilChanged()
|
||||
val activePausedDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveAndPausedDownloads().distinctUntilChanged()
|
||||
val pausedDownloads : Flow<List<DownloadItem>> = downloadDao.getPausedDownloads().distinctUntilChanged()
|
||||
val processingDownloads : Flow<List<DownloadItemConfigureMultiple>> = downloadDao.getProcessingDownloads().distinctUntilChanged()
|
||||
val queuedDownloads : Pager<Int, DownloadItemSimple> = Pager(
|
||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||
|
|
@ -67,15 +69,16 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
)
|
||||
|
||||
val activeDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active).toListString())
|
||||
val activePausedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.Paused).toListString())
|
||||
val queuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Queued).toListString())
|
||||
val activeQueuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.Queued).toListString())
|
||||
val pausedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Paused).toListString())
|
||||
val cancelledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Cancelled).toListString())
|
||||
val erroredDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Error).toListString())
|
||||
val savedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Saved).toListString())
|
||||
val scheduledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Scheduled).toListString())
|
||||
|
||||
enum class Status {
|
||||
Active, Queued, Error, Cancelled, Saved, Processing, Scheduled, Duplicate
|
||||
Active, Paused, Queued, Error, Cancelled, Saved, Processing, Scheduled, Duplicate
|
||||
}
|
||||
|
||||
suspend fun insert(item: DownloadItem) : Long {
|
||||
|
|
@ -222,48 +225,47 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context, inputData: Data.Builder = Data.Builder()) : Result<String> {
|
||||
fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context, continueAfterPriorityItems: Boolean = true) : Result<String> {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
|
||||
val currentWork = workManager.getWorkInfosByTag("download").await()
|
||||
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
var delay = 0L
|
||||
if (queuedItems.isNotEmpty()){
|
||||
val earliestStart = queuedItems.minBy { it.downloadStartTime }
|
||||
delay = if (earliestStart.downloadStartTime != 0L){
|
||||
earliestStart.downloadStartTime.minus(currentTime)
|
||||
} else 0
|
||||
if (delay <= 60000L) delay = 0L
|
||||
}
|
||||
|
||||
val useAlarmForScheduling = sharedPreferences.getBoolean("use_alarm_for_scheduling", false)
|
||||
|
||||
if (delay > 0L && useAlarmForScheduling) {
|
||||
AlarmScheduler(context).scheduleAt(queuedItems.minBy { it.downloadStartTime }.downloadStartTime)
|
||||
return Result.success("")
|
||||
}
|
||||
|
||||
|
||||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.addTag("download")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
.setInputData(inputData.build())
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
|
||||
val inputData = Data.Builder()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
var delay = 0L
|
||||
if (queuedItems.isNotEmpty()){
|
||||
val earliestStart = queuedItems.minBy { it.downloadStartTime }
|
||||
delay = if (earliestStart.downloadStartTime != 0L){
|
||||
earliestStart.downloadStartTime.minus(currentTime)
|
||||
} else 0
|
||||
if (delay <= 60000L) delay = 0L
|
||||
inputData.putLongArray("priority_item_ids", queuedItems.map { it.id }.toLongArray())
|
||||
}
|
||||
|
||||
val useAlarmForScheduling = sharedPreferences.getBoolean("use_alarm_for_scheduling", false)
|
||||
if (delay > 0L && useAlarmForScheduling) {
|
||||
AlarmScheduler(context).scheduleAt(queuedItems.minBy { it.downloadStartTime }.downloadStartTime)
|
||||
return Result.success("")
|
||||
}
|
||||
|
||||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
||||
inputData.putBoolean("continue_after_priority_ids", continueAfterPriorityItems)
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
.addTag("download")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
.setInputData(inputData.build())
|
||||
|
||||
workManager.enqueueUniqueWork(
|
||||
System.currentTimeMillis().toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest.build()
|
||||
)
|
||||
|
||||
|
||||
val message = StringBuilder()
|
||||
|
||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdl.database.repository
|
|||
import com.deniscerri.ytdl.database.DBManager.SORTING
|
||||
import com.deniscerri.ytdl.database.dao.HistoryDao
|
||||
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
|
@ -26,41 +27,28 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
|||
return historyDao.getAllHistoryByURL(url)
|
||||
}
|
||||
|
||||
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) : List<HistoryItem> {
|
||||
fun getFiltered(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryViewModel.HistoryStatus) : List<HistoryItem> {
|
||||
var filtered = when(sortType){
|
||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
||||
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, format, site, sort.toString())
|
||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, type, site, sort.toString())
|
||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, type, site, sort.toString())
|
||||
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, type, site, sort.toString())
|
||||
HistorySortType.FILESIZE -> {
|
||||
val items = historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
val items = historyDao.getHistorySortedByID(query, type, site, sort.toString())
|
||||
when(sort){
|
||||
SORTING.DESC -> items.sortedByDescending { it.format.filesize }
|
||||
SORTING.ASC -> items.sortedBy { it.format.filesize }
|
||||
}
|
||||
}
|
||||
}
|
||||
if(notDeleted){
|
||||
filtered = filtered.filter { it.downloadPath.any { it2 -> FileUtil.exists(it2) } }
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
fun getRecordsBetweenTwoItems(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) : List<HistoryItem> {
|
||||
var filtered = when(sortType){
|
||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
||||
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, format, site, sort.toString())
|
||||
HistorySortType.FILESIZE -> {
|
||||
val items = historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
when(sort){
|
||||
SORTING.DESC -> items.sortedByDescending { it.format.filesize }
|
||||
SORTING.ASC -> items.sortedBy { it.format.filesize }
|
||||
}
|
||||
when(statusFilter) {
|
||||
HistoryViewModel.HistoryStatus.DELETED -> {
|
||||
filtered = filtered.filter { it.downloadPath.any { it2 -> !FileUtil.exists(it2) } }
|
||||
}
|
||||
}
|
||||
if(notDeleted){
|
||||
filtered = filtered.filter { it.downloadPath.any { it2 -> FileUtil.exists(it2) } }
|
||||
HistoryViewModel.HistoryStatus.NOT_DELETED -> {
|
||||
filtered = filtered.filter { it.downloadPath.any { it2 -> FileUtil.exists(it2) } }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return filtered
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ import com.deniscerri.ytdl.database.repository.HistoryRepository.HistorySortType
|
|||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeChannelURL
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.extractors.GoogleApiUtil
|
||||
import com.deniscerri.ytdl.util.extractors.PipedApiUtil
|
||||
import com.deniscerri.ytdl.util.extractors.newpipe.NewPipeUtil
|
||||
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
||||
import com.deniscerri.ytdl.util.extractors.YoutubeApiUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDLException
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -46,6 +48,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
|
||||
enum class SourceType {
|
||||
YOUTUBE_VIDEO,
|
||||
YOUTUBE_WATCHVIDEOS,
|
||||
YOUTUBE_PLAYLIST,
|
||||
YOUTUBE_CHANNEL,
|
||||
SEARCH_QUERY,
|
||||
|
|
@ -111,6 +114,40 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
return items
|
||||
}
|
||||
|
||||
private suspend fun getYoutubeWatchVideos(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : List<ResultItem> {
|
||||
if (resetResults) deleteAll()
|
||||
|
||||
//throw YoutubeDLException("Youtube Watch Videos is not yet supported in data fetching. You can download it directly by clicking Continue Anyway or by Quick Downloading it!")
|
||||
//TODO use below code after youtubedl-android has fixed issue #295 in their repo
|
||||
val items = mutableListOf<ResultItem>()
|
||||
val ytExtractorResult = newPipeUtil?.getPlaylistData(inputQuery) {
|
||||
if (addToResults){
|
||||
runBlocking {
|
||||
val ids = resultDao.insertMultiple(it)
|
||||
ids.forEachIndexed { index, id ->
|
||||
it[index].id = id
|
||||
}
|
||||
}
|
||||
}
|
||||
items.addAll(it)
|
||||
}
|
||||
val response = if (ytExtractorResult?.isSuccess == true){
|
||||
ytExtractorResult.getOrElse { items }
|
||||
}else{
|
||||
val res = ytdlpUtil.getFromYTDL(inputQuery)
|
||||
if (addToResults) {
|
||||
val ids = resultDao.insertMultiple(res)
|
||||
ids.forEachIndexed { index, id ->
|
||||
res[index].id = id
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
itemCount.value = response.size
|
||||
return response
|
||||
}
|
||||
|
||||
private suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : List<ResultItem>{
|
||||
val theURL = inputQuery.replace("\\?list.*".toRegex(), "")
|
||||
val ytExtractorRes = newPipeUtil?.getVideoData(theURL)
|
||||
|
|
@ -157,9 +194,11 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
ytExtractorResult.getOrElse { items }
|
||||
}else{
|
||||
val res = ytdlpUtil.getFromYTDL(playlistURL)
|
||||
val ids = resultDao.insertMultiple(res)
|
||||
ids.forEachIndexed { index, id ->
|
||||
res[index].id = id
|
||||
if (addToResults) {
|
||||
val ids = resultDao.insertMultiple(res)
|
||||
ids.forEachIndexed { index, id ->
|
||||
res[index].id = id
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
|
@ -313,6 +352,9 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
SourceType.YOUTUBE_VIDEO -> {
|
||||
getYoutubeVideo(inputQuery, resetResults, addToResults)
|
||||
}
|
||||
SourceType.YOUTUBE_WATCHVIDEOS -> {
|
||||
getYoutubeWatchVideos(inputQuery, resetResults, addToResults)
|
||||
}
|
||||
SourceType.YOUTUBE_PLAYLIST -> {
|
||||
if (singleItem){
|
||||
getFromYTDLP(inputQuery, resetResults, addToResults, true)
|
||||
|
|
@ -345,6 +387,8 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
type = SourceType.YOUTUBE_PLAYLIST
|
||||
}else if (inputQuery.isYoutubeChannelURL()) {
|
||||
type = SourceType.YOUTUBE_CHANNEL
|
||||
}else if (inputQuery.isYoutubeWatchVideosURL()) {
|
||||
type = SourceType.YOUTUBE_WATCHVIDEOS
|
||||
}
|
||||
} else if (Patterns.WEB_URL.matcher(inputQuery).matches()) {
|
||||
type = SourceType.YT_DLP
|
||||
|
|
|
|||
|
|
@ -9,9 +9,13 @@ import android.os.Parcelable
|
|||
import android.util.DisplayMetrics
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.filter
|
||||
import androidx.paging.map
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
|
|
@ -38,16 +42,23 @@ import com.deniscerri.ytdl.ui.downloadcard.MultipleItemFormatTuple
|
|||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.FormatUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.extractors.YTDLPUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.deniscerri.ytdl.work.UpdateMultipleDownloadsFormatsWorker
|
||||
import com.google.gson.Gson
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -62,12 +73,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
private val sharedPreferences: SharedPreferences
|
||||
private val commandTemplateDao: CommandTemplateDao
|
||||
private val formatUtil = FormatUtil(application)
|
||||
private val notificationUtil = NotificationUtil(application)
|
||||
private val ytdlpUtil = YTDLPUtil(application)
|
||||
private val resources : Resources
|
||||
|
||||
val allDownloads : Flow<PagingData<DownloadItem>>
|
||||
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val activeDownloads : Flow<List<DownloadItem>>
|
||||
val activePausedDownloads : Flow<List<DownloadItem>>
|
||||
val processingDownloads : Flow<List<DownloadItemConfigureMultiple>>
|
||||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
|
|
@ -75,13 +88,21 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val scheduledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
|
||||
val activeDownloadsCount : Flow<Int>
|
||||
val activePausedDownloadsCount : Flow<Int>
|
||||
val queuedDownloadsCount : Flow<Int>
|
||||
val activeQueuedDownloadsCount : Flow<Int>
|
||||
val pausedDownloadsCount : Flow<Int>
|
||||
val cancelledDownloadsCount : Flow<Int>
|
||||
val erroredDownloadsCount : Flow<Int>
|
||||
val savedDownloadsCount : Flow<Int>
|
||||
val scheduledDownloadsCount : Flow<Int>
|
||||
|
||||
val pausedAllDownloads = MediatorLiveData(PausedAllDownloadsState.HIDDEN)
|
||||
private val pausedAllDownloadsFlow : Flow<PausedAllDownloadsState>
|
||||
private var isPausingResuming = false
|
||||
enum class PausedAllDownloadsState {
|
||||
PAUSE, RESUME, PROCESSING, HIDDEN
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class AlreadyExistsIDs(
|
||||
var downloadItemID: Long,
|
||||
|
|
@ -122,8 +143,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||
|
||||
activeDownloadsCount = repository.activeDownloadsCount
|
||||
activePausedDownloadsCount = repository.activePausedDownloadsCount
|
||||
queuedDownloadsCount = repository.queuedDownloadsCount
|
||||
activeQueuedDownloadsCount = repository.activeQueuedDownloadsCount
|
||||
pausedDownloadsCount = repository.pausedDownloadsCount
|
||||
cancelledDownloadsCount = repository.cancelledDownloadsCount
|
||||
erroredDownloadsCount = repository.erroredDownloadsCount
|
||||
savedDownloadsCount = repository.savedDownloadsCount
|
||||
|
|
@ -132,6 +154,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
allDownloads = repository.allDownloads.flow
|
||||
queuedDownloads = repository.queuedDownloads.flow
|
||||
activeDownloads = repository.activeDownloads
|
||||
activePausedDownloads = repository.activePausedDownloads
|
||||
processingDownloads = repository.processingDownloads
|
||||
savedDownloads = repository.savedDownloads.flow
|
||||
scheduledDownloads = repository.scheduledDownloads.flow
|
||||
|
|
@ -144,6 +167,25 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
}
|
||||
|
||||
pausedAllDownloadsFlow = combine(activeDownloadsCount, queuedDownloadsCount, pausedDownloadsCount) { active, queued, paused ->
|
||||
if (isPausingResuming) {
|
||||
return@combine PausedAllDownloadsState.PROCESSING
|
||||
}
|
||||
|
||||
if (active == 0 && queued == 0 && paused == 0) {
|
||||
return@combine PausedAllDownloadsState.HIDDEN
|
||||
}else if (paused > 1 || (active == 0 && queued > 0) || (paused > 0 && active > 0)) {
|
||||
return@combine PausedAllDownloadsState.RESUME
|
||||
}else if (active > 1) {
|
||||
return@combine PausedAllDownloadsState.PAUSE
|
||||
}else{
|
||||
return@combine PausedAllDownloadsState.HIDDEN
|
||||
}
|
||||
}
|
||||
|
||||
pausedAllDownloads.addSource(pausedAllDownloadsFlow.asLiveData()) {
|
||||
pausedAllDownloads.value = it
|
||||
}
|
||||
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
val locale = if (Build.VERSION.SDK_INT < 33) {
|
||||
|
|
@ -496,13 +538,20 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
)
|
||||
}
|
||||
else -> {
|
||||
val lastUsedCommandTemplate = sharedPreferences.getString("lastCommandTemplateUsed", "")!!
|
||||
val c = if (lastUsedCommandTemplate.isBlank()){
|
||||
commandTemplateDao.getFirst() ?: CommandTemplate(0,"","", useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false)
|
||||
}else{
|
||||
commandTemplateDao.getTemplateByContent(lastUsedCommandTemplate) ?: CommandTemplate(0, "", lastUsedCommandTemplate, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false)
|
||||
val preferredTemplateContent =
|
||||
sharedPreferences.getString("preferred_command_template", "")!!.ifEmpty {
|
||||
sharedPreferences.getString("lastCommandTemplateUsed", "")!!
|
||||
}
|
||||
|
||||
val c = if (preferredTemplateContent.isNotBlank()) {
|
||||
commandTemplateDao.getTemplateByContent(preferredTemplateContent)
|
||||
}else {
|
||||
commandTemplateDao.getFirst()
|
||||
}
|
||||
return generateCommandFormat(c)
|
||||
|
||||
return generateCommandFormat(
|
||||
c ?: CommandTemplate(0, "", preferredTemplateContent, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -656,7 +705,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.deleteAllWithIDs(ids)
|
||||
}
|
||||
|
||||
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||
private fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||
processingItemsJob?.apply { cancel(CancellationException()) }
|
||||
repository.cancelActiveQueued()
|
||||
}
|
||||
|
|
@ -705,14 +754,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun resetActiveToQueued() {
|
||||
dbManager.downloadDao.resetActiveToQueued()
|
||||
}
|
||||
|
||||
suspend fun startDownloadWorker(list: List<DownloadItem>){
|
||||
repository.startDownloadWorker(list, application)
|
||||
}
|
||||
|
||||
suspend fun putAtTopOfQueue(ids: List<Long>) = CoroutineScope(Dispatchers.IO).launch{
|
||||
val downloads = dao.getQueuedDownloadsListIDs()
|
||||
val lastID = ids.maxOf { it }
|
||||
|
|
@ -809,11 +850,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
items.forEach {
|
||||
if (it.downloadStartTime > 0) {
|
||||
it.status = DownloadRepository.Status.Scheduled.toString()
|
||||
}else{
|
||||
}else {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
}
|
||||
|
||||
|
||||
//CHECK DUPLICATES
|
||||
var isDuplicate = false
|
||||
if (checkDuplicate.isNotEmpty() && !ignoreDuplicates){
|
||||
|
|
@ -939,11 +979,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
}else{
|
||||
val queued = repository.updateAll(queuedItems)
|
||||
|
||||
if (sharedPreferences.getBoolean("paused_downloads", true)) {
|
||||
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
resetActiveToQueued()
|
||||
}
|
||||
println(queued.size)
|
||||
|
||||
result.message = repository.startDownloadWorker(queued, context).getOrElse { "" }
|
||||
|
||||
|
|
@ -1173,4 +1209,69 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
|
||||
fun pauseAllDownloads() = viewModelScope.launch {
|
||||
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
|
||||
isPausingResuming = true
|
||||
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
||||
val activeDownloadsList = withContext(Dispatchers.IO){
|
||||
getActiveDownloads()
|
||||
}
|
||||
activeDownloadsList.forEach {
|
||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
||||
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
||||
}
|
||||
delay(1000)
|
||||
isPausingResuming = false
|
||||
activeDownloadsList.forEach {
|
||||
repository.setDownloadStatus(it.id, DownloadRepository.Status.Paused)
|
||||
}
|
||||
pausedAllDownloads.value = PausedAllDownloadsState.RESUME
|
||||
}
|
||||
|
||||
fun resumeAllDownloads() = viewModelScope.launch {
|
||||
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
|
||||
isPausingResuming = true
|
||||
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
||||
val paused = withContext(Dispatchers.IO) {
|
||||
dao.getPausedDownloadsList()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
dbManager.downloadDao.resetPausedToQueued()
|
||||
repository.startDownloadWorker(paused, application)
|
||||
}
|
||||
delay(1000)
|
||||
isPausingResuming = false
|
||||
withContext(Dispatchers.Main) {
|
||||
pausedAllDownloads.value = PausedAllDownloadsState.PAUSE
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelAllDownloads() = viewModelScope.launch {
|
||||
WorkManager.getInstance(application).cancelAllWorkByTag("download")
|
||||
val activeAndQueued = withContext(Dispatchers.IO){
|
||||
repository.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
activeAndQueued.forEach { id ->
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||
}
|
||||
cancelActiveQueued()
|
||||
}
|
||||
|
||||
fun resumeDownload(itemID: Long) = viewModelScope.launch {
|
||||
kotlin.runCatching {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
repository.getItemByID(itemID)
|
||||
}
|
||||
item.status = DownloadRepository.Status.Queued.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
updateDownload(item)
|
||||
}
|
||||
putAtTopOfQueue(listOf(itemID))
|
||||
withContext(Dispatchers.IO){
|
||||
repository.startDownloadWorker(listOf(item), application, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,9 +10,17 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.deniscerri.ytdl.database.DBManager
|
||||
import com.deniscerri.ytdl.database.DBManager.SORTING
|
||||
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.repository.HistoryRepository
|
||||
import com.deniscerri.ytdl.database.repository.HistoryRepository.HistorySortType
|
||||
import com.deniscerri.ytdl.util.Extensions
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
|
@ -20,36 +28,40 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
|||
val sortOrder = MutableLiveData(SORTING.DESC)
|
||||
val sortType = MutableLiveData(HistorySortType.DATE)
|
||||
val websiteFilter = MutableLiveData("")
|
||||
val statusFilter = MutableLiveData(HistoryStatus.UNSET)
|
||||
private val queryFilter = MutableLiveData("")
|
||||
private val formatFilter = MutableLiveData("")
|
||||
private val notDeletedFilter = MutableLiveData(false)
|
||||
private val typeFilter = MutableLiveData("")
|
||||
|
||||
enum class HistoryStatus {
|
||||
UNSET, DELETED, NOT_DELETED, ALL
|
||||
}
|
||||
|
||||
val allItems : LiveData<List<HistoryItem>>
|
||||
private var _items = MediatorLiveData<List<HistoryItem>>()
|
||||
|
||||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).historyDao
|
||||
repository = HistoryRepository(dao)
|
||||
allItems = repository.items.asLiveData()
|
||||
|
||||
_items.addSource(allItems){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
}
|
||||
_items.addSource(formatFilter){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
_items.addSource(typeFilter){
|
||||
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
}
|
||||
_items.addSource(sortType){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
}
|
||||
_items.addSource(websiteFilter){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
}
|
||||
_items.addSource(queryFilter){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
}
|
||||
|
||||
_items.addSource(notDeletedFilter){
|
||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
_items.addSource(statusFilter){
|
||||
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -77,22 +89,26 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
|||
queryFilter.value = filter
|
||||
}
|
||||
|
||||
fun setFormatFilter(filter: String){
|
||||
formatFilter.value = filter
|
||||
fun setTypeFilter(filter: String){
|
||||
typeFilter.value = filter
|
||||
}
|
||||
|
||||
fun setNotDeleted(filter: Boolean){
|
||||
notDeletedFilter.value = filter
|
||||
fun setStatusFilter(status: HistoryStatus) {
|
||||
statusFilter.value = status
|
||||
}
|
||||
|
||||
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) = viewModelScope.launch(Dispatchers.IO){
|
||||
_items.postValue(repository.getFiltered(query, format, site, sortType, sort, notDeleted))
|
||||
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryStatus) = viewModelScope.launch(Dispatchers.IO){
|
||||
_items.postValue(repository.getFiltered(query, format, site, sortType, sort, statusFilter))
|
||||
}
|
||||
|
||||
fun getAll() : List<HistoryItem> {
|
||||
return repository.getAll()
|
||||
}
|
||||
|
||||
fun getByID(id: Long) : HistoryItem {
|
||||
return repository.getItem(id)
|
||||
}
|
||||
|
||||
fun insert(item: HistoryItem) = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.insert(item)
|
||||
}
|
||||
|
|
@ -118,13 +134,14 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
|||
}
|
||||
|
||||
fun getRecordsBetweenTwoItems(item1: Long, item2: Long) : List<HistoryItem> {
|
||||
val filtered = repository.getFiltered(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||
val filtered = repository.getFiltered(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
|
||||
val firstIndex = filtered.indexOfFirst { it.id == item1 }
|
||||
val secondIndex = filtered.indexOfFirst { it.id == item2 }
|
||||
if(firstIndex > secondIndex) {
|
||||
return filtered.filterIndexed { index, _ -> index in (secondIndex + 1) until firstIndex }
|
||||
|
||||
return if(firstIndex > secondIndex) {
|
||||
filtered.filterIndexed { index, _ -> index in (secondIndex + 1) until firstIndex }
|
||||
}else{
|
||||
return filtered.filterIndexed { index, _ -> index in (firstIndex + 1) until secondIndex }
|
||||
filtered.filterIndexed { index, _ -> index in (firstIndex + 1) until secondIndex }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import kotlinx.coroutines.joinAll
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
|
||||
|
|
@ -96,6 +97,14 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
playlistFilter.value = p
|
||||
}
|
||||
|
||||
private fun resetPlaylistFilter() = viewModelScope.launch(Dispatchers.Main) {
|
||||
_items.removeSource(playlistFilter)
|
||||
playlistFilter.value = ""
|
||||
_items.addSource(playlistFilter) {
|
||||
filter()
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilteredList() : LiveData<List<ResultItem>> {
|
||||
return _items
|
||||
}
|
||||
|
|
@ -133,7 +142,6 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
repository.itemCount.value = inputQueries.size
|
||||
}
|
||||
val resetResults = inputQueries.size == 1
|
||||
|
||||
uiState.update {it.copy(processing = true, errorMessage = null)}
|
||||
val res = mutableListOf<ResultItem?>()
|
||||
|
||||
|
|
@ -195,6 +203,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
|
||||
suspend fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteAll()
|
||||
resetPlaylistFilter()
|
||||
}
|
||||
|
||||
fun update(item: ResultItem) = viewModelScope.launch(Dispatchers.IO){
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
|||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.ui.BaseActivity
|
||||
import com.deniscerri.ytdl.util.Extensions.extractURL
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -170,16 +171,12 @@ class ShareActivity : BaseActivity() {
|
|||
runCatching { supportFragmentManager.popBackStack() }
|
||||
|
||||
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
|
||||
val url = when(action){
|
||||
val data = when(action){
|
||||
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
|
||||
else -> intent.dataString!!
|
||||
}
|
||||
val matcher = Patterns.WEB_URL.matcher(url)
|
||||
val inputQuery = if (matcher.find()){
|
||||
matcher.group()
|
||||
}else{
|
||||
url
|
||||
}
|
||||
|
||||
val inputQuery = data.extractURL()
|
||||
|
||||
val type = intent.getStringExtra("TYPE")
|
||||
val background = intent.getBooleanExtra("BACKGROUND", false)
|
||||
|
|
|
|||
|
|
@ -981,12 +981,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
searchView!!.editText.setSelection(searchView!!.editText.length())
|
||||
}
|
||||
|
||||
|
||||
private fun updateMultiplePlaylistResults(playlistTitles: List<String>) {
|
||||
playlistNameFilterChipGroup.children.filter { it.tag != "all" }.forEach {
|
||||
playlistNameFilterChipGroup.removeView(it)
|
||||
}
|
||||
|
||||
if (playlistTitles.isEmpty() || playlistTitles.size == 1) {
|
||||
playlistNameFilterScrollView.isVisible = false
|
||||
playlistNameFilterChipGroup.children.filter { it.tag != "all" }.forEach {
|
||||
playlistNameFilterChipGroup.removeView(it)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -995,9 +997,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
|
||||
for (t in playlistTitles) {
|
||||
val exists = playlistNameFilterChipGroup.children.any { it.tag == t }
|
||||
if (exists) continue
|
||||
|
||||
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, playlistNameFilterChipGroup, false) as Chip
|
||||
tmp.text = t
|
||||
tmp.tag = t
|
||||
|
|
@ -1008,6 +1007,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
playlistNameFilterChipGroup.addView(tmp)
|
||||
}
|
||||
|
||||
if (playlistNameFilterChipGroup.children.all { !(it as Chip).isChecked }) {
|
||||
(playlistNameFilterChipGroup.children.first() as Chip).isChecked = true
|
||||
}
|
||||
|
||||
playlistNameFilterScrollView.isVisible = true
|
||||
}
|
||||
}
|
||||
|
|
@ -71,8 +71,6 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
// PROGRESS BAR ----------------------------------------------------
|
||||
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
|
||||
progressBar.tag = "${item.id}##progress"
|
||||
progressBar.progress = 0
|
||||
progressBar.isIndeterminate = true
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
|
|
@ -122,39 +120,39 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
|
||||
cancelButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)}
|
||||
|
||||
// val resumeButton = card.findViewById<MaterialButton>(R.id.active_download_resume)
|
||||
// resumeButton.isEnabled = true
|
||||
// if (resumeButton.hasOnClickListeners()) resumeButton.setOnClickListener(null)
|
||||
// if (activePaused) {
|
||||
// resumeButton.setIconResource(R.drawable.exomedia_ic_play_arrow_white)
|
||||
// resumeButton.setOnClickListener {
|
||||
// resumeButton.isEnabled = false
|
||||
// onItemClickListener.onResumeClick(item.id)
|
||||
// }
|
||||
// }else {
|
||||
// resumeButton.setIconResource(R.drawable.exomedia_ic_pause_white)
|
||||
// resumeButton.setOnClickListener {
|
||||
// resumeButton.isEnabled = false
|
||||
// onItemClickListener.onPauseClick(item.id)
|
||||
// }
|
||||
// }
|
||||
val resumeButton = card.findViewById<MaterialButton>(R.id.active_download_resume)
|
||||
resumeButton.isEnabled = true
|
||||
if (resumeButton.hasOnClickListeners()) resumeButton.setOnClickListener(null)
|
||||
val isPaused = item.status == DownloadRepository.Status.Paused.toString()
|
||||
if (isPaused) {
|
||||
resumeButton.setIconResource(R.drawable.exomedia_ic_play_arrow_white)
|
||||
resumeButton.setOnClickListener {
|
||||
resumeButton.isEnabled = false
|
||||
onItemClickListener.onResumeClick(item.id)
|
||||
}
|
||||
}else {
|
||||
resumeButton.setIconResource(R.drawable.exomedia_ic_pause_white)
|
||||
resumeButton.setOnClickListener {
|
||||
resumeButton.isEnabled = false
|
||||
onItemClickListener.onPauseClick(item.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("paused_downloads", false)) {
|
||||
if (isPaused) {
|
||||
progressBar.isIndeterminate = false
|
||||
progressBar.progress = 0
|
||||
cancelButton.isEnabled = true
|
||||
output.text = activity.getString(R.string.exo_download_paused)
|
||||
}else{
|
||||
progressBar.isIndeterminate = true
|
||||
progressBar.isIndeterminate = progressBar.progress <= 0
|
||||
cancelButton.isEnabled = true
|
||||
}
|
||||
}
|
||||
interface OnItemClickListener {
|
||||
fun onCancelClick(itemID: Long)
|
||||
fun onOutputClick(item: DownloadItem)
|
||||
}
|
||||
|
||||
enum class ActiveDownloadAction {
|
||||
Resume, Pause
|
||||
fun onPauseClick(itemID: Long)
|
||||
fun onResumeClick(itemID: Long)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -129,35 +129,35 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
else fileSize.text = fileSizeReadable
|
||||
|
||||
val menu = card.findViewById<View>(R.id.options)
|
||||
//val paused = sharedPreferences.getBoolean("paused_downloads", false) || item.status == DownloadRepository.Status.ActivePaused.toString()
|
||||
val paused = item.status == DownloadRepository.Status.Paused.toString()
|
||||
menu.setOnClickListener {
|
||||
val popup = PopupMenu(activity, it)
|
||||
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
|
||||
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
|
||||
|
||||
// val pause = popup.menu[0]
|
||||
// val resume = popup.menu[1]
|
||||
//
|
||||
// if (paused){
|
||||
// pause.isVisible = false
|
||||
// resume.isVisible = true
|
||||
// }else{
|
||||
// pause.isVisible = true
|
||||
// resume.isVisible = false
|
||||
// }
|
||||
val pause = popup.menu[0]
|
||||
val resume = popup.menu[1]
|
||||
|
||||
if (paused){
|
||||
pause.isVisible = false
|
||||
resume.isVisible = true
|
||||
}else{
|
||||
pause.isVisible = true
|
||||
resume.isVisible = false
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener { m ->
|
||||
when(m.itemId){
|
||||
// R.id.pause -> {
|
||||
// onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
|
||||
// if (progressBar.progress == 0) progressBar.isIndeterminate = false
|
||||
// popup.dismiss()
|
||||
// }
|
||||
// R.id.resume -> {
|
||||
// onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
|
||||
// progressBar.isIndeterminate = true
|
||||
// popup.dismiss()
|
||||
// }
|
||||
R.id.pause -> {
|
||||
onItemClickListener.onPauseClick(item.id, position)
|
||||
if (progressBar.progress == 0) progressBar.isIndeterminate = false
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.resume -> {
|
||||
onItemClickListener.onResumeClick(item.id, position)
|
||||
progressBar.isIndeterminate = true
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.cancel -> {
|
||||
onItemClickListener.onCancelClick(item.id)
|
||||
popup.dismiss()
|
||||
|
|
@ -170,7 +170,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
|
||||
}
|
||||
|
||||
progressBar.isIndeterminate = !sharedPreferences.getBoolean("paused_downloads", false)
|
||||
progressBar.isIndeterminate = !paused
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onCardClick()
|
||||
|
|
@ -178,6 +178,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
}
|
||||
interface OnItemClickListener {
|
||||
fun onCancelClick(itemID: Long)
|
||||
fun onPauseClick(itemID: Long, position: Int)
|
||||
fun onResumeClick(itemID: Long, position: Int)
|
||||
fun onCardClick()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@ package com.deniscerri.ytdl.ui.adapter
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
|
|
@ -22,10 +27,12 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
|||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
private val checkedItems: ArrayList<Long> = ArrayList()
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||
|
|
@ -43,18 +50,18 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val item = getItem(position)!!
|
||||
val card = holder.item
|
||||
card.popup()
|
||||
|
||||
val title = card.findViewById<TextView>(R.id.title)
|
||||
title.text = item?.title
|
||||
title.text = item.title
|
||||
|
||||
val content = card.findViewById<TextView>(R.id.content)
|
||||
content.text = item?.content
|
||||
content.text = item.content
|
||||
|
||||
card.findViewById<TextView>(R.id.useInExtraCommands).apply {
|
||||
isVisible = item!!.useAsExtraCommand
|
||||
isVisible = item.useAsExtraCommand
|
||||
val extraAudio = if (item.useAsExtraCommandAudio) context.getString(R.string.audio) else null
|
||||
val extraVideo = if (item.useAsExtraCommandVideo) context.getString(R.string.video) else null
|
||||
val finalText = context.getString(R.string.extra_command) + " " + listOfNotNull(
|
||||
|
|
@ -64,7 +71,12 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
|||
text = finalText
|
||||
}
|
||||
|
||||
if (checkedItems.contains(item!!.id)) {
|
||||
card.findViewById<TextView>(R.id.preferredTemplate).apply {
|
||||
val preferred = sharedPreferences.getString("preferred_command_template", "")
|
||||
isVisible = preferred == item.content
|
||||
}
|
||||
|
||||
if (checkedItems.contains(item.id)) {
|
||||
card.isChecked = true
|
||||
card.strokeWidth = 5
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ import android.view.ViewGroup
|
|||
import android.view.Window
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.MediaItem
|
||||
|
|
@ -240,16 +242,21 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
true
|
||||
}
|
||||
|
||||
downloadThumb.isVisible = item.thumb.isNotBlank()
|
||||
downloadThumb.setOnClickListener {
|
||||
downloadManager.enqueue(
|
||||
DownloadManager.Request(item.thumb.toUri())
|
||||
.setAllowedNetworkTypes(
|
||||
DownloadManager.Request.NETWORK_WIFI or
|
||||
DownloadManager.Request.NETWORK_MOBILE
|
||||
)
|
||||
.setAllowedOverRoaming(true)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/" + item.title + ".jpg"))
|
||||
runCatching {
|
||||
downloadManager.enqueue(
|
||||
DownloadManager.Request(item.thumb.toUri())
|
||||
.setAllowedNetworkTypes(
|
||||
DownloadManager.Request.NETWORK_WIFI or
|
||||
DownloadManager.Request.NETWORK_MOBILE
|
||||
)
|
||||
.setAllowedOverRoaming(true)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/" + item.title + ".jpg"))
|
||||
}.onFailure {
|
||||
Toast.makeText(requireContext(), getString(R.string.failed_download), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
title.text = item.title
|
||||
|
|
@ -479,33 +486,24 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
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) {
|
||||
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
}
|
||||
}
|
||||
|
||||
if (activeCount == 1){
|
||||
val queue = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
|
||||
if (!sharedPreferences.getBoolean("paused_downloads", false)){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPauseClick(itemID: Long, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateToStatus(itemID, DownloadRepository.Status.Paused)
|
||||
}
|
||||
activeAdapter.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResumeClick(itemID: Long, position: Int) {
|
||||
downloadViewModel.resumeDownload(itemID)
|
||||
}
|
||||
|
||||
override fun onCardClick() {
|
||||
this.dismiss()
|
||||
findNavController().navigate(
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import android.annotation.SuppressLint
|
|||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
|
|
@ -23,6 +25,8 @@ import androidx.annotation.OptIn
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginBottom
|
||||
|
|
@ -36,6 +40,7 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.room.util.getColumnIndexOrThrow
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
|
|
@ -86,8 +91,8 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
private lateinit var activeDownloads : ActiveDownloadAdapter
|
||||
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var pause: ExtendedFloatingActionButton
|
||||
private lateinit var resume: ExtendedFloatingActionButton
|
||||
private lateinit var pauseBtn: ExtendedFloatingActionButton
|
||||
private lateinit var resumeBtn: ExtendedFloatingActionButton
|
||||
private lateinit var noResults: RelativeLayout
|
||||
private lateinit var workManager: WorkManager
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
|
@ -119,80 +124,64 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
activeRecyclerView.adapter = activeDownloads
|
||||
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
pause = view.findViewById(R.id.pause)
|
||||
pause.isVisible = false
|
||||
resume = view.findViewById(R.id.resume)
|
||||
pauseBtn = view.findViewById(R.id.pause)
|
||||
pauseBtn.isVisible = false
|
||||
resumeBtn = view.findViewById(R.id.resume)
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
|
||||
pause.setOnClickListener {
|
||||
downloadViewModel.pausedAllDownloads.observe(viewLifecycleOwner) { value ->
|
||||
lifecycleScope.launch {
|
||||
pause.isEnabled = false
|
||||
delay(1000)
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
val activeDownloadsList = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}
|
||||
|
||||
activeDownloadsList.forEach {
|
||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
||||
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
||||
}
|
||||
preferences.edit().putBoolean("paused_downloads", true).apply()
|
||||
pause.isVisible = false
|
||||
resume.isEnabled = false
|
||||
resume.isVisible = true
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
resume.isEnabled = true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
resume.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
resume.isEnabled = false
|
||||
delay(1000)
|
||||
preferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
resume.isVisible = false
|
||||
pause.isEnabled = false
|
||||
pause.isVisible = true
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.resetActiveToQueued()
|
||||
downloadViewModel.startDownloadWorker(listOf())
|
||||
withContext(Dispatchers.Main){
|
||||
when(value) {
|
||||
DownloadViewModel.PausedAllDownloadsState.PAUSE -> {
|
||||
resumeBtn.isVisible = false
|
||||
pauseBtn.isClickable = true
|
||||
val typedValue = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorPrimaryContainer, typedValue, true)
|
||||
pauseBtn.backgroundTintList = ColorStateList.valueOf(typedValue.data)
|
||||
pauseBtn.isVisible = true
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
pause.isEnabled = true
|
||||
}
|
||||
DownloadViewModel.PausedAllDownloadsState.RESUME -> {
|
||||
pauseBtn.isVisible = false
|
||||
resumeBtn.isClickable = true
|
||||
val typedValue = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorPrimaryContainer, typedValue, true)
|
||||
resumeBtn.backgroundTintList = ColorStateList.valueOf(typedValue.data)
|
||||
resumeBtn.isVisible = true
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
}
|
||||
DownloadViewModel.PausedAllDownloadsState.PROCESSING -> {
|
||||
pauseBtn.isClickable = false
|
||||
pauseBtn.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.grey))
|
||||
|
||||
resumeBtn.isClickable = false
|
||||
resumeBtn.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.grey))
|
||||
}
|
||||
else -> {
|
||||
pauseBtn.isVisible = false
|
||||
resumeBtn.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
val activeCount = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
pauseBtn.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.pauseAllDownloads()
|
||||
}
|
||||
|
||||
val queuedCount = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getQueuedDownloadsCount()
|
||||
}
|
||||
|
||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||
pause.isVisible = (queuedCount > 0 || activeCount > 0) && !pausedDownloads
|
||||
resume.isVisible = (queuedCount > 0 || activeCount > 0) && pausedDownloads
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeQueuedDownloadsCount.collectLatest {
|
||||
if (it == 0) {
|
||||
pause.isVisible = false
|
||||
resume.isVisible = false
|
||||
}
|
||||
resumeBtn.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.resumeAllDownloads()
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloads.collectLatest {
|
||||
downloadViewModel.activePausedDownloads.collectLatest {
|
||||
noResults.isVisible = it.isEmpty()
|
||||
activeDownloads.submitList(it)
|
||||
activeRecyclerView.scrollTo(0,0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,32 +224,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
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){
|
||||
downloadViewModel.getQueued()
|
||||
}
|
||||
|
||||
if (!preferences.getBoolean("paused_downloads", false)){
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -275,4 +238,18 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
override fun onPauseClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
YoutubeDL.getInstance().destroyProcessById(itemID.toString())
|
||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateToStatus(itemID, DownloadRepository.Status.Paused)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResumeClick(itemID: Long) {
|
||||
downloadViewModel.resumeDownload(itemID)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -209,7 +209,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
when(m.itemId){
|
||||
R.id.clear_queue -> {
|
||||
showDeleteDialog {
|
||||
cancelAllDownloads()
|
||||
downloadViewModel.cancelAllDownloads()
|
||||
}
|
||||
}
|
||||
R.id.clear_cancelled -> {
|
||||
|
|
@ -270,24 +270,6 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
deleteDialog.show()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun cancelAllDownloads() {
|
||||
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
fragmentAdapter.notifyDataSetChanged()
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
lifecycleScope.launch {
|
||||
val notificationUtil = NotificationUtil(requireContext())
|
||||
val activeAndQueued = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveAndQueuedDownloadIDs()
|
||||
}
|
||||
activeAndQueued.forEach { id ->
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||
}
|
||||
downloadViewModel.cancelActiveQueued()
|
||||
}
|
||||
}
|
||||
|
||||
fun scrollToActive(){
|
||||
tabLayout.getTabAt(0)!!.select()
|
||||
viewPager2.setCurrentItem(0, true)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ import androidx.appcompat.view.ActionMode
|
|||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -47,7 +49,9 @@ import com.deniscerri.ytdl.database.repository.HistoryRepository
|
|||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.ui.adapter.HistoryAdapter
|
||||
import com.deniscerri.ytdl.ui.downloadcard.FormatSelectionBottomSheetDialog.FormatCategory
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
|
|
@ -85,9 +89,9 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
private var uiHandler: Handler? = null
|
||||
private var noResults: RelativeLayout? = null
|
||||
private var selectionChips: LinearLayout? = null
|
||||
private var websiteGroup: ChipGroup? = null
|
||||
private var historyList: List<HistoryItem?>? = null
|
||||
private var allhistoryList: List<HistoryItem?>? = null
|
||||
private var websiteList: MutableList<String> = mutableListOf()
|
||||
private lateinit var selectedObjects: ArrayList<HistoryItem>
|
||||
private var actionMode : ActionMode? = null
|
||||
|
||||
|
|
@ -112,7 +116,6 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
topAppBar = view.findViewById(R.id.history_toolbar)
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
selectionChips = view.findViewById(R.id.history_selection_chips)
|
||||
websiteGroup = view.findViewById(R.id.website_chip_group)
|
||||
uiHandler = Handler(Looper.getMainLooper())
|
||||
sortChip = view.findViewById(R.id.sortChip)
|
||||
selectedObjects = arrayListOf()
|
||||
|
|
@ -127,7 +130,6 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
|
||||
|
||||
historyList = mutableListOf()
|
||||
allhistoryList = mutableListOf()
|
||||
|
||||
historyAdapter =
|
||||
HistoryAdapter(
|
||||
|
|
@ -149,14 +151,14 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
|
||||
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
|
||||
historyViewModel.allItems.observe(viewLifecycleOwner) {
|
||||
allhistoryList = it
|
||||
if(it.isEmpty()){
|
||||
noResults!!.visibility = VISIBLE
|
||||
selectionChips!!.visibility = GONE
|
||||
websiteGroup!!.removeAllViews()
|
||||
topAppBar!!.menu.children.firstOrNull { m -> m.itemId == R.id.filters }?.isVisible = false
|
||||
}else{
|
||||
noResults!!.visibility = GONE
|
||||
selectionChips!!.visibility = VISIBLE
|
||||
topAppBar!!.menu.children.firstOrNull { m -> m.itemId == R.id.filters }?.isVisible = true
|
||||
updateWebsiteChips(it)
|
||||
}
|
||||
}
|
||||
|
|
@ -306,6 +308,106 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
deleteDialog.show()
|
||||
}
|
||||
}
|
||||
R.id.filters -> {
|
||||
val filterSheet = BottomSheetDialog(requireContext())
|
||||
filterSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
filterSheet.setContentView(R.layout.history_other_filters_sheet)
|
||||
|
||||
//format status
|
||||
val notDeleted = filterSheet.findViewById<TextView>(R.id.not_deleted)!!
|
||||
val deleted = filterSheet.findViewById<TextView>(R.id.deleted)!!
|
||||
when(historyViewModel.statusFilter.value) {
|
||||
HistoryViewModel.HistoryStatus.ALL -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
}
|
||||
HistoryViewModel.HistoryStatus.DELETED -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0)
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
}
|
||||
HistoryViewModel.HistoryStatus.NOT_DELETED -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
notDeleted.setOnClickListener {
|
||||
val status = historyViewModel.statusFilter.value
|
||||
when (status) {
|
||||
HistoryViewModel.HistoryStatus.ALL -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.DELETED)
|
||||
}
|
||||
HistoryViewModel.HistoryStatus.NOT_DELETED -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.UNSET)
|
||||
}
|
||||
HistoryViewModel.HistoryStatus.DELETED -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.ALL)
|
||||
}
|
||||
else -> {
|
||||
notDeleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.NOT_DELETED)
|
||||
}
|
||||
}
|
||||
}
|
||||
deleted.setOnClickListener {
|
||||
val status = historyViewModel.statusFilter.value
|
||||
when (status) {
|
||||
HistoryViewModel.HistoryStatus.ALL -> {
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.NOT_DELETED)
|
||||
}
|
||||
HistoryViewModel.HistoryStatus.NOT_DELETED -> {
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.ALL)
|
||||
}
|
||||
HistoryViewModel.HistoryStatus.DELETED -> {
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.UNSET)
|
||||
}
|
||||
else -> {
|
||||
deleted.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check,0,0,0)
|
||||
historyViewModel.setStatusFilter(HistoryViewModel.HistoryStatus.DELETED)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (websiteList.size < 2) {
|
||||
filterSheet.findViewById<View>(R.id.websiteFilters)?.isVisible = false
|
||||
}else{
|
||||
val websiteGroup = filterSheet.findViewById<ChipGroup>(R.id.websitesChipGroup)
|
||||
val websiteFilter = historyViewModel.websiteFilter.value
|
||||
|
||||
for (i in websiteList.indices) {
|
||||
val w = websiteList[i]
|
||||
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
|
||||
tmp.text = w
|
||||
tmp.id = i
|
||||
tmp.setOnClickListener {
|
||||
Log.e(TAG, tmp.isChecked.toString())
|
||||
if (tmp.isChecked) {
|
||||
historyViewModel.setWebsiteFilter(tmp.text as String)
|
||||
tmp.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setWebsiteFilter("")
|
||||
tmp.isChecked = false
|
||||
}
|
||||
}
|
||||
if (w == websiteFilter){
|
||||
tmp.isChecked = true
|
||||
}
|
||||
websiteGroup!!.addView(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
filterSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
filterSheet.show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -374,80 +476,43 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
val audio = fragmentView!!.findViewById<Chip>(R.id.audio_chip)
|
||||
audio.setOnClickListener {
|
||||
if (audio.isChecked) {
|
||||
historyViewModel.setFormatFilter("audio")
|
||||
historyViewModel.setTypeFilter(DownloadViewModel.Type.audio.toString())
|
||||
audio.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setFormatFilter("")
|
||||
historyViewModel.setTypeFilter("")
|
||||
audio.isChecked = false
|
||||
}
|
||||
}
|
||||
val video = fragmentView!!.findViewById<Chip>(R.id.video_chip)
|
||||
video.setOnClickListener {
|
||||
if (video.isChecked) {
|
||||
historyViewModel.setFormatFilter("video")
|
||||
historyViewModel.setTypeFilter(DownloadViewModel.Type.video.toString())
|
||||
video.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setFormatFilter("")
|
||||
historyViewModel.setTypeFilter("")
|
||||
video.isChecked = false
|
||||
}
|
||||
}
|
||||
val command = fragmentView!!.findViewById<Chip>(R.id.command_chip)
|
||||
command.setOnClickListener {
|
||||
if (command.isChecked) {
|
||||
historyViewModel.setFormatFilter("command")
|
||||
historyViewModel.setTypeFilter(DownloadViewModel.Type.command.toString())
|
||||
command.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setFormatFilter("")
|
||||
historyViewModel.setTypeFilter("")
|
||||
command.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
val notDeleted = fragmentView!!.findViewById<Chip>(R.id.not_deleted_chip)
|
||||
notDeleted.setOnClickListener {
|
||||
if (notDeleted.isChecked) {
|
||||
historyViewModel.setNotDeleted(true)
|
||||
notDeleted.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setNotDeleted(false)
|
||||
notDeleted.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun updateWebsiteChips(list : List<HistoryItem>) {
|
||||
val websites = mutableListOf<String>()
|
||||
val websiteFilter = historyViewModel.websiteFilter.value
|
||||
websiteList = mutableListOf()
|
||||
for (item in list){
|
||||
if (!websites.contains(item.website)) websites.add(item.website)
|
||||
if (item.website == "null" || item.website.isEmpty()) continue
|
||||
if (!websiteList.contains(item.website)) websiteList.add(item.website)
|
||||
}
|
||||
websiteGroup!!.removeAllViews()
|
||||
if (websites.size <= 1) {
|
||||
requireView().findViewById<View>(R.id.website_divider).visibility = GONE
|
||||
return
|
||||
}
|
||||
//val websites = historyRecyclerViewAdapter!!.websites
|
||||
for (i in websites.indices) {
|
||||
val w = websites[i]
|
||||
if (w == "null" || w.isEmpty()) continue
|
||||
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
|
||||
tmp.text = w
|
||||
tmp.id = i
|
||||
tmp.setOnClickListener {
|
||||
Log.e(TAG, tmp.isChecked.toString())
|
||||
if (tmp.isChecked) {
|
||||
historyViewModel.setWebsiteFilter(tmp.text as String)
|
||||
tmp.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setWebsiteFilter("")
|
||||
tmp.isChecked = false
|
||||
}
|
||||
}
|
||||
if (w == websiteFilter){
|
||||
tmp.isChecked = true
|
||||
}
|
||||
websiteGroup!!.addView(tmp)
|
||||
}
|
||||
requireView().findViewById<View>(R.id.website_divider).visibility = VISIBLE
|
||||
}
|
||||
|
||||
override fun onCardClick(itemID: Long, isPresent: Boolean) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
|
|
@ -23,6 +24,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -43,6 +45,7 @@ import com.deniscerri.ytdl.util.UiUtil
|
|||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
|
@ -64,6 +67,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var sortChip: Chip
|
||||
private lateinit var selectedObjects: ArrayList<CommandTemplate>
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private var actionMode : ActionMode? = null
|
||||
private val jsonFormat = Json { prettyPrint = true }
|
||||
|
||||
|
|
@ -95,7 +99,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
recyclerView = view.findViewById(R.id.template_recyclerview)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
recyclerView.adapter = templatesAdapter
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("templates")){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
|
|
@ -117,8 +121,8 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
commandTemplateViewModel.sortOrder.observe(viewLifecycleOwner){
|
||||
if (it != null){
|
||||
when(it){
|
||||
SORTING.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
|
||||
SORTING.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
|
||||
SORTING.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
|
||||
SORTING.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -264,9 +268,10 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onItemClick(commandTemplate: CommandTemplate, index: Int) {
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(commandTemplate,mainActivity, this, commandTemplateViewModel, newTemplate = {
|
||||
templatesAdapter.notifyItemChanged(index)
|
||||
templatesAdapter.notifyDataSetChanged()
|
||||
}, {})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,14 +145,13 @@ class MoreFragment : Fragment() {
|
|||
}
|
||||
|
||||
activeDownloadsList.forEach {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
||||
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
||||
it.status = DownloadRepository.Status.Paused.toString()
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
mainSharedPreferencesEditor.putBoolean("paused_downloads", true).apply()
|
||||
}
|
||||
|
||||
if (doNotShowAgain){
|
||||
|
|
|
|||
|
|
@ -148,6 +148,14 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
backup = findPreference("backup")
|
||||
restore = findPreference("restore")
|
||||
|
||||
findPreference<Preference>("package_name")?.apply {
|
||||
summary = BuildConfig.APPLICATION_ID
|
||||
setOnPreferenceClickListener {
|
||||
UiUtil.copyToClipboard(summary.toString(), requireActivity())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
backup!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import android.net.Uri
|
|||
import android.text.Html
|
||||
import android.text.Spanned
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Patterns
|
||||
import android.util.TypedValue
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
|
@ -45,16 +46,22 @@ import com.deniscerri.ytdl.database.models.Format
|
|||
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository.EveryCategory
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeChannelURL
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||
import com.deniscerri.ytdl.util.Extensions.toTimePeriodsArray
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.neo.highlight.core.Highlight
|
||||
import com.neo.highlight.util.listener.HighlightTextWatcher
|
||||
import com.neo.highlight.util.scheme.ColorScheme
|
||||
import com.squareup.picasso.Picasso
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
|
|
@ -212,7 +219,13 @@ object Extensions {
|
|||
tags.forEach {
|
||||
runCatching {
|
||||
val tmp = this.getString(it)
|
||||
if (tmp != "null") return tmp
|
||||
if (tmp != "null") {
|
||||
return if (tmp.startsWith("[")) {
|
||||
Json.decodeFromString<List<String>>(tmp).joinToString(", ")
|
||||
}else{
|
||||
tmp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -508,4 +521,16 @@ object Extensions {
|
|||
return Pattern.compile("((^(https?)://)?(www.)?(m.)?youtu(.be)?(be.com))/@[a-zA-Z]+").matcher(this).find()
|
||||
}
|
||||
|
||||
fun String.isYoutubeWatchVideosURL() : Boolean {
|
||||
return Pattern.compile("((^(https?)://)?(www.)?(m.)?youtu(.be)?(be.com))/watch_videos\\?video_ids=.*").matcher(this).find()
|
||||
}
|
||||
|
||||
fun String.extractURL() : String {
|
||||
val res = Pattern.compile("(http|ftp|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])").matcher(this)
|
||||
return if (res.find()){
|
||||
res.group()
|
||||
}else{
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import androidx.preference.PreferenceManager
|
|||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import java.util.Collections
|
||||
|
||||
class FormatUtil(private var context: Context) {
|
||||
private val sharedPreferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
|
@ -19,8 +20,13 @@ class FormatUtil(private var context: Context) {
|
|||
private val videoCodecPreference : String = sharedPreferences.getString("video_codec", "").toString()
|
||||
private val audioContainerPreference : String = sharedPreferences.getString("audio_format", "").toString()
|
||||
private val videoContainerPreference : String = sharedPreferences.getString("video_format", "").toString()
|
||||
@SuppressLint("RestrictedApi")
|
||||
private val videoResolutionOrder = context.getStringArray(R.array.video_formats_values).filter { it.contains("_") }.map{ it.split("_")[0].dropLast(1) }.reversed()
|
||||
private val videoResolutionOrder = context.getStringArray(R.array.video_formats_values)
|
||||
.filter { it.contains("_") }
|
||||
.map{ it.split("_")[0].dropLast(1) }.toMutableList().apply {
|
||||
this.reverse()
|
||||
}
|
||||
|
||||
|
||||
private val preferSmallerFormats = sharedPreferences.getBoolean("prefer_smaller_formats", false)
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
|
|
|
|||
|
|
@ -182,9 +182,12 @@ object UiUtil {
|
|||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.create_command_template)
|
||||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
val ok : Button = bottomSheet.findViewById(R.id.template_create)!!
|
||||
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
|
||||
val content : TextInputLayout = bottomSheet.findViewById(R.id.content)!!
|
||||
val preferredCommandSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.preferredCommandTemplateSwitch)!!
|
||||
val extraCommandsSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandsSwitch)!!
|
||||
val extraCommandsAudio : CheckBox = bottomSheet.findViewById(R.id.checkbox_audio)!!
|
||||
val extraCommandsVideo : CheckBox = bottomSheet.findViewById(R.id.checkbox_video)!!
|
||||
|
|
@ -248,6 +251,8 @@ object UiUtil {
|
|||
}
|
||||
|
||||
if (item != null){
|
||||
preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "")
|
||||
|
||||
extraCommandsSwitch.isChecked = item.useAsExtraCommand
|
||||
if (item.useAsExtraCommand){
|
||||
extraCommandsAudio.isVisible = true
|
||||
|
|
@ -294,6 +299,13 @@ object UiUtil {
|
|||
}
|
||||
|
||||
ok.setOnClickListener {
|
||||
if (preferredCommandSwitch.isChecked) {
|
||||
sharedPreferences.edit().putString("preferred_command_template", content.editText!!.text.toString()).apply()
|
||||
}else if (sharedPreferences.getString("preferred_command_template", "") == item?.content){
|
||||
sharedPreferences.edit().putString("preferred_command_template", "").apply()
|
||||
}
|
||||
|
||||
|
||||
if (item == null){
|
||||
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked)
|
||||
commandTemplateViewModel.insert(t)
|
||||
|
|
@ -304,7 +316,6 @@ object UiUtil {
|
|||
item.useAsExtraCommand = extraCommandsSwitch.isChecked
|
||||
item.useAsExtraCommandAudio = extraCommandsAudio.isChecked
|
||||
item.useAsExtraCommandVideo = extraCommandsVideo.isChecked
|
||||
Log.e("aa", item.toString())
|
||||
commandTemplateViewModel.update(item)
|
||||
newTemplate(item)
|
||||
}
|
||||
|
|
@ -1037,7 +1048,7 @@ object UiUtil {
|
|||
}
|
||||
|
||||
|
||||
suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: List<CommandTemplate>) -> Unit) {
|
||||
suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, onlyOne: Boolean = false, itemSelected: (itemSelected: List<CommandTemplate>) -> Unit) {
|
||||
val bottomSheet = BottomSheetDialog(activity)
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.command_template_list)
|
||||
|
|
@ -1050,6 +1061,7 @@ object UiUtil {
|
|||
linearLayout!!.removeAllViews()
|
||||
val selectedItems = mutableListOf<CommandTemplate>()
|
||||
val ok = bottomSheet.findViewById<MaterialButton>(R.id.command_ok)
|
||||
ok?.isVisible = !onlyOne
|
||||
ok?.isEnabled = list.size == 1
|
||||
|
||||
list.forEach {template ->
|
||||
|
|
@ -1065,6 +1077,11 @@ object UiUtil {
|
|||
(it as MaterialCardView).isChecked = true
|
||||
}
|
||||
|
||||
if (onlyOne) {
|
||||
itemSelected(listOf(template))
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
ok?.isEnabled = selectedItems.isNotEmpty()
|
||||
}
|
||||
linearLayout.addView(item)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
|||
import com.deniscerri.ytdl.util.Extensions.getIntByAny
|
||||
import com.deniscerri.ytdl.util.Extensions.getStringByAny
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
|
||||
import com.deniscerri.ytdl.util.Extensions.isYoutubeWatchVideosURL
|
||||
import com.deniscerri.ytdl.util.Extensions.toStringDuration
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.FormatUtil
|
||||
|
|
@ -51,7 +52,14 @@ class YTDLPUtil(private val context: Context) {
|
|||
|
||||
val request : YoutubeDLRequest
|
||||
if (query.contains("http")){
|
||||
request = YoutubeDLRequest(query)
|
||||
if (query.isYoutubeWatchVideosURL()) {
|
||||
request = YoutubeDLRequest(emptyList())
|
||||
val config = File(context.cacheDir.absolutePath + "/config" + System.currentTimeMillis() + "##url.txt")
|
||||
config.writeText(query)
|
||||
request.addOption("--config", config.absolutePath)
|
||||
}else{
|
||||
request = YoutubeDLRequest(query)
|
||||
}
|
||||
}else{
|
||||
request = YoutubeDLRequest(emptyList())
|
||||
when (searchEngine){
|
||||
|
|
@ -64,13 +72,8 @@ class YTDLPUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
val lang = sharedPreferences.getString("app_language", "en")
|
||||
if (searchEngine == "ytsearch" || query.isYoutubeURL()) {
|
||||
var extractorArgs = "player_client=default,mediaconnect"
|
||||
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
|
||||
extractorArgs += ";lang=$lang"
|
||||
}
|
||||
request.addOption("--extractor-args", "youtube:$extractorArgs")
|
||||
request.addOption("--extractor-args", "youtube:${getYoutubeExtractorArgs()}")
|
||||
}
|
||||
|
||||
request.addOption("--flat-playlist")
|
||||
|
|
@ -103,7 +106,7 @@ class YTDLPUtil(private val context: Context) {
|
|||
request.addOption("-P", FileUtil.getCachePath(context) + "/tmp")
|
||||
|
||||
|
||||
|
||||
println(parseYTDLRequestString(request))
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
val results: List<String?> = try {
|
||||
val lineSeparator = System.getProperty("line.separator")
|
||||
|
|
@ -119,7 +122,7 @@ class YTDLPUtil(private val context: Context) {
|
|||
val title = jsonObject.getStringByAny("alt_title", "title", "webpage_url_basename")
|
||||
if (title == "[Private video]" || title == "[Deleted video]") continue
|
||||
|
||||
var author = jsonObject.getStringByAny("artist", "uploader", "channel", "playlist_uploader", "uploader_id")
|
||||
var author = jsonObject.getStringByAny("artists", "artist", "uploader", "channel", "playlist_uploader", "uploader_id")
|
||||
var duration = jsonObject.getIntByAny("duration").toString()
|
||||
if (duration != "-1"){
|
||||
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
||||
|
|
@ -689,12 +692,7 @@ class YTDLPUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if (downloadItem.url.isYoutubeURL()) {
|
||||
var extractorArgs = "player_client=default,mediaconnect"
|
||||
val lang = sharedPreferences.getString("app_language", "en")
|
||||
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
|
||||
extractorArgs += ";lang=$lang"
|
||||
}
|
||||
request.addOption("--extractor-args", "youtube:$extractorArgs")
|
||||
request.addOption("--extractor-args", "youtube:${getYoutubeExtractorArgs()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1098,4 +1096,18 @@ class YTDLPUtil(private val context: Context) {
|
|||
}
|
||||
return request
|
||||
}
|
||||
|
||||
private fun getYoutubeExtractorArgs() : String {
|
||||
var extractorArgs = "player_client=default,mediaconnect"
|
||||
val lang = sharedPreferences.getString("app_language", "en")
|
||||
if (context.getStringArray(R.array.subtitle_langs).contains(lang)) {
|
||||
extractorArgs += ";lang=$lang"
|
||||
}
|
||||
val poToken = sharedPreferences.getString("youtube_po_token", "")!!
|
||||
if (poToken.isNotBlank()) {
|
||||
extractorArgs += ";po_token=web+$poToken"
|
||||
}
|
||||
|
||||
return extractorArgs
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +151,8 @@ class NewPipeUtil(context: Context) {
|
|||
|
||||
fun getChannelData(url: String, progress: (pagedResults: MutableList<ResultItem>) -> Unit) : Result<List<ResultItem>> {
|
||||
try {
|
||||
//TODO BROKEN FOR NOW
|
||||
return Result.failure(Throwable())
|
||||
val req = ChannelInfo.getInfo(ServiceList.YouTube, url)
|
||||
println(Gson().toJson(req))
|
||||
val items = mutableListOf<ResultItem>()
|
||||
|
|
@ -326,7 +328,7 @@ class NewPipeUtil(context: Context) {
|
|||
if (stream.audioStreams.isNotEmpty()){
|
||||
for (f in 0 until stream.audioStreams.size){
|
||||
val it = stream.audioStreams[f]
|
||||
if (it.bitrate == 0) continue
|
||||
if (it.bitrate == 0 || listOf(599, 600).contains(it.itag)) continue
|
||||
|
||||
val formatObj = Format(
|
||||
format_id = it.itag.toString(),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import androidx.work.ForegroundInfo
|
|||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.await
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.App
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
|
|
@ -38,6 +37,7 @@ import com.yausername.youtubedl_android.YoutubeDL
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -53,6 +53,13 @@ class DownloadWorker(
|
|||
@SuppressLint("RestrictedApi")
|
||||
override suspend fun doWork(): Result {
|
||||
if (isStopped) return Result.success()
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
val currentWork = withContext(Dispatchers.IO) {
|
||||
workManager.getWorkInfosByTag("download").get()
|
||||
}
|
||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) {
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val notificationUtil = NotificationUtil(App.instance)
|
||||
val ytdlpUtil = YTDLPUtil(context)
|
||||
|
|
@ -66,10 +73,11 @@ class DownloadWorker(
|
|||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val time = System.currentTimeMillis() + 6000
|
||||
val queuedItems = dao.getQueuedScheduledDownloadsUntil(time)
|
||||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||
val priorityItemIDs = inputData.getLongArray("priority_item_ids")!!.toMutableList()
|
||||
val continueAfterPriorityIds = inputData.getBoolean("continue_after_priority_ids", true)
|
||||
|
||||
// this is needed for observe sources call, so it wont create result items
|
||||
// [removed]
|
||||
//val createResultItem = inputData.getBoolean("createResultItem", true)
|
||||
|
||||
val confTmp = Configuration(context.resources.configuration)
|
||||
|
|
@ -100,8 +108,9 @@ class DownloadWorker(
|
|||
val foregroundInfo = ForegroundInfo(1000000000, workNotif)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
queuedItems.collect { items ->
|
||||
if (isStopped) return@collect
|
||||
queuedItems.collectLatest { items ->
|
||||
if (this@DownloadWorker.isStopped) return@collectLatest
|
||||
|
||||
runningYTDLInstances.clear()
|
||||
val activeDownloads = dao.getActiveDownloadsList()
|
||||
activeDownloads.forEach {
|
||||
|
|
@ -112,17 +121,28 @@ class DownloadWorker(
|
|||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
if (items.isEmpty() && running.isEmpty()) {
|
||||
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
return@collect
|
||||
return@collectLatest
|
||||
}
|
||||
|
||||
if (useScheduler){
|
||||
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
|
||||
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
return@collect
|
||||
return@collectLatest
|
||||
}
|
||||
}
|
||||
|
||||
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) {
|
||||
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
return@collectLatest
|
||||
}
|
||||
|
||||
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
|
||||
val eligibleDownloads = items.take(if (concurrentDownloads < 0) 0 else concurrentDownloads).filter { it.id !in running }
|
||||
val eligibleDownloads = if (priorityItemIDs.isNotEmpty()) {
|
||||
val tmp = priorityItemIDs.take(concurrentDownloads)
|
||||
items.filter { it.id !in running && tmp.contains(it.id) }
|
||||
}else{
|
||||
items.take(concurrentDownloads).filter { it.id !in running }
|
||||
}
|
||||
|
||||
eligibleDownloads.forEach{downloadItem ->
|
||||
val notification = notificationUtil.createDownloadServiceNotification(openDownloadQueue, downloadItem.title.ifEmpty { downloadItem.url })
|
||||
|
|
@ -365,7 +385,10 @@ class DownloadWorker(
|
|||
}
|
||||
|
||||
if (eligibleDownloads.isNotEmpty()){
|
||||
eligibleDownloads.forEach { it.status = DownloadRepository.Status.Active.toString() }
|
||||
eligibleDownloads.forEach {
|
||||
it.status = DownloadRepository.Status.Active.toString()
|
||||
priorityItemIDs.remove(it.id)
|
||||
}
|
||||
dao.updateMultiple(eligibleDownloads)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ class ObserveSourceWorker(
|
|||
}
|
||||
|
||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
|
||||
downloadRepo.startDownloadWorker(queuedItems, context, Data.Builder().putBoolean("createResultItem", false))
|
||||
downloadRepo.startDownloadWorker(queuedItems, context)
|
||||
|
||||
if(!useScheduler){
|
||||
queuedItems.filter { it.downloadStartTime != 0L || (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||
|
|
|
|||
5
app/src/main/res/drawable/baseline_exit_to_app_24.xml
Normal file
5
app/src/main/res/drawable/baseline_exit_to_app_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="?attr/colorAccent" android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -128,13 +128,14 @@
|
|||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
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"
|
||||
android:layout_marginStart="60dp"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_toolbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/main_menu">
|
||||
|
|
|
|||
|
|
@ -166,21 +166,21 @@
|
|||
app:layout_constraintVertical_bias="0.51"
|
||||
>
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/active_download_resume"-->
|
||||
<!-- style="?attr/materialIconButtonFilledStyle"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:contentDescription="@string/resume"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:backgroundTint="?attr/colorSurface"-->
|
||||
<!-- app:cornerRadius="15dp"-->
|
||||
<!-- app:icon="@drawable/exomedia_ic_play_arrow_white"-->
|
||||
<!-- app:iconSize="30dp"-->
|
||||
<!-- app:iconTint="?android:textColorPrimary"-->
|
||||
<!-- app:layout_constraintBottom_toTopOf="@+id/output"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"-->
|
||||
<!-- app:layout_constraintVertical_bias="0.51" />-->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_resume"
|
||||
style="?attr/materialIconButtonFilledStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/resume"
|
||||
android:layout_height="wrap_content"
|
||||
app:backgroundTint="?attr/colorSurface"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/output"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
|
||||
app:layout_constraintVertical_bias="0.51" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_delete"
|
||||
|
|
|
|||
|
|
@ -41,26 +41,66 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/useInExtraCommands"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:visibility="gone"
|
||||
android:ellipsize="end"
|
||||
android:text="@string/extra_command"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/content" />
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/content"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preferredTemplate"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
android:text="@string/preferred_command_template"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/useInExtraCommands"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:text="@string/extra_command"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondaryContainer"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -102,54 +102,60 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/preferredCommandTemplateSwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:checked="false"
|
||||
android:text="@string/preferred_command_template"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
<View
|
||||
style="@style/Divider.Horizontal"
|
||||
android:layout_marginVertical="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="20dp">
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/extraCommandsSwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:checked="false"
|
||||
android:text="@string/extra_command"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/extraCommandsSwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:checked="false"
|
||||
android:text="@string/extra_command"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_audio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_marginEnd="20dp"
|
||||
android:visibility="gone"
|
||||
android:checked="true"
|
||||
android:text="@string/audio" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_audio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:visibility="gone"
|
||||
android:checked="true"
|
||||
android:text="@string/audio" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_video"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:visibility="gone"
|
||||
android:text="@string/video" />
|
||||
|
||||
</LinearLayout>
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_video"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:visibility="gone"
|
||||
android:text="@string/video" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
style="@style/Divider.Horizontal"
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="20dp">
|
||||
android:layout_marginTop="20dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:visibility="gone"
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/pause"
|
||||
android:text="@string/pause_all"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/pause"
|
||||
android:contentDescription="@string/pause_all"
|
||||
app:icon="@drawable/exomedia_ic_pause_white"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
|
|
@ -29,9 +29,9 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:text="@string/resume"
|
||||
android:text="@string/resume_all"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="@string/resume"
|
||||
android:contentDescription="@string/resume_all"
|
||||
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@
|
|||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
|
||||
<include layout="@layout/no_results"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/history_selection_chips"
|
||||
android:layout_width="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
|
|
@ -58,16 +57,6 @@
|
|||
|
||||
<View style="@style/Divider.Vertical"/>
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/not_deleted_chip"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/not_deleted"/>
|
||||
|
||||
<View android:id="@+id/notdeleted_divider" style="@style/Divider.Vertical"/>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
app:selectionRequired="false"
|
||||
|
|
@ -97,17 +86,6 @@
|
|||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<View android:id="@+id/website_divider" style="@style/Divider.Vertical"/>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/website_chip_group"
|
||||
app:selectionRequired="false"
|
||||
app:singleSelection="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
|
|
|||
91
app/src/main/res/layout/history_other_filters_sheet.xml
Normal file
91
app/src/main/res/layout/history_other_filters_sheet.xml
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout style="@style/Widget.Material3.BottomSheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/format_filter_linear"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:text="@string/status"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/not_deleted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="30dp"
|
||||
android:focusable="true"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:text="@string/not_deleted"
|
||||
android:textSize="15sp"
|
||||
app:drawableStartCompat="@drawable/empty" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/deleted"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:drawablePadding="30dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:text="@string/deleted"
|
||||
app:drawableStartCompat="@drawable/empty" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/websiteFilters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/websites"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/websitesChipGroup"
|
||||
android:layout_width="wrap_content"
|
||||
app:chipSpacingVertical="-10dp"
|
||||
app:chipSpacing="0dp"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
app:singleSelection="true"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="false">
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLength="9"
|
||||
android:inputType="number" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
|
@ -57,6 +58,7 @@
|
|||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLength="9"
|
||||
android:inputType="text" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<!-- <group>-->
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/pause"-->
|
||||
<!-- android:title="@string/pause"-->
|
||||
<!-- android:icon="@drawable/exomedia_ic_pause_white"-->
|
||||
<!-- app:showAsAction="ifRoom" />-->
|
||||
<group>
|
||||
<item
|
||||
android:id="@+id/pause"
|
||||
android:title="@string/pause"
|
||||
android:icon="@drawable/exomedia_ic_pause_white"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<!-- <item-->
|
||||
<!-- android:id="@+id/resume"-->
|
||||
<!-- android:title="@string/resume"-->
|
||||
<!-- android:icon="@drawable/exomedia_ic_play_arrow_white"-->
|
||||
<!-- app:showAsAction="ifRoom" />-->
|
||||
<!-- </group>-->
|
||||
<item
|
||||
android:id="@+id/resume"
|
||||
android:title="@string/resume"
|
||||
android:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:showAsAction="ifRoom" />
|
||||
</group>
|
||||
|
||||
<item
|
||||
android:id="@+id/cancel"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@
|
|||
app:showAsAction="collapseActionView|always"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/filters"
|
||||
android:title="Filters"
|
||||
android:icon="@drawable/baseline_filter_alt_24"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/download_queue"
|
||||
android:title="@string/download_queue"
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@
|
|||
<string name="use_alarm_manager">Use AlarmManager instead of WorkManager for Scheduling</string>
|
||||
<string name="use_alarm_manager_summary">Enable this if WorkManager is restricted by your device vendor or scheduled jobs are not too precise</string>
|
||||
<string name="use_item_url_not_playlist">Use Item URL instead of Playlist URL</string>
|
||||
<string name="use_item_url_not_playlist_summary">Useful for Libretube offline playlists that are not recognised by yt-dlp. Playlist metadata wont be embedded with this enabled</string>
|
||||
<string name="use_item_url_not_playlist_summary">Playlist metadata wont be embedded with this enabled</string>
|
||||
<string name="recode_video">Recode Video</string>
|
||||
<string name="recode_video_summary">Recodes the video file to the specified video format</string>
|
||||
<string name="daily">Daily</string>
|
||||
|
|
@ -422,4 +422,11 @@
|
|||
<string name="playlist_as_album">Use Playlist Name as Album Metadata</string>
|
||||
<string name="playlist_as_album_summary">If Album Metadata is not present, use playlist name instead</string>
|
||||
<string name="data_fetching_extractor_youtube">Data Fetching Extractor (YouTube)</string>
|
||||
<string name="pause_all">Pause All</string>
|
||||
<string name="resume_all">Resume All</string>
|
||||
<string name="package_name">Package Name</string>
|
||||
<string name="preferred_command_template">Preferred Command Template</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="deleted">Deleted</string>
|
||||
<string name="websites">Websites</string>
|
||||
</resources>
|
||||
|
|
@ -102,6 +102,13 @@
|
|||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_source" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue=""
|
||||
android:icon="@drawable/ic_code"
|
||||
android:key="youtube_po_token"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
android:title="PO Token" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_dns"
|
||||
app:key="piped_instance"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@
|
|||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/about" >
|
||||
<Preference
|
||||
app:icon="@drawable/baseline_exit_to_app_24"
|
||||
app:key="package_name"
|
||||
app:title="@string/package_name" />
|
||||
<Preference
|
||||
app:icon="@drawable/ic_discord"
|
||||
app:key="discord"
|
||||
|
|
|
|||
14
build.gradle
14
build.gradle
|
|
@ -15,7 +15,8 @@ buildscript {
|
|||
// supports java 1.6
|
||||
commonsCompressVer = '1.12'
|
||||
youtubedlAndroidVer = "ed652962e5"
|
||||
youtubedlAndroidVerNoPyCrypto = "97bee6c0e8"
|
||||
youtubedlAndroidVerNoPyCrypto = "97bee6c0e8"
|
||||
youtubedlAndroidVerMine = "8831745ea3"
|
||||
oldytdlAndroidVer = "0.14.0"
|
||||
workVer = "2.9.1"
|
||||
composeVer = '1.4.2'
|
||||
|
|
@ -23,8 +24,8 @@ buildscript {
|
|||
coroutineVer = '1.7.3'
|
||||
retrofitVer = "2.9.0"
|
||||
kodeinVer = "7.16.0"
|
||||
navVer = "2.7.7"
|
||||
media3_version = "1.4.0"
|
||||
navVer = "2.8.3"
|
||||
media3_version = "1.4.1"
|
||||
agp_version = '8.1.0'
|
||||
agp_version1 = '7.4.2'
|
||||
roomVer = '2.5.2'
|
||||
|
|
@ -38,16 +39,15 @@ buildscript {
|
|||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '8.4.0' apply false
|
||||
id 'com.android.library' version '8.4.0' apply false
|
||||
id 'com.android.application' version '8.5.2' apply false
|
||||
id 'com.android.library' version '8.5.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.8.10" apply false
|
||||
id "org.jetbrains.kotlin.plugin.parcelize" version "1.8.10" apply false
|
||||
id 'com.android.test' version '8.4.0' apply false
|
||||
id 'com.android.test' version '8.5.2' apply false
|
||||
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
|
||||
}
|
||||
|
||||
|
||||
tasks.register('clean', Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
# What's Changed
|
||||
|
||||
- newpipe extractor general fixes
|
||||
- fixed newpipe formats not working
|
||||
- removed piped because its more annoying than anything, it doesnt work currently
|
||||
*shortest changelog wr*
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#Wed Oct 18 18:36:39 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
Loading…
Reference in a new issue