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