tmp commit
This commit is contained in:
parent
738ebb8558
commit
d62dc7f9dc
64 changed files with 1624 additions and 837 deletions
|
|
@ -10,13 +10,13 @@ plugins {
|
|||
def properties = new Properties()
|
||||
def versionMajor = 1
|
||||
def versionMinor = 7
|
||||
def versionPatch = 3
|
||||
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
|
||||
def versionPatch = 4
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def versionExt = ".${versionBuild}"
|
||||
|
||||
//if (versionBuild > 0){
|
||||
// versionExt = ".${versionBuild}-beta"
|
||||
//}
|
||||
if (versionBuild > 0){
|
||||
versionExt = ".${versionBuild}-beta"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.deniscerri.ytdlnis'
|
||||
|
|
@ -144,7 +144,7 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||
implementation 'androidx.compose.ui:ui-android:1.5.4'
|
||||
implementation 'androidx.compose.ui:ui-android:1.6.2'
|
||||
testImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
||||
|
|
@ -168,7 +168,7 @@ dependencies {
|
|||
implementation 'androidx.paging:paging-runtime-ktx:3.2.1'
|
||||
implementation "androidx.room:room-paging:$roomVer"
|
||||
androidTestImplementation "androidx.room:room-testing:$roomVer"
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
||||
implementation "androidx.compose.runtime:runtime:$composeVer"
|
||||
androidTestImplementation 'com.google.truth:truth:1.1.5'
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ dependencies {
|
|||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
|
||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||
|
||||
// For media playback using ExoPlayer
|
||||
|
|
@ -196,6 +196,6 @@ dependencies {
|
|||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.0-beta01'
|
||||
implementation 'androidx.compose.material3:material3-android:1.2.0'
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,23 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
|
||||
android:maxSdkVersion="32"/>
|
||||
<uses-permission
|
||||
android:name="android.permission.SCHEDULE_EXACT_ALARM"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
|
||||
|
||||
|
|
@ -71,6 +75,152 @@
|
|||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="youtube.com" />
|
||||
<data android:host="youtube.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="twitter.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="x.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="soundcloud.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="facebook.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="instagram.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="tiktok.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="bilibili.tv" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtu.be" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="twitch.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="reddit.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="pinterest.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="music.youtube.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="m.youtube.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="vk.com" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="rutube.com" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".receiver.ShareFileActivity"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.os.Looper
|
|||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.util.CrashListener
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.aria2c.Aria2c
|
||||
import com.yausername.ffmpeg.FFmpeg
|
||||
|
|
@ -25,7 +26,6 @@ class App : Application() {
|
|||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this@App)
|
||||
setDefaultValues()
|
||||
|
||||
applicationScope = CoroutineScope(SupervisorJob())
|
||||
applicationScope.launch((Dispatchers.IO)) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import android.graphics.drawable.ColorDrawable
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
|
|
@ -41,6 +43,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
|||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||
import com.deniscerri.ytdlnis.ui.HomeFragment
|
||||
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
|
||||
import com.deniscerri.ytdlnis.util.CrashListener
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
|
|
@ -56,6 +59,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -81,6 +85,7 @@ class MainActivity : BaseActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
CrashListener(this).registerExceptionHandler()
|
||||
ThemeUtil.updateTheme(this)
|
||||
window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
|
@ -90,7 +95,11 @@ class MainActivity : BaseActivity() {
|
|||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
if (preferences.getBoolean("incognito", false)) resultViewModel.deleteAll()
|
||||
if (preferences.getBoolean("incognito", false)) {
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
askPermissions()
|
||||
checkUpdate()
|
||||
|
|
@ -211,13 +220,15 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
if (navigationView is NavigationBarView){
|
||||
when(destination.id){
|
||||
R.id.homeFragment, R.id.historyFragment, R.id.moreFragment -> showBottomNavigation()
|
||||
else -> hideBottomNavigation()
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (navigationView is NavigationBarView){
|
||||
when(destination.id){
|
||||
R.id.homeFragment, R.id.historyFragment, R.id.moreFragment -> showBottomNavigation()
|
||||
else -> hideBottomNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
navigationView.visibilityChanged {
|
||||
|
|
@ -288,9 +299,16 @@ class MainActivity : BaseActivity() {
|
|||
findViewById<FragmentContainerView>(R.id.frame_layout).updateLayoutParams {
|
||||
this.width = LayoutParams.MATCH_PARENT
|
||||
}
|
||||
navigationView.animate()?.translationX(-navigationView.width.toFloat())?.setDuration(300)?.withEndAction {
|
||||
navigationView.visibility = View.GONE
|
||||
}?.start()
|
||||
|
||||
if (resources.getBoolean(R.bool.is_right_to_left)){
|
||||
navigationView.animate()?.translationX(navigationView.width.toFloat())?.setDuration(300)?.withEndAction {
|
||||
navigationView.visibility = View.GONE
|
||||
}?.start()
|
||||
}else{
|
||||
navigationView.animate()?.translationX(-navigationView.width.toFloat())?.setDuration(300)?.withEndAction {
|
||||
navigationView.visibility = View.GONE
|
||||
}?.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ interface DownloadDao {
|
|||
fun getProcessingDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloadsList() : List<DownloadItem>
|
||||
suspend fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused','PausedReQueued')")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
|
|
@ -250,4 +250,7 @@ interface DownloadDao {
|
|||
|
||||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in (:statuses) ORDER BY id DESC")
|
||||
fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List<String>) : List<Long>
|
||||
|
||||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
fun getQueuedIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long>
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
@Dao
|
||||
interface ResultDao {
|
||||
@Query("SELECT * FROM results")
|
||||
@Query("SELECT * FROM results order by id")
|
||||
fun getResults() : Flow<List<ResultItem>>
|
||||
|
||||
@Query("SELECT COUNT(id) FROM results")
|
||||
|
|
@ -49,4 +49,7 @@ interface ResultDao {
|
|||
@Query("SELECT * FROM results WHERE url=:url LIMIT 1")
|
||||
fun getResultByURL(url: String) : ResultItem?
|
||||
|
||||
@Query("SELECT * from results WHERE id > :item1 AND id < :item2 ORDER BY id")
|
||||
fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem>
|
||||
|
||||
}
|
||||
|
|
@ -24,12 +24,12 @@ interface TerminalDao {
|
|||
fun getActiveTerminalsCount() : Int
|
||||
|
||||
@Query("UPDATE terminalDownloads set log=:l where id=:id")
|
||||
fun updateTerminalLog(l: String, id: Long)
|
||||
suspend fun updateTerminalLog(l: String, id: Long)
|
||||
|
||||
@Query("SELECT * FROM terminalDownloads WHERE id=:id LIMIT 1")
|
||||
fun getTerminalById(id: Long) : TerminalItem?
|
||||
@Transaction
|
||||
fun updateLog(line: String, id: Long){
|
||||
suspend fun updateLog(line: String, id: Long){
|
||||
val t = getTerminalById(id) ?: return
|
||||
val log = t.log ?: ""
|
||||
updateTerminalLog(log.appendLineToLog(line), id)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,26 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
|||
return filtered
|
||||
}
|
||||
|
||||
fun getRecordsBetweenTwoItems(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) : List<HistoryItem> {
|
||||
var filtered = when(sortType){
|
||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
||||
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, format, site, sort.toString())
|
||||
HistorySortType.FILESIZE -> {
|
||||
val items = historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
when(sort){
|
||||
SORTING.DESC -> items.sortedByDescending { it.format.filesize }
|
||||
SORTING.ASC -> items.sortedBy { it.format.filesize }
|
||||
}
|
||||
}
|
||||
}
|
||||
if(notDeleted){
|
||||
filtered = filtered.filter { it.downloadPath.any { it2 -> FileUtil.exists(it2) } }
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
suspend fun insert(item: HistoryItem){
|
||||
historyDao.insert(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
search(inputQuery, resetResults, addToResults)
|
||||
}
|
||||
SourceType.YT_DLP -> {
|
||||
getDefault(inputQuery, resetResults, addToResults)
|
||||
getDefault(inputQuery, resetResults, addToResults, singleItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,4 +179,15 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi
|
|||
}
|
||||
}
|
||||
|
||||
fun getRecordsBetweenTwoItems(item1: Long, item2: Long) : List<CommandTemplate> {
|
||||
val filtered = repository.getFiltered(queryFilter.value!!, sortType.value!!, sortOrder.value!!)
|
||||
val firstIndex = filtered.indexOfFirst { it.id == item1 }
|
||||
val secondIndex = filtered.indexOfFirst { it.id == item2 }
|
||||
if(firstIndex > secondIndex) {
|
||||
return filtered.filterIndexed { index, _ -> index in (secondIndex + 1) until firstIndex }
|
||||
}else{
|
||||
return filtered.filterIndexed { index, _ -> index in (firstIndex + 1) until secondIndex }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
package com.deniscerri.ytdlnis.database.viewmodel
|
||||
|
||||
import android.R.attr.description
|
||||
import android.R.attr.duration
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteDatabase.OPEN_READONLY
|
||||
import android.database.sqlite.SQLiteDatabase.OPEN_READWRITE
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.webkit.CookieManager
|
||||
|
|
@ -18,12 +22,14 @@ import com.deniscerri.ytdlnis.database.DBManager
|
|||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
||||
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
||||
import com.deniscerri.ytdlnis.util.Extensions.convertNetscapeToSetCookie
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.util.Date
|
||||
|
||||
|
||||
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
private val repository: CookieRepository
|
||||
val items: LiveData<List<CookieItem>>
|
||||
|
|
@ -33,6 +39,15 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
"# WebView Generated by the YTDLnis app\n" +
|
||||
"# This is a generated file! Do not edit."
|
||||
|
||||
private val projection = arrayOf(
|
||||
CookieObject.HOST,
|
||||
CookieObject.EXPIRY,
|
||||
CookieObject.PATH,
|
||||
CookieObject.NAME,
|
||||
CookieObject.VALUE,
|
||||
CookieObject.SECURE
|
||||
)
|
||||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).cookieDao
|
||||
repository = CookieRepository(dao)
|
||||
|
|
@ -86,14 +101,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
dbPath.absolutePath, null, OPEN_READONLY
|
||||
)
|
||||
|
||||
val projection = arrayOf(
|
||||
CookieObject.HOST,
|
||||
CookieObject.EXPIRY,
|
||||
CookieObject.PATH,
|
||||
CookieObject.NAME,
|
||||
CookieObject.VALUE,
|
||||
CookieObject.SECURE
|
||||
)
|
||||
|
||||
val cookieList = mutableListOf<WebViewActivity.CookieItem>()
|
||||
db.query(
|
||||
"cookies", projection, null, null, null, null, null
|
||||
|
|
@ -108,20 +116,16 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
|
||||
|
||||
val host = if (hostKey[0] != '.') ".$hostKey" else hostKey
|
||||
|
||||
val requiredHost = URL(url).host
|
||||
if(host.contains(requiredHost)){
|
||||
cookieList.add(
|
||||
WebViewActivity.CookieItem(
|
||||
domain = host,
|
||||
name = name,
|
||||
value = value,
|
||||
path = path,
|
||||
secure = secure,
|
||||
expiry = expiry
|
||||
)
|
||||
cookieList.add(
|
||||
WebViewActivity.CookieItem(
|
||||
domain = host,
|
||||
name = name,
|
||||
value = value,
|
||||
path = path,
|
||||
secure = secure,
|
||||
expiry = expiry
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
close()
|
||||
}
|
||||
|
|
@ -134,14 +138,17 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
fun updateCookiesFile() = viewModelScope.launch(Dispatchers.IO) {
|
||||
val cookies = repository.getAll()
|
||||
val cookieTXT = StringBuilder(cookieHeader)
|
||||
val cookieFile = File(application.cacheDir, "cookies.txt")
|
||||
if (cookies.isEmpty()) cookieFile.apply { writeText("") }
|
||||
cookies.forEach {
|
||||
it.content.lines().forEach {line ->
|
||||
if (! cookieTXT.contains(line)) cookieTXT.append(it.content)
|
||||
FileUtil.getCookieFile(application, true){ c ->
|
||||
val cookieFile = File(c)
|
||||
if (cookies.isEmpty()) cookieFile.apply { writeText("") }
|
||||
cookies.forEach {
|
||||
it.content.lines().forEach {line ->
|
||||
if (! cookieTXT.contains(line)) cookieTXT.append(it.content)
|
||||
}
|
||||
}
|
||||
cookieFile.apply { writeText(cookieTXT.toString()) }
|
||||
}
|
||||
cookieFile.apply { writeText(cookieTXT.toString()) }
|
||||
|
||||
}
|
||||
|
||||
suspend fun importFromClipboard() {
|
||||
|
|
@ -170,11 +177,14 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
try{
|
||||
val clipboard: ClipboardManager =
|
||||
application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val cookieFile = File(application.cacheDir, "cookies.txt")
|
||||
if (! cookieFile.exists()) updateCookiesFile()
|
||||
cookieFile.readText().let {
|
||||
clipboard.setText(it)
|
||||
FileUtil.getCookieFile(application, true){c ->
|
||||
val cookieFile = File(c)
|
||||
if (! cookieFile.exists()) updateCookiesFile()
|
||||
cookieFile.readText().let {
|
||||
clipboard.setText(it)
|
||||
}
|
||||
}
|
||||
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
@ -182,12 +192,14 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
|
||||
fun exportToFile(exported: (File?) -> Unit) {
|
||||
try{
|
||||
val cookieFile = File(application.cacheDir, "cookies.txt")
|
||||
if (!cookieFile.exists()) updateCookiesFile()
|
||||
FileUtil.getCookieFile(application, true){ c ->
|
||||
val cookieFile = File(c)
|
||||
if (!cookieFile.exists()) updateCookiesFile()
|
||||
|
||||
val downloads = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis_Cookies.txt")
|
||||
val file = cookieFile.copyTo(downloads, true)
|
||||
exported(file)
|
||||
val downloads = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "YTDLnis_Cookies.txt")
|
||||
val file = cookieFile.copyTo(downloads, true)
|
||||
exported(file)
|
||||
}
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
exported(null)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.database.viewmodel
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
|
|
@ -179,10 +181,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
if (sharedPreferences.getBoolean("incognito", false)){
|
||||
if (item.status == DownloadRepository.Status.Cancelled.toString() || item.status == DownloadRepository.Status.Error.toString()){
|
||||
repository.delete(item.id)
|
||||
return
|
||||
}
|
||||
}else{
|
||||
repository.update(item)
|
||||
}
|
||||
|
||||
repository.update(item)
|
||||
}
|
||||
|
||||
fun getItemByID(id: Long) : DownloadItem {
|
||||
|
|
@ -461,7 +464,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
sharedPreferences.getString("audio_language", "")?.apply {
|
||||
if (this.isNotBlank()){
|
||||
requirements.add { it: Format -> it.lang == this }
|
||||
requirements.add { it: Format -> it.lang?.contains(this) == true }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -476,6 +479,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
|
||||
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.split("_")[0].dropLast(1)) }
|
||||
requirements.add { it: Format -> "^(${videoCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)}
|
||||
requirements.add { it: Format -> it.acodec == "none" || it.acodec == "" }
|
||||
return requirements
|
||||
}
|
||||
|
||||
|
|
@ -948,5 +952,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return dao.getIDsBetweenTwoItems(item1, item2, statuses)
|
||||
}
|
||||
|
||||
fun getQueuedIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long> {
|
||||
return dao.getQueuedIDsBetweenTwoItems(item1, item2)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -117,4 +117,15 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
|||
repository.clearDeletedHistory()
|
||||
}
|
||||
|
||||
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 firstIndex = filtered.indexOfFirst { it.id == item1 }
|
||||
val secondIndex = filtered.indexOfFirst { it.id == item2 }
|
||||
if(firstIndex > secondIndex) {
|
||||
return filtered.filterIndexed { index, _ -> index in (secondIndex + 1) until firstIndex }
|
||||
}else{
|
||||
return filtered.filterIndexed { index, _ -> index in (firstIndex + 1) until secondIndex }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@ import androidx.preference.PreferenceManager
|
|||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
||||
|
|
@ -37,6 +39,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
val items : LiveData<List<ResultItem>>
|
||||
private val infoUtil: InfoUtil
|
||||
private val notificationUtil: NotificationUtil
|
||||
private val dao: ResultDao
|
||||
data class ResultsUiState(
|
||||
var processing: Boolean,
|
||||
var errorMessage: Pair<Int, String>?,
|
||||
|
|
@ -67,7 +70,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
val dao = DBManager.getInstance(application).resultDao
|
||||
dao = DBManager.getInstance(application).resultDao
|
||||
repository = ResultRepository(dao, getApplication<Application>().applicationContext)
|
||||
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
|
||||
items = repository.allResults.asLiveData()
|
||||
|
|
@ -156,7 +159,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
}
|
||||
}
|
||||
|
||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
suspend fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteAll()
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +222,8 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
viewModelScope.launch(Dispatchers.IO) {
|
||||
updatingData.emit(false)
|
||||
updatingData.value = false
|
||||
updateResultData.emit(mutableListOf())
|
||||
updateResultData.emit(null)
|
||||
updateResultData.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,53 +235,66 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
updatingData.emit(false)
|
||||
updatingData.value = false
|
||||
updateResultData.emit(null)
|
||||
updateResultData.value = null
|
||||
}
|
||||
|
||||
suspend fun cancelUpdateFormatsItemData(){
|
||||
updateFormatsResultDataJob?.cancel(CancellationException())
|
||||
updatingFormats.emit(false)
|
||||
updatingFormats.value = false
|
||||
updateFormatsResultData.emit(null)
|
||||
updateFormatsResultData.value = null
|
||||
updateFormatsResultDataJob?.cancel(CancellationException())
|
||||
}
|
||||
|
||||
suspend fun updateFormatItemData(result: ResultItem){
|
||||
if (updateFormatsResultDataJob == null || updateFormatsResultDataJob?.isCancelled == true || updateFormatsResultDataJob?.isCompleted == true) {
|
||||
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
updatingFormats.emit(true)
|
||||
val formats = kotlin.runCatching {
|
||||
infoUtil.getFormats(result.url)
|
||||
}.onFailure { e ->
|
||||
if (e !is kotlinx.coroutines.CancellationException){
|
||||
uiState.update {it.copy(
|
||||
processing = false,
|
||||
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
||||
actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
|
||||
)}
|
||||
try {
|
||||
val formats = infoUtil.getFormats(result.url)
|
||||
updatingFormats.emit(false)
|
||||
formats.apply {
|
||||
if (formats.isNotEmpty() && updateFormatsResultDataJob?.isCancelled == false) {
|
||||
getItemByURL(result.url)?.apply {
|
||||
this.formats = formats.toMutableList()
|
||||
update(this)
|
||||
}
|
||||
updateFormatsResultData.emit(formats.toMutableList())
|
||||
}
|
||||
}
|
||||
}catch(e: Exception) {
|
||||
if (e !is kotlinx.coroutines.CancellationException) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
processing = false,
|
||||
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
||||
actions = mutableListOf(
|
||||
Pair(
|
||||
R.string.copy_log,
|
||||
ResultAction.COPY_LOG
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
Log.e(tag, e.toString())
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
updatingFormats.emit(false)
|
||||
|
||||
formats?.apply {
|
||||
if (formats.isNotEmpty() && isActive) {
|
||||
getItemByURL(result.url)?.apply {
|
||||
this.formats = formats.toMutableList()
|
||||
update(this)
|
||||
}
|
||||
updateFormatsResultData.emit(formats.toMutableList())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
updateFormatsResultDataJob?.start()
|
||||
updateFormatsResultDataJob?.invokeOnCompletion {
|
||||
if (it != null){
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
updatingFormats.emit(false)
|
||||
updateFormatsResultData.emit(mutableListOf())
|
||||
updatingFormats.value = false
|
||||
updateFormatsResultData.emit(null)
|
||||
updateFormatsResultData.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem>{
|
||||
return dao.getResultsBetweenTwoItems(item1, item2)
|
||||
}
|
||||
}
|
||||
|
|
@ -37,12 +37,14 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.models.SearchSuggestionItem
|
||||
import com.deniscerri.ytdlnis.database.models.SearchSuggestionType
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
|
||||
import com.deniscerri.ytdlnis.ui.adapter.SearchSuggestionsAdapter
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.Extensions.toListString
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
|
|
@ -99,7 +101,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
private var searchSuggestionsRecyclerView: RecyclerView? = null
|
||||
private var uiHandler: Handler? = null
|
||||
private var resultsList: List<ResultItem?>? = null
|
||||
private var selectedObjects: ArrayList<ResultItem>? = null
|
||||
private lateinit var selectedObjects: ArrayList<ResultItem>
|
||||
private var quickLaunchSheet = false
|
||||
private var sharedPreferences: SharedPreferences? = null
|
||||
private var actionMode: ActionMode? = null
|
||||
|
|
@ -115,6 +117,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
mainActivity = activity as MainActivity?
|
||||
quickLaunchSheet = false
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
selectedObjects = arrayListOf()
|
||||
return fragmentView
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +139,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
//initViews
|
||||
shimmerCards = view.findViewById(R.id.shimmer_results_framelayout)
|
||||
searchBar = view.findViewById(R.id.search_bar)
|
||||
searchView = view.findViewById(R.id.search_view)
|
||||
appBarLayout = view.findViewById(R.id.home_appbarlayout)
|
||||
|
|
@ -160,6 +162,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
recyclerView?.adapter = homeAdapter
|
||||
recyclerView?.enableFastScroll()
|
||||
|
||||
shimmerCards = view.findViewById(R.id.shimmer_results_framelayout)
|
||||
|
||||
|
||||
|
||||
searchSuggestionsAdapter = SearchSuggestionsAdapter(
|
||||
this,
|
||||
|
|
@ -214,8 +219,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
|
||||
if (inputQueries != null) {
|
||||
resultViewModel.deleteAll()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
resultViewModel.deleteAll()
|
||||
resultViewModel.parseQueries(inputQueries!!){}
|
||||
inputQueries = null
|
||||
}
|
||||
|
|
@ -606,9 +611,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
resultViewModel.addSearchQueryToHistory(q)
|
||||
}
|
||||
}
|
||||
resultViewModel.deleteAll()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
Thread.sleep(300)
|
||||
resultViewModel.deleteAll()
|
||||
if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){
|
||||
if (queryList.size == 1 && Patterns.WEB_URL.matcher(queryList.first()).matches()){
|
||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||
|
|
@ -683,23 +687,28 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
|
||||
override fun onCardClick(videoURL: String, add: Boolean) {
|
||||
val item = resultsList?.find { it?.url == videoURL }
|
||||
if (add) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
}else{
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
}
|
||||
} else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
lifecycleScope.launch {
|
||||
val item = resultsList?.find { it?.url == videoURL }
|
||||
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
actionMode?.apply {
|
||||
if (add) selectedObjects.add(item!!)
|
||||
else selectedObjects.remove(item!!)
|
||||
|
||||
if (selectedObjects.size == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
actionMode?.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects.size == 2){
|
||||
val selectedIDs = selectedObjects.sortedBy { it.id }
|
||||
val resultsInMiddle = withContext(Dispatchers.IO){
|
||||
resultViewModel.getResultsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
|
||||
}.toMutableList()
|
||||
this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onCardDetailsClick(videoURL: String) {
|
||||
|
|
@ -739,7 +748,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.main_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
searchBar!!.isEnabled = false
|
||||
searchBar!!.menu.forEach { it.isEnabled = false }
|
||||
(activity as MainActivity).disableBottomNavigation()
|
||||
|
|
@ -758,15 +767,32 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
item: MenuItem?
|
||||
): Boolean {
|
||||
return when (item!!.itemId) {
|
||||
R.id.select_between -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedIDs = selectedObjects.sortedBy { it.id }
|
||||
val resultsInMiddle = withContext(Dispatchers.IO){
|
||||
resultViewModel.getResultsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
|
||||
}.toMutableList()
|
||||
if (resultsInMiddle.isNotEmpty()){
|
||||
selectedObjects.addAll(resultsInMiddle)
|
||||
homeAdapter?.checkMultipleItems(selectedObjects.map { it.url })
|
||||
actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}"
|
||||
}
|
||||
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.delete_results -> {
|
||||
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
if (selectedObjects?.size == resultsList?.size){
|
||||
resultViewModel.deleteAll()
|
||||
if (selectedObjects.size == resultsList?.size){
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
resultViewModel.deleteAll()
|
||||
}
|
||||
}else{
|
||||
resultViewModel.deleteSelected(selectedObjects!!.toList())
|
||||
resultViewModel.deleteSelected(selectedObjects.toList())
|
||||
}
|
||||
clearCheckedItems()
|
||||
actionMode?.finish()
|
||||
|
|
@ -776,14 +802,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
R.id.download -> {
|
||||
lifecycleScope.launch {
|
||||
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
|
||||
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects.size == 1) {
|
||||
showSingleDownloadSheet(
|
||||
selectedObjects!![0],
|
||||
downloadViewModel.getDownloadType(url = selectedObjects!![0].url)
|
||||
selectedObjects[0],
|
||||
downloadViewModel.getDownloadType(url = selectedObjects[0].url)
|
||||
)
|
||||
}else{
|
||||
val downloadList = withContext(Dispatchers.IO){
|
||||
downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
|
||||
downloadViewModel.turnResultItemsToDownloadItems(selectedObjects)
|
||||
}
|
||||
|
||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||
|
|
@ -803,8 +829,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
R.id.select_all -> {
|
||||
homeAdapter?.checkAll(resultsList)
|
||||
selectedObjects?.clear()
|
||||
resultsList?.forEach { selectedObjects?.add(it!!) }
|
||||
selectedObjects.clear()
|
||||
resultsList?.forEach { selectedObjects.add(it!!) }
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
|
|
@ -812,11 +838,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
homeAdapter?.invertSelected(resultsList)
|
||||
val invertedList = arrayListOf<ResultItem>()
|
||||
resultsList?.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it!!)
|
||||
if (!selectedObjects.contains(it)) invertedList.add(it!!)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
selectedObjects.clear()
|
||||
selectedObjects.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
|
|
@ -836,10 +862,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
|
||||
private fun clearCheckedItems(){
|
||||
homeAdapter?.clearCheckedItems()
|
||||
selectedObjects?.forEach {
|
||||
selectedObjects.forEach {
|
||||
homeAdapter?.notifyItemChanged(resultsList!!.indexOf(it))
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects.clear()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,38 +1,46 @@
|
|||
package com.deniscerri.ytdlnis.ui.adapter
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<Pair<DownloadItem, Long?>, AlreadyExistsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
DIFF_CALLBACK
|
||||
).build()) {
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val cardView: ConstraintLayout
|
||||
val cardView: MaterialCardView
|
||||
|
||||
init {
|
||||
cardView = itemView as ConstraintLayout
|
||||
cardView = itemView as MaterialCardView
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,38 +51,78 @@ class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: A
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position) ?: return
|
||||
val alreadyExistsItem = holder.cardView
|
||||
alreadyExistsItem.tag = item.first.id.toString()
|
||||
alreadyExistsItem.popup()
|
||||
val alreadyExistsItem = getItem(position) ?: return
|
||||
val item = alreadyExistsItem.first
|
||||
val historyID = alreadyExistsItem.second
|
||||
|
||||
val editBtn = alreadyExistsItem.findViewById<Button>(R.id.already_exists_edit)
|
||||
val deleteBtn = alreadyExistsItem.findViewById<Button>(R.id.already_exists_delete)
|
||||
val card = holder.cardView
|
||||
card.tag = item.id.toString()
|
||||
card.popup()
|
||||
|
||||
val ttle = alreadyExistsItem.findViewById<Button>(R.id.already_exists_title)
|
||||
ttle.text = item.first.title
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
// THUMBNAIL ----------------------------------
|
||||
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")
|
||||
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item.thumb) }
|
||||
|
||||
val duration = card.findViewById<TextView>(R.id.duration)
|
||||
duration.text = item.duration
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
var title = item.title
|
||||
if (title.length > 100) {
|
||||
title = title.substring(0, 40) + "..."
|
||||
}
|
||||
itemTitle.text = title.ifEmpty { item.url }
|
||||
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
else formatNote!!.text = item.format.format_note.uppercase()
|
||||
|
||||
val codec = card.findViewById<TextView>(R.id.codec)
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
item.format.encoding.uppercase()
|
||||
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||
item.format.vcodec.uppercase()
|
||||
} else {
|
||||
item.format.acodec.uppercase()
|
||||
}
|
||||
if (codecText == "" || codecText == "none"){
|
||||
codec.visibility = View.GONE
|
||||
}else{
|
||||
codec.visibility = View.VISIBLE
|
||||
codec.text = codecText
|
||||
}
|
||||
|
||||
ttle.setOnLongClickListener {
|
||||
onItemClickListener.onDeleteItem(item.first, position, item.second)
|
||||
val fileSize = card.findViewById<TextView>(R.id.file_size)
|
||||
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
|
||||
else fileSize.text = fileSizeReadable
|
||||
|
||||
val editBtn = card.findViewById<Button>(R.id.already_exists_edit)
|
||||
val deleteBtn = card.findViewById<Button>(R.id.already_exists_delete)
|
||||
|
||||
card.setOnLongClickListener {
|
||||
onItemClickListener.onDeleteItem(item, position, historyID)
|
||||
true
|
||||
}
|
||||
|
||||
editBtn.setOnClickListener {
|
||||
onItemClickListener.onEditItem(item.first, position)
|
||||
onItemClickListener.onEditItem(item, position)
|
||||
}
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
onItemClickListener.onDeleteItem(item.first, position, item.second)
|
||||
onItemClickListener.onDeleteItem(item, position, historyID)
|
||||
}
|
||||
|
||||
ttle.setOnClickListener(null)
|
||||
if (item.second != null){
|
||||
ttle.setOnClickListener {
|
||||
onItemClickListener.onShowHistoryItem(item.second!!)
|
||||
card.setOnClickListener(null)
|
||||
if (historyID != null){
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onShowHistoryItem(historyID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -181,6 +181,13 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
onItemClickListener.onCardSelect(itemID, card.isChecked)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkMultipleItems(list: List<Long>){
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(list)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onCardClick(itemID: Long, isPresent: Boolean)
|
||||
fun onButtonClick(itemID: Long, isPresent: Boolean)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Handler
|
||||
|
|
@ -22,19 +23,18 @@ import com.deniscerri.ytdlnis.util.Extensions.popup
|
|||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.squareup.picasso.Picasso
|
||||
|
||||
|
||||
class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, HomeAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
DIFF_CALLBACK
|
||||
).build()) {
|
||||
private val checkedVideos: ArrayList<String>
|
||||
private val checkedItems: ArrayList<String>
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
checkedVideos = ArrayList()
|
||||
checkedItems = ArrayList()
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
|
|
@ -132,7 +132,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
// videoBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video));
|
||||
// }
|
||||
// }
|
||||
if (checkedVideos.contains(videoURL)) {
|
||||
if (checkedItems.contains(videoURL)) {
|
||||
card.isChecked = true
|
||||
card.strokeWidth = 5
|
||||
} else {
|
||||
|
|
@ -145,7 +145,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
true
|
||||
}
|
||||
card.setOnClickListener {
|
||||
if (checkedVideos.size > 0) {
|
||||
if (checkedItems.size > 0) {
|
||||
checkCard(card, videoURL)
|
||||
}else{
|
||||
onItemClickListener.onCardDetailsClick(videoURL)
|
||||
|
|
@ -156,10 +156,10 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
private fun checkCard(card: MaterialCardView, videoURL: String) {
|
||||
if (card.isChecked) {
|
||||
card.strokeWidth = 0
|
||||
checkedVideos.remove(videoURL)
|
||||
checkedItems.remove(videoURL)
|
||||
} else {
|
||||
card.strokeWidth = 5
|
||||
checkedVideos.add(videoURL)
|
||||
checkedItems.add(videoURL)
|
||||
}
|
||||
card.isChecked = !card.isChecked
|
||||
onItemClickListener.onCardClick(videoURL, card.isChecked)
|
||||
|
|
@ -173,23 +173,30 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
}
|
||||
|
||||
fun checkAll(items: List<ResultItem?>?){
|
||||
checkedVideos.clear()
|
||||
checkedVideos.addAll(items!!.map { it!!.url })
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(items!!.map { it!!.url })
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkMultipleItems(list: List<String>){
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(list)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun invertSelected(items: List<ResultItem?>?){
|
||||
val invertedList = mutableListOf<String>()
|
||||
items?.forEach {
|
||||
if (!checkedVideos.contains(it!!.url)) invertedList.add(it.url)
|
||||
if (!checkedItems.contains(it!!.url)) invertedList.add(it.url)
|
||||
}
|
||||
checkedVideos.clear()
|
||||
checkedVideos.addAll(invertedList)
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(invertedList)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun clearCheckedItems(){
|
||||
checkedVideos.clear()
|
||||
checkedItems.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.loadThumbnail
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -42,10 +43,10 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
|||
}
|
||||
|
||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||
val cardView: ConstraintLayout
|
||||
val cardView: MaterialCardView
|
||||
|
||||
init {
|
||||
cardView = itemView.findViewById(R.id.playlist_card_constraintLayout)
|
||||
cardView = itemView.findViewById(R.id.playlist_card)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,14 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
|||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun checkMultipleItems(list: List<Long>){
|
||||
checkedItems.clear()
|
||||
checkedItems.addAll(list)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun invertSelected(items: List<CommandTemplate?>?){
|
||||
val invertedList = mutableListOf<Long>()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -69,7 +70,10 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
behavior.peekHeight = displayMetrics.heightPixels - 400
|
||||
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = displayMetrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||
|
|
|
|||
|
|
@ -438,8 +438,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
}
|
||||
|
||||
//replace existing chip to enable click events
|
||||
if (selectedCuts.contains(it.title)){
|
||||
val idx = selectedCuts.indexOf(it.title)
|
||||
val idx = selectedCuts.indexOfFirst { c -> c.contains(it.title) }
|
||||
if (idx > -1){
|
||||
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text == it.title }
|
||||
chipGroup.removeView(chipForDeletion)
|
||||
createChapterChip(it, idx)
|
||||
|
|
@ -456,7 +456,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
player.seekTo(0)
|
||||
suggestedChips.children.apply {
|
||||
this.forEach {
|
||||
it.isVisible = ! selectedCuts.contains((it as Chip).text)
|
||||
it.isVisible = ! selectedCuts.any { c -> c.contains((it as Chip).text) }
|
||||
}
|
||||
suggestedLabel.isVisible = this.any { it.isVisible }
|
||||
}
|
||||
|
|
@ -527,15 +527,17 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
|
||||
private fun createChapterChip(chapter: ChapterItem, position: Int?) : Chip {
|
||||
val chip = layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
||||
chip.text = chapter.title
|
||||
val timestamp = "${chapter.start_time.toInt().toStringDuration(Locale.US)}-${chapter.end_time.toInt().toStringDuration(Locale.US)} [${chapter.title}]"
|
||||
chip.text = timestamp
|
||||
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
|
||||
chip.isCheckedIconVisible = false
|
||||
|
||||
if (position != null) chipGroup.addView(chip, position)
|
||||
else chipGroup.addView(chip)
|
||||
|
||||
if (! selectedCuts.contains(chapter.title))
|
||||
selectedCuts.add(chip.text.toString())
|
||||
|
||||
if (! selectedCuts.contains(timestamp))
|
||||
selectedCuts.add(timestamp)
|
||||
|
||||
listener.onChangeCut(selectedCuts)
|
||||
if (chapter.start_time == 0L && chapter.end_time == 0L) {
|
||||
|
|
|
|||
|
|
@ -305,8 +305,10 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
scheduleBtn.setOnClickListener{
|
||||
UiUtil.showDatePicker(fragmentManager) {
|
||||
lifecycleScope.launch {
|
||||
resultViewModel.cancelUpdateItemData()
|
||||
resultViewModel.cancelUpdateFormatsItemData()
|
||||
withContext(Dispatchers.IO){
|
||||
resultViewModel.cancelUpdateItemData()
|
||||
resultViewModel.cancelUpdateFormatsItemData()
|
||||
}
|
||||
}
|
||||
|
||||
scheduleBtn.isEnabled = false
|
||||
|
|
@ -704,8 +706,8 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
lifecycleScope.launch {
|
||||
resultViewModel.cancelUpdateItemData()
|
||||
resultViewModel.cancelUpdateFormatsItemData()
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -222,8 +222,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
val listener = object : OnFormatClickListener {
|
||||
override fun onFormatClick(item: List<FormatTuple>) {
|
||||
downloadItem.format = item.first().format
|
||||
downloadItem.videoPreferences.audioFormatIDs.clear()
|
||||
item.first().audioFormats?.map { it.format_id }?.let {
|
||||
downloadItem.videoPreferences.audioFormatIDs.clear()
|
||||
downloadItem.videoPreferences.audioFormatIDs.addAll(it)
|
||||
}
|
||||
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.core.view.isVisible
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
|
|
@ -499,7 +500,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}else{
|
||||
val selectedFormats = mutableListOf<Format>()
|
||||
formatCollection.forEach {
|
||||
selectedFormats.add(it.first{ f -> f.format_id == format.format_id})
|
||||
selectedFormats.add(it.firstOrNull{ f -> f.format_id == format.format_id} ?: format)
|
||||
}
|
||||
if (selectedFormats.isEmpty()) {
|
||||
items.forEach { _ ->
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActionBar.LayoutParams
|
||||
import android.app.Dialog
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.widget.TextView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.children
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -27,50 +32,38 @@ import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
|||
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickListener {
|
||||
class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.OnItemClickListener {
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var listAdapter : PlaylistAdapter
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var ok: MenuItem
|
||||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var ok: MaterialButton
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var fromTextInput: TextInputLayout
|
||||
private lateinit var toTextInput: TextInputLayout
|
||||
|
||||
private lateinit var count: TextView
|
||||
private lateinit var selectBetween: MenuItem
|
||||
|
||||
private lateinit var items: List<ResultItem?>
|
||||
private lateinit var itemURLs: List<String>
|
||||
private lateinit var type: DownloadViewModel.Type
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NORMAL, R.style.FullScreenDialogTheme)
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.select_playlist_items, container, false)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
return dialog
|
||||
}
|
||||
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33){
|
||||
arguments?.getParcelableArrayList("results", ResultItem::class.java)
|
||||
|
|
@ -85,11 +78,24 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
}
|
||||
}
|
||||
type = arguments?.getSerializable("type") as DownloadViewModel.Type
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi", "NotifyDataSetChanged")
|
||||
override fun setupDialog(dialog: Dialog, style: Int) {
|
||||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.select_playlist_items, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
dialog?.window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = displayMetrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
toolbar = view.findViewById<MaterialToolbar>(R.id.toolbar)
|
||||
|
||||
listAdapter =
|
||||
PlaylistAdapter(
|
||||
|
|
@ -103,7 +109,8 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
recyclerView.enableFastScroll()
|
||||
listAdapter.submitList(items)
|
||||
|
||||
toolbar.title = "0 ${resources.getString(R.string.selected)}"
|
||||
count = view.findViewById(R.id.count)
|
||||
count.text = "0 ${resources.getString(R.string.selected)}"
|
||||
|
||||
fromTextInput = view.findViewById(R.id.from_textinput)
|
||||
toTextInput = view.findViewById(R.id.to_textinput)
|
||||
|
|
@ -124,7 +131,7 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
if (endNr > items.size) endNr = items.size - 1
|
||||
listAdapter.checkRange(startNr, endNr)
|
||||
ok.isEnabled = true
|
||||
toolbar.title = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
count.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -144,12 +151,12 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
if (endNr > items.size) endNr = items.size -1
|
||||
listAdapter.checkRange(startNr, endNr)
|
||||
ok.isEnabled = true
|
||||
toolbar.title = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
count.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val checkAll = view.findViewById<ExtendedFloatingActionButton>(R.id.check_all)
|
||||
val checkAll = view.findViewById<FloatingActionButton>(R.id.check_all)
|
||||
checkAll!!.setOnClickListener {
|
||||
if (listAdapter.getCheckedItems().size != items.size){
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth("1")
|
||||
|
|
@ -158,7 +165,7 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
fromTextInput.isEnabled = true
|
||||
toTextInput.isEnabled = true
|
||||
ok.isEnabled = true
|
||||
toolbar.title = resources.getString(R.string.all_items_selected)
|
||||
count.text = resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
reset()
|
||||
fromTextInput.isEnabled = true
|
||||
|
|
@ -170,9 +177,9 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
}
|
||||
|
||||
|
||||
ok = toolbar.menu.getItem(0)
|
||||
ok = view.findViewById(R.id.bottomsheet_ok_button)
|
||||
ok.isEnabled = false
|
||||
ok.setOnMenuItemClickListener {
|
||||
ok.setOnClickListener {
|
||||
ok.isEnabled = false
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val checkedItems = listAdapter.getCheckedItems()
|
||||
|
|
@ -200,7 +207,7 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
downloadViewModel.insertToProcessing(downloadItems)
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog)
|
||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadMultipleBottomSheetDialog)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,30 +217,45 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
true
|
||||
}
|
||||
|
||||
view.findViewById<BottomAppBar>(R.id.bottomAppBar).setOnMenuItemClickListener { m: MenuItem ->
|
||||
val itemId = m.itemId
|
||||
if (itemId == R.id.invert_selected) {
|
||||
listAdapter.invertSelected(items)
|
||||
val checkedItems = listAdapter.getCheckedItems()
|
||||
if (checkedItems.size == items.size){
|
||||
toolbar.title= resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
toolbar.title = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
val bottomAppBar = view.findViewById<BottomAppBar>(R.id.bottomAppBar)
|
||||
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when(m.itemId) {
|
||||
R.id.invert_selected -> {
|
||||
listAdapter.invertSelected(items)
|
||||
val checkedItems = listAdapter.getCheckedItems()
|
||||
if (checkedItems.size == items.size){
|
||||
count.text = resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
count.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
if(checkedItems.isNotEmpty() && checkedItems.size < items.size){
|
||||
fromTextInput.isEnabled = false
|
||||
toTextInput.isEnabled = false
|
||||
}
|
||||
ok.isEnabled = checkedItems.isNotEmpty()
|
||||
}
|
||||
if(checkedItems.isNotEmpty() && checkedItems.size < items.size){
|
||||
fromTextInput.isEnabled = false
|
||||
toTextInput.isEnabled = false
|
||||
R.id.select_between -> {
|
||||
val selectedItems = listAdapter.getCheckedItems()
|
||||
if(selectedItems.size != 2){
|
||||
m.isVisible = false
|
||||
}else{
|
||||
val item2 = itemURLs.indexOf(selectedItems.last())
|
||||
val item1 = itemURLs.indexOf(selectedItems.first())
|
||||
if(item1 > item2) listAdapter.checkRange(item2, item1)
|
||||
else listAdapter.checkRange(item1, item2)
|
||||
count.text = "${listAdapter.getCheckedItems().size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
ok.isEnabled = checkedItems.isNotEmpty()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
dismiss()
|
||||
}
|
||||
selectBetween = bottomAppBar.menu.findItem(R.id.select_between)
|
||||
itemURLs = items.map { it!!.url }
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun checkRanges(start: String, end: String) : Boolean {
|
||||
return start.isNotBlank() && end.isNotBlank()
|
||||
}
|
||||
|
|
@ -241,15 +263,15 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
private fun reset(){
|
||||
ok.isEnabled = false
|
||||
listAdapter.clearCheckeditems()
|
||||
toolbar.title = "0 ${resources.getString(R.string.selected)}"
|
||||
count.text = "0 ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
|
||||
|
||||
override fun onCardSelect(itemURL: String, isChecked: Boolean, checkedItems: List<String>) {
|
||||
if (checkedItems.size == items.size){
|
||||
toolbar.title = resources.getString(R.string.all_items_selected)
|
||||
count.text = resources.getString(R.string.all_items_selected)
|
||||
}else{
|
||||
toolbar.title = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
count.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
|
||||
}
|
||||
if (checkedItems.isEmpty()){
|
||||
ok.isEnabled = false
|
||||
|
|
@ -259,6 +281,17 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
ok.isEnabled = true
|
||||
fromTextInput.isEnabled = false
|
||||
toTextInput.isEnabled = false
|
||||
val canSelectBetween = run {
|
||||
val size = checkedItems.size
|
||||
if(size != 2) false
|
||||
else {
|
||||
val item1 = itemURLs.indexOf(checkedItems.first())
|
||||
val item2 = itemURLs.indexOf(checkedItems.last())
|
||||
|
||||
(item1-item2).absoluteValue > 1
|
||||
}
|
||||
}
|
||||
selectBetween.isVisible = canSelectBetween
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
this.finish()
|
||||
}else{
|
||||
this.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if (selectedObjects == 2){
|
||||
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
this.finish()
|
||||
}else{
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects == 2){
|
||||
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
|
|
@ -213,7 +214,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
lifecycleScope.launch {
|
||||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Error).toListString())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
private var websiteGroup: ChipGroup? = null
|
||||
private var historyList: List<HistoryItem?>? = null
|
||||
private var allhistoryList: List<HistoryItem?>? = null
|
||||
private var selectedObjects: ArrayList<HistoryItem>? = null
|
||||
private lateinit var selectedObjects: ArrayList<HistoryItem>
|
||||
private var actionMode : ActionMode? = null
|
||||
|
||||
private lateinit var sortChip: Chip
|
||||
|
|
@ -113,7 +113,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
websiteGroup = view.findViewById(R.id.website_chip_group)
|
||||
uiHandler = Handler(Looper.getMainLooper())
|
||||
sortChip = view.findViewById(R.id.sortChip)
|
||||
selectedObjects = ArrayList()
|
||||
selectedObjects = arrayListOf()
|
||||
|
||||
|
||||
historyList = mutableListOf()
|
||||
|
|
@ -465,21 +465,26 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = findItem(itemID)
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
lifecycleScope.launch {
|
||||
val item = findItem(itemID)
|
||||
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
actionMode?.apply {
|
||||
if (isChecked) selectedObjects.add(item!!)
|
||||
else selectedObjects.remove(item!!)
|
||||
|
||||
}else{
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
if (selectedObjects.size == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
actionMode?.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects.size == 2){
|
||||
val selectedIDs = selectedObjects.sortedBy { it.id }
|
||||
val resultsInMiddle = withContext(Dispatchers.IO){
|
||||
historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
|
||||
}.toMutableList()
|
||||
this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -491,7 +496,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.history_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
(activity as MainActivity).disableBottomNavigation()
|
||||
topAppBar!!.menu.forEach { it.isEnabled = false }
|
||||
return true
|
||||
|
|
@ -509,6 +514,21 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
item: MenuItem?
|
||||
): Boolean {
|
||||
return when (item!!.itemId) {
|
||||
R.id.select_between -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedIDs = selectedObjects.sortedBy { it.id }
|
||||
val resultsInMiddle = withContext(Dispatchers.IO){
|
||||
historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
|
||||
}.toMutableList()
|
||||
if (resultsInMiddle.isNotEmpty()){
|
||||
selectedObjects.addAll(resultsInMiddle)
|
||||
historyAdapter?.checkMultipleItems(selectedObjects.map { it.id })
|
||||
actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}"
|
||||
}
|
||||
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.delete_results -> {
|
||||
val deleteFile = booleanArrayOf(false)
|
||||
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
|
||||
|
|
@ -519,7 +539,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
for (obj in selectedObjects!!){
|
||||
for (obj in selectedObjects){
|
||||
historyViewModel.delete(obj, deleteFile[0])
|
||||
}
|
||||
clearCheckedItems()
|
||||
|
|
@ -529,15 +549,15 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
true
|
||||
}
|
||||
R.id.share -> {
|
||||
UiUtil.shareFileIntent(requireContext(), selectedObjects!!.map { it.downloadPath }.flatten())
|
||||
UiUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten())
|
||||
clearCheckedItems()
|
||||
actionMode?.finish()
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
historyAdapter?.checkAll(historyList)
|
||||
selectedObjects?.clear()
|
||||
historyList?.forEach { selectedObjects?.add(it!!) }
|
||||
selectedObjects.clear()
|
||||
historyList?.forEach { selectedObjects.add(it!!) }
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
|
|
@ -545,11 +565,11 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
historyAdapter?.invertSelected(historyList)
|
||||
val invertedList = arrayListOf<HistoryItem>()
|
||||
historyList?.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it!!)
|
||||
if (!selectedObjects.contains(it)!!) invertedList.add(it!!)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
selectedObjects.clear()
|
||||
selectedObjects.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
|
|
@ -567,7 +587,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
|
||||
private fun clearCheckedItems(){
|
||||
historyAdapter?.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
selectedObjects.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
}
|
||||
|
||||
this.menu.findItem(R.id.up).isVisible = position > 0
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects == 2){
|
||||
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
|
|
@ -254,7 +255,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
lifecycleScope.launch {
|
||||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
|
||||
downloadViewModel.getQueuedIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
this.finish()
|
||||
}else{
|
||||
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects == 2){
|
||||
val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
|
|
@ -207,7 +208,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
lifecycleScope.launch {
|
||||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Cancelled).toListString())
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Saved).toListString())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.more
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
|
|
@ -62,7 +63,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
private lateinit var noResults: RelativeLayout
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var sortChip: Chip
|
||||
private var selectedObjects: ArrayList<CommandTemplate>? = null
|
||||
private lateinit var selectedObjects: ArrayList<CommandTemplate>
|
||||
private var actionMode : ActionMode? = null
|
||||
private val jsonFormat = Json { prettyPrint = true }
|
||||
|
||||
|
|
@ -76,6 +77,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
return inflater.inflate(R.layout.fragment_command_templates, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
templatesList = listOf()
|
||||
|
|
@ -273,21 +275,26 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
}
|
||||
|
||||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = templatesList.find { it.id == itemID }
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
lifecycleScope.launch {
|
||||
val item = templatesList.find { it.id == itemID }
|
||||
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
actionMode?.apply {
|
||||
if (isChecked) selectedObjects.add(item!!)
|
||||
else selectedObjects.remove(item!!)
|
||||
|
||||
}else{
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
actionMode?.finish()
|
||||
if (selectedObjects.size == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
actionMode?.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects.size == 2){
|
||||
val selectedIDs = selectedObjects.sortedBy { it.id }
|
||||
val resultsInMiddle = withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
|
||||
}.toMutableList()
|
||||
this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -295,7 +302,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.templates_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -311,12 +318,27 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
item: MenuItem?
|
||||
): Boolean {
|
||||
return when (item!!.itemId) {
|
||||
R.id.select_between -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedIDs = selectedObjects.sortedBy { it.id }
|
||||
val resultsInMiddle = withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
|
||||
}.toMutableList()
|
||||
if (resultsInMiddle.isNotEmpty()){
|
||||
selectedObjects.addAll(resultsInMiddle)
|
||||
templatesAdapter.checkMultipleItems(selectedObjects.map { it.id })
|
||||
actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}"
|
||||
}
|
||||
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.delete_results -> {
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
for (obj in selectedObjects!!){
|
||||
for (obj in selectedObjects){
|
||||
commandTemplateViewModel.delete(obj)
|
||||
}
|
||||
clearCheckedItems()
|
||||
|
|
@ -327,8 +349,8 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
}
|
||||
R.id.select_all -> {
|
||||
templatesAdapter.checkAll(templatesList)
|
||||
selectedObjects?.clear()
|
||||
templatesList.forEach { selectedObjects?.add(it) }
|
||||
selectedObjects.clear()
|
||||
templatesList.forEach { selectedObjects.add(it) }
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
|
|
@ -336,11 +358,11 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
templatesAdapter.invertSelected(templatesList)
|
||||
val invertedList = arrayListOf<CommandTemplate>()
|
||||
templatesList.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||
if (!selectedObjects.contains(it)!!) invertedList.add(it)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
selectedObjects.clear()
|
||||
selectedObjects.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
|
|
@ -348,7 +370,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
lifecycleScope.launch{
|
||||
val output = jsonFormat.encodeToString(
|
||||
CommandTemplateExport(
|
||||
templates = selectedObjects!!.toList(),
|
||||
templates = selectedObjects.toList(),
|
||||
shortcuts = listOf()
|
||||
)
|
||||
)
|
||||
|
|
@ -374,7 +396,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
|
||||
private fun clearCheckedItems(){
|
||||
templatesAdapter.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
selectedObjects.clear()
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.chip.Chip
|
||||
|
|
@ -128,7 +129,9 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
cookiesViewModel.deleteAll()
|
||||
kotlin.runCatching {
|
||||
File(context?.cacheDir, "cookies.txt").apply { writeText("") }
|
||||
FileUtil.getCookieFile(requireContext(), true){
|
||||
File(it).apply { writeText("") }
|
||||
}
|
||||
}
|
||||
}
|
||||
deleteDialog.show()
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||
import com.deniscerri.ytdlnis.util.Extensions.convertNetscapeToSetCookie
|
||||
import com.google.accompanist.web.AccompanistWebChromeClient
|
||||
import com.google.accompanist.web.AccompanistWebViewClient
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.google.android.exoplayer2.util.Log
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
|
@ -49,16 +49,15 @@ class WebViewActivity : BaseActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.webview_activity)
|
||||
url = intent.extras!!.getString("url")!!
|
||||
cookies = ""
|
||||
cookiesViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
||||
toolbar = appbar.findViewById(R.id.webviewToolbar)
|
||||
generateBtn = toolbar.findViewById(R.id.generate)
|
||||
webViewCompose = findViewById(R.id.webview_compose)
|
||||
cookieManager = CookieManager.getInstance()
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
lifecycleScope.launch {
|
||||
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
||||
toolbar = appbar.findViewById(R.id.webviewToolbar)
|
||||
generateBtn = toolbar.findViewById(R.id.generate)
|
||||
webViewCompose = findViewById(R.id.webview_compose)
|
||||
cookieManager = CookieManager.getInstance()
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this@WebViewActivity)
|
||||
|
||||
lifecycleScope.launch{
|
||||
webViewClient = object : AccompanistWebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
|
|
@ -78,9 +77,7 @@ class WebViewActivity : BaseActivity() {
|
|||
|
||||
generateBtn.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
val a = cookieManager.getCookie(url).replace(";", "\n")
|
||||
Log.e("TESTING", a)
|
||||
withContext(Dispatchers.IO) {
|
||||
cookiesViewModel.getCookiesFromDB(url).getOrNull()?.let {
|
||||
kotlin.runCatching {
|
||||
cookiesViewModel.insert(
|
||||
|
|
@ -92,12 +89,17 @@ class WebViewActivity : BaseActivity() {
|
|||
)
|
||||
cookiesViewModel.updateCookiesFile()
|
||||
}.onFailure {
|
||||
withContext(Dispatchers.Main){
|
||||
Toast.makeText(this@WebViewActivity, "Something went wrong", Toast.LENGTH_SHORT).show()
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
this@WebViewActivity,
|
||||
"Something went wrong",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
cookieManager.removeAllCookies(null)
|
||||
withContext(Dispatchers.Main) {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +111,6 @@ class WebViewActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
private lateinit var topAppBar: MaterialToolbar
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var logViewModel: LogViewModel
|
||||
private var selectedObjects: ArrayList<LogItem>? = null
|
||||
private lateinit var selectedObjects: ArrayList<LogItem>
|
||||
private var actionMode : ActionMode? = null
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -139,18 +139,18 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||
val item = items.find { it.id == itemID }
|
||||
if (isChecked) {
|
||||
selectedObjects!!.add(item!!)
|
||||
selectedObjects.add(item!!)
|
||||
if (actionMode == null){
|
||||
actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||
|
||||
}else{
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
selectedObjects!!.remove(item)
|
||||
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects!!.isEmpty()){
|
||||
selectedObjects.remove(item)
|
||||
actionMode?.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
if (selectedObjects.isEmpty()){
|
||||
actionMode?.finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +159,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
private val contextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.logs_menu_context, menu)
|
||||
mode.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
mode.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
for (obj in selectedObjects!!){
|
||||
for (obj in selectedObjects){
|
||||
logViewModel.delete(obj)
|
||||
}
|
||||
clearCheckedItems()
|
||||
|
|
@ -191,8 +191,8 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
}
|
||||
R.id.select_all -> {
|
||||
downloadLogAdapter.checkAll(items)
|
||||
selectedObjects?.clear()
|
||||
items.forEach { selectedObjects?.add(it) }
|
||||
selectedObjects.clear()
|
||||
items.forEach { selectedObjects.add(it) }
|
||||
mode?.title = getString(R.string.all_items_selected)
|
||||
true
|
||||
}
|
||||
|
|
@ -200,11 +200,11 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
downloadLogAdapter.invertSelected(items)
|
||||
val invertedList = arrayListOf<LogItem>()
|
||||
items.forEach {
|
||||
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||
if (!selectedObjects.contains(it)!!) invertedList.add(it)
|
||||
}
|
||||
selectedObjects?.clear()
|
||||
selectedObjects?.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||
selectedObjects.clear()
|
||||
selectedObjects.addAll(invertedList)
|
||||
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
|
||||
if (invertedList.isEmpty()) actionMode?.finish()
|
||||
true
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
|||
|
||||
private fun clearCheckedItems(){
|
||||
downloadLogAdapter.clearCheckeditems()
|
||||
selectedObjects?.clear()
|
||||
selectedObjects.clear()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.provider.Settings
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
|
|
@ -30,6 +31,8 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
private var videoPath: Preference? = null
|
||||
private var commandPath: Preference? = null
|
||||
private var accessAllFiles : Preference? = null
|
||||
private var noFragments: SwitchPreferenceCompat? = null
|
||||
private var keepFragments: SwitchPreferenceCompat? = null
|
||||
private var cacheDownloads : Preference? = null
|
||||
private var audioFilenameTemplate : Preference? = null
|
||||
private var videoFilenameTemplate : Preference? = null
|
||||
|
|
@ -50,6 +53,8 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
videoPath = findPreference("video_path")
|
||||
commandPath = findPreference("command_path")
|
||||
accessAllFiles = findPreference("access_all_files")
|
||||
noFragments = findPreference("no_part")
|
||||
keepFragments = findPreference("keep_cache")
|
||||
cacheDownloads = findPreference("cache_downloads")
|
||||
videoFilenameTemplate = findPreference("file_name_template")
|
||||
audioFilenameTemplate = findPreference("file_name_template_audio")
|
||||
|
|
@ -116,6 +121,17 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
true
|
||||
}
|
||||
|
||||
noFragments!!.setOnPreferenceChangeListener { _, newValue ->
|
||||
if(newValue as Boolean){
|
||||
editor.putBoolean("keep_cache", false).apply()
|
||||
keepFragments!!.isChecked = false
|
||||
keepFragments!!.isEnabled = false
|
||||
}else{
|
||||
keepFragments!!.isEnabled = true
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
||||
videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader)s - %(title)s")
|
||||
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||
|
|
@ -171,7 +187,7 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
|||
if (list.first() == null) return@observe
|
||||
|
||||
if (list.first().state == WorkInfo.State.SUCCEEDED){
|
||||
cacheSize = File(requireContext().cacheDir.absolutePath + "/downloads").walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
cacheSize = File(FileUtil.getCachePath(requireContext())).walkBottomUp().fold(0L) { acc, file -> acc + file.length() }
|
||||
clearCache!!.summary = "(${FileUtil.convertFileSize(cacheSize)}) ${resources.getString(R.string.clear_temporary_files_summary)}"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.content.Context
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.LogDao
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class CrashListener(private val context: Context) : Thread.UncaughtExceptionHandler {
|
||||
|
||||
override fun uncaughtException(p0: Thread, p1: Throwable) {
|
||||
p1.message?.apply {
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
createLog(this@apply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createLog(message: String){
|
||||
kotlin.runCatching {
|
||||
val db = DBManager.getInstance(context)
|
||||
val dao = db.logDao
|
||||
dao.insert(LogItem(
|
||||
id = 0L,
|
||||
title = "APP CRASH",
|
||||
content = message,
|
||||
format = Format("", "", "", "", "", 0, "", "", "", "", "", ""),
|
||||
downloadType = DownloadViewModel.Type.command,
|
||||
downloadTime = System.currentTimeMillis()
|
||||
))
|
||||
}
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
fun registerExceptionHandler(){
|
||||
Thread.setDefaultUncaughtExceptionHandler(this)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import androidx.core.view.updateLayoutParams
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.withStarted
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
|
@ -40,6 +41,7 @@ import kotlinx.coroutines.launch
|
|||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.net.HttpCookie
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
|
|
@ -225,22 +227,26 @@ object Extensions {
|
|||
number = nr
|
||||
verticalOffset = 3
|
||||
horizontalOffset =
|
||||
if (nr < 10) 7
|
||||
else if (nr < 100) 10
|
||||
else 20
|
||||
if (nr < 10) dp(App.instance.resources, 7f)
|
||||
else if (nr < 100) dp(App.instance.resources, 10f)
|
||||
else dp(App.instance.resources, 20f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun String.appendLineToLog(line: String): String {
|
||||
val lines = this.split("\n")
|
||||
//clean dublicate progress + add newline
|
||||
var newLine = line
|
||||
if (newLine.contains("[download")) {
|
||||
newLine = "[download]" + line.split("[download]").last()
|
||||
if (!lines.takeLast(3).contains(line)){
|
||||
//clean dublicate progress + add newline
|
||||
var newLine = line
|
||||
if (newLine.contains("[download")) {
|
||||
newLine = "[download]" + line.split("[download]").last()
|
||||
}
|
||||
|
||||
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
||||
}
|
||||
|
||||
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
||||
return this
|
||||
}
|
||||
|
||||
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
|
||||
|
|
@ -272,5 +278,55 @@ object Extensions {
|
|||
}
|
||||
}
|
||||
|
||||
fun String.convertToTimestamp() : Int {
|
||||
return try {
|
||||
val timeArray = this.split(":")
|
||||
var timeSeconds = timeArray[timeArray.lastIndex].toInt()
|
||||
var times = 60
|
||||
for (i in timeArray.lastIndex - 1 downTo 0) {
|
||||
timeSeconds += timeArray[i].toInt() * times
|
||||
times *= 60
|
||||
}
|
||||
timeSeconds
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun String.convertNetscapeToSetCookie(): String {
|
||||
// Split the Netscape cookie string
|
||||
val parts =
|
||||
this.split("\t").dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
|
||||
if (parts.isEmpty()) return ""
|
||||
|
||||
// Extract the individual components
|
||||
val domain = parts[0].trim { it <= ' ' }
|
||||
val isSecure =
|
||||
java.lang.Boolean.parseBoolean(parts[3].trim { it <= ' ' })
|
||||
val expiry = parts[4].trim { it <= ' ' }.toLong()
|
||||
val name = parts[5].trim { it <= ' ' }
|
||||
val value = parts[6].trim { it <= ' ' }
|
||||
|
||||
// Create the BasicClientCookie
|
||||
val cookie = HttpCookie(name, value)
|
||||
cookie.domain = domain
|
||||
cookie.path = "/"
|
||||
cookie.secure = isSecure
|
||||
|
||||
// Set expiry
|
||||
if (expiry != 0L) {
|
||||
cookie.maxAge = expiry
|
||||
} else {
|
||||
// For session cookies, set to null
|
||||
cookie.maxAge = -1L
|
||||
}
|
||||
|
||||
// Get the Set-Cookie header format
|
||||
return cookie.toString()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -262,15 +262,22 @@ object FileUtil {
|
|||
}
|
||||
|
||||
fun getCachePath(context: Context) : String {
|
||||
return context.cacheDir.absolutePath + "/downloads/"
|
||||
val externalPath = context.getExternalFilesDir(null)
|
||||
return if (externalPath == null){
|
||||
context.cacheDir.absolutePath + "/downloads/"
|
||||
}else{
|
||||
externalPath.absolutePath + "/downloads/"
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteConfigFiles(request: YoutubeDLRequest) {
|
||||
request.getArguments("--config")?.forEach {
|
||||
if (it != null) File(it).delete()
|
||||
}
|
||||
request.getArguments("--config-locations")?.forEach {
|
||||
if (it != null) File(it).delete()
|
||||
runCatching {
|
||||
request.getArguments("--config")?.forEach {
|
||||
if (it != null) File(it).delete()
|
||||
}
|
||||
request.getArguments("--config-locations")?.forEach {
|
||||
if (it != null) File(it).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,16 +294,18 @@ object FileUtil {
|
|||
}
|
||||
|
||||
fun getDownloadArchivePath(context: Context) : String {
|
||||
return context.cacheDir.absolutePath + "/download_archive.txt"
|
||||
return context.filesDir.absolutePath + "/download_archive.txt"
|
||||
}
|
||||
|
||||
fun getDefaultTerminalPath() : String {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/TERMINAL_CACHE"
|
||||
}
|
||||
|
||||
fun getCookieFile(context : Context, path: (path: String) -> Unit){
|
||||
fun getCookieFile(context : Context, ignoreIfExists: Boolean = false, path: (path: String) -> Unit){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()) path(cookiesFile.absolutePath)
|
||||
if (ignoreIfExists || cookiesFile.exists()){
|
||||
path(cookiesFile.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
fun convertFileSize(s: Long): String{
|
||||
|
|
|
|||
|
|
@ -249,8 +249,8 @@ class InfoUtil(private val context: Context) {
|
|||
formats.groupBy { it.format_id }.forEach {
|
||||
if (it.value.count() > 1) {
|
||||
it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" }
|
||||
val engDefault = it.value.find { f -> f.format_note.contains("original", true) }
|
||||
engDefault?.format_id = (engDefault?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
|
||||
val defaultLang = it.value.find { f -> f.format_note.contains("original", true) }
|
||||
defaultLang?.format_id = (defaultLang?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}"
|
||||
}
|
||||
}
|
||||
formats.sortByDescending { it.filesize }
|
||||
|
|
@ -648,9 +648,9 @@ class InfoUtil(private val context: Context) {
|
|||
if (result.isNullOrBlank()) continue
|
||||
val jsonObject = JSONObject(result)
|
||||
val title = jsonObject.getStringByAny("track", "alt_title", "title", "webpage_url_basename")
|
||||
if (title == "[Private video]") continue
|
||||
if (title == "[Private video]" || title == "[Deleted video]") continue
|
||||
|
||||
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
||||
var author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
||||
var duration = jsonObject.getIntByAny("duration").toString()
|
||||
if (duration != "-1"){
|
||||
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
||||
|
|
@ -666,6 +666,17 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if(author.isEmpty()){
|
||||
runCatching {
|
||||
val firstEntry = jsonObject.getJSONArray("entries").getJSONObject(0)
|
||||
author = firstEntry.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
||||
val thumbs = firstEntry.getJSONArray("thumbnails")
|
||||
if (thumbs.length() > 0){
|
||||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor")
|
||||
var playlistTitle = jsonObject.getStringByAny("playlist_title")
|
||||
var playlistURL: String? = ""
|
||||
|
|
@ -991,11 +1002,15 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
if(keepCache){
|
||||
request.addOption("--part")
|
||||
request.addOption("--keep-fragments")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("no_part", false)){
|
||||
request.addOption("--no-part")
|
||||
}
|
||||
|
||||
val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true)
|
||||
val thumbnailFormat = sharedPreferences.getString("thumbnail_format", "jpg")
|
||||
var filenameTemplate = downloadItem.customFileNameTemplate
|
||||
|
||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||
|
|
@ -1003,7 +1018,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
if (downloadItem.SaveThumb) {
|
||||
request.addOption("--write-thumbnail")
|
||||
request.addOption("--convert-thumbnails", sharedPreferences.getString("thumbnail_format", "jpg")!!)
|
||||
request.addOption("--convert-thumbnails", thumbnailFormat!!)
|
||||
}
|
||||
if (!sharedPreferences.getBoolean("mtime", false)){
|
||||
request.addOption("--no-mtime")
|
||||
|
|
@ -1032,22 +1047,13 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if (downloadItem.playlistTitle.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:playlist", ".+", downloadItem.playlistTitle))
|
||||
runCatching {
|
||||
if (downloadItem.playlistIndex != null){
|
||||
request.addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(downloadItem.title.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(150)))
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.replace("\"", "\\\"").take(150)))
|
||||
}
|
||||
|
||||
|
||||
if (downloadItem.author.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.replace("\"", "\\\"").take(30)))
|
||||
}
|
||||
|
||||
request.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$")
|
||||
|
|
@ -1059,10 +1065,7 @@ class InfoUtil(private val context: Context) {
|
|||
if (downloadItem.downloadSections.isNotBlank()){
|
||||
downloadItem.downloadSections.split(";").forEach {
|
||||
if (it.isBlank()) return@forEach
|
||||
if (it.contains(":"))
|
||||
request.addOption("--download-sections", "*$it")
|
||||
else
|
||||
request.addOption("--download-sections", it)
|
||||
request.addOption("--download-sections", "*${it.split(" ")[0]}")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_keyframes", false) && !request.hasOption("--force-keyframes-at-cuts")){
|
||||
request.addOption("--force-keyframes-at-cuts")
|
||||
|
|
@ -1121,6 +1124,13 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
val ext = downloadItem.container
|
||||
if (audioQualityId.isNotBlank()) {
|
||||
if(audioQualityId.contains("-")){
|
||||
audioQualityId = if(!downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){
|
||||
"ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b"
|
||||
}else{
|
||||
"$audioQualityId/${audioQualityId.split("-")[0]}"
|
||||
}
|
||||
}
|
||||
if (audioQualityId.contains("-") && !downloadItem.format.lang.isNullOrBlank() && downloadItem.format.lang != "None"){
|
||||
audioQualityId = "ba[format_id~='^(${audioQualityId.split("-")[0]})'][language^=${downloadItem.format.lang}]/ba/b"
|
||||
}
|
||||
|
|
@ -1163,26 +1173,26 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
if (downloadItem.playlistTitle.isNotEmpty()) {
|
||||
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
|
||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s")
|
||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track_number)s")
|
||||
} else {
|
||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||
}
|
||||
}
|
||||
|
||||
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
|
||||
if (thumbnailFormat == "jpg"){
|
||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||
}
|
||||
|
||||
if (downloadItem.audioPreferences.embedThumb) {
|
||||
if (downloadItem.audioPreferences.embedThumb){
|
||||
request.addOption("--embed-thumbnail")
|
||||
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
|
||||
val cropThumb = downloadItem.audioPreferences.cropThumb ?: sharedPreferences.getBoolean("crop_thumbnail", true)
|
||||
if (!request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", thumbnailFormat!!)
|
||||
if (cropThumb){
|
||||
try {
|
||||
runCatching {
|
||||
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
|
||||
val configData = "--ppa \"ffmpeg:-c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||
val configData = "--ppa \"ThumbnailsConvertor:-vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||
config.writeText(configData)
|
||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||
request.addOption("--config", config.absolutePath)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1440,7 +1450,7 @@ class InfoUtil(private val context: Context) {
|
|||
val acodecPreference = sharedPreferences.getString("audio_codec", "m4a|mp4a|aac")
|
||||
audioFormats.forEachIndexed { idx, it -> formats.add(Format(audioFormatsValues[idx], containerPreference!!,"",acodecPreference!!, "",0, it)) }
|
||||
audioFormats.reverse()
|
||||
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"",acodecPreference!!, "",1, it)) }
|
||||
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"",resources.getString(R.string.preferred_format_id), "",1, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
|
|
@ -1449,10 +1459,24 @@ class InfoUtil(private val context: Context) {
|
|||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||
val formats = mutableListOf<Format>()
|
||||
val containerPreference = sharedPreferences.getString("video_format", "")
|
||||
val acodecPreference = sharedPreferences.getString("audio_codec", "m4a|mp4a|aac")
|
||||
val vcodecPreference = sharedPreferences.getString("video_codec", "avc|h264")!!.ifEmpty { resources.getString(R.string.defaultValue) }
|
||||
videoFormats.reversed().forEach { formats.add(Format(it, containerPreference!!,vcodecPreference,acodecPreference!!, "",0, it.split("_")[0])) }
|
||||
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,vcodecPreference,acodecPreference!!, "",1, it)) }
|
||||
val audioCodecPreference = sharedPreferences.getString("audio_codec", "m4a|mp4a|aac")!!.run {
|
||||
if (this.isNotEmpty()){
|
||||
val audioCodecs = resources.getStringArray(R.array.audio_codec)
|
||||
val audioCodecsValues = resources.getStringArray(R.array.audio_codec_values)
|
||||
audioCodecs[audioCodecsValues.indexOf(this)]
|
||||
}else this
|
||||
}
|
||||
val videoCodecPreference = sharedPreferences.getString("video_codec", "avc|h264")!!.run {
|
||||
if (this.isEmpty()){
|
||||
resources.getString(R.string.defaultValue)
|
||||
}else{
|
||||
val videoCodecs = resources.getStringArray(R.array.video_codec)
|
||||
val videoCodecsValues = resources.getStringArray(R.array.video_codec_values)
|
||||
videoCodecs[videoCodecsValues.indexOf(this)]
|
||||
}
|
||||
}
|
||||
videoFormats.reversed().forEach { formats.add(Format(it, containerPreference!!,videoCodecPreference,audioCodecPreference, "",0, it.split("_")[0])) }
|
||||
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,resources.getString(R.string.preferred_format_id),"", "",1, it)) }
|
||||
return formats
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import androidx.core.content.FileProvider
|
|||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
||||
|
|
@ -221,13 +223,14 @@ class NotificationUtil(var context: Context) {
|
|||
fun createDownloadFinished(
|
||||
id: Long,
|
||||
title: String?,
|
||||
downloadType: DownloadViewModel.Type,
|
||||
filepath: List<String>?,
|
||||
res: Resources
|
||||
) {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle("${res.getString(R.string.downloaded)} $title")
|
||||
.setContentTitle("${res.getString(R.string.downloaded)} (${downloadType}): $title")
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
|
|
@ -237,7 +240,7 @@ class NotificationUtil(var context: Context) {
|
|||
)
|
||||
.setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString())
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||
.setContentText("")
|
||||
.setContentText(title)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.clearActions()
|
||||
|
|
@ -387,7 +390,7 @@ class NotificationUtil(var context: Context) {
|
|||
)
|
||||
|
||||
try {
|
||||
notificationBuilder.setProgress(100, progress, false)
|
||||
notificationBuilder.setProgress(100, progress, progress == 0)
|
||||
.setContentTitle(title)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
.clearActions()
|
||||
|
|
@ -420,7 +423,7 @@ class NotificationUtil(var context: Context) {
|
|||
)
|
||||
|
||||
try {
|
||||
notificationBuilder.setProgress(100, progress, false)
|
||||
notificationBuilder.setProgress(100, progress, progress == 0)
|
||||
.setContentTitle(title)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
.clearActions()
|
||||
|
|
@ -462,7 +465,7 @@ class NotificationUtil(var context: Context) {
|
|||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||
val contentText = "${progress}/${totalFiles}"
|
||||
try {
|
||||
notificationBuilder.setProgress(100, progress, false)
|
||||
notificationBuilder.setProgress(100, progress, progress == 0)
|
||||
.setContentTitle(resources.getString(R.string.move_temporary_files))
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
notificationManager.notify(id, notificationBuilder.build())
|
||||
|
|
@ -534,7 +537,7 @@ class NotificationUtil(var context: Context) {
|
|||
|
||||
|
||||
try {
|
||||
notificationBuilder.setProgress(queue, progress, false)
|
||||
notificationBuilder.setProgress(queue, progress, progress == 0)
|
||||
.setContentTitle(resources.getString(R.string.update_formats_background))
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
.clearActions()
|
||||
|
|
|
|||
|
|
@ -111,6 +111,9 @@ object UiUtil {
|
|||
if (!audioFormats.isNullOrEmpty()){
|
||||
text = "id: " + audioFormats.joinToString("+") { it.format_id }
|
||||
visibility = View.VISIBLE
|
||||
}else if (chosenFormat.vcodec != "none" && chosenFormat.vcodec != "" && chosenFormat.acodec != "none" && chosenFormat.acodec != "") {
|
||||
text = chosenFormat.acodec
|
||||
visibility = View.VISIBLE
|
||||
}else{
|
||||
visibility = View.GONE
|
||||
}
|
||||
|
|
@ -1351,12 +1354,18 @@ object UiUtil {
|
|||
subtitleLanguages.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (items.all { it.videoPreferences.writeAutoSubs}) {
|
||||
saveAutoSubtitles.isChecked = true
|
||||
subtitleLanguages.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
saveSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
||||
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
|
||||
saveSubtitlesClicked(saveSubtitles.isChecked)
|
||||
}
|
||||
|
||||
saveAutoSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked || saveAutoSubtitles.isChecked
|
||||
saveAutoSubtitlesClicked(saveAutoSubtitles.isChecked)
|
||||
}
|
||||
|
||||
|
|
@ -1650,6 +1659,7 @@ object UiUtil {
|
|||
textSize = 20f
|
||||
}
|
||||
sheet.findViewById<TextView>(R.id.bottom_sheet_subtitle)?.text = message
|
||||
sheet.findViewById<LinearLayout>(R.id.multiple_top_info)?.isVisible = false
|
||||
|
||||
var adapter: AlreadyExistsAdapter? = null
|
||||
val list = mutableListOf<Pair<DownloadItem, Long?>>()
|
||||
|
|
@ -1712,7 +1722,7 @@ object UiUtil {
|
|||
layoutManager = GridLayoutManager(context, 1)
|
||||
this.adapter = adapter
|
||||
enableFastScroll()
|
||||
setPadding(0,20,0,0)
|
||||
setPadding(0,0,0,0)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1993,7 +2003,7 @@ object UiUtil {
|
|||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
context.getString(R.string.dismiss)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ class UpdateUtil(var context: Context) {
|
|||
.setTitle(activity.getString(R.string.changelog))
|
||||
.setView(scrollView)
|
||||
.setIcon(R.drawable.ic_chapters)
|
||||
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
|
||||
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
changeLogDialog.show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ class DownloadWorker(
|
|||
|
||||
queuedItems.collect { items ->
|
||||
runningYTDLInstances.clear()
|
||||
dao.getActiveDownloadsList().forEach {
|
||||
val activeDownloads = dao.getActiveDownloadsList()
|
||||
activeDownloads.forEach {
|
||||
runningYTDLInstances.add(it.id)
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +156,7 @@ class DownloadWorker(
|
|||
|
||||
runCatching {
|
||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).ifEmpty { listOf("") }.first().toString(), "id" to downloadItem.id))
|
||||
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
|
||||
notificationUtil.updateDownloadNotification(
|
||||
downloadItem.id.toInt(),
|
||||
|
|
@ -267,7 +268,7 @@ class DownloadWorker(
|
|||
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.id, downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
||||
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
||||
)
|
||||
|
||||
if (wasQuickDownloaded && createResultItem){
|
||||
|
|
@ -335,7 +336,7 @@ class DownloadWorker(
|
|||
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to it.toString(), "id" to downloadItem.id))
|
||||
}
|
||||
if (items.size <= 1) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
//if (items.size <= 1) WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.work.WorkerParameters
|
|||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
|
@ -28,7 +29,8 @@ class MoveCacheFilesWorker(
|
|||
val notificationUtil = NotificationUtil(App.instance)
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
|
||||
val downloadFolders = File(context.cacheDir.absolutePath + "/downloads")
|
||||
val cachePath = FileUtil.getCachePath(context)
|
||||
val downloadFolders = File(cachePath)
|
||||
val allContent = downloadFolders.walk()
|
||||
allContent.drop(1)
|
||||
val totalFiles = allContent.count()
|
||||
|
|
@ -44,7 +46,7 @@ class MoveCacheFilesWorker(
|
|||
allContent.forEach {
|
||||
progress++
|
||||
notificationUtil.updateCacheMovingNotification(id, progress, totalFiles)
|
||||
val destFile = File(destination.absolutePath + "/${it.absolutePath.removePrefix(context.cacheDir.absolutePath + "/downloads")}")
|
||||
val destFile = File(destination.absolutePath + "/${it.absolutePath.removePrefix(cachePath)}")
|
||||
if (it.isDirectory) {
|
||||
destFile.mkdirs()
|
||||
return@forEach
|
||||
|
|
|
|||
|
|
@ -64,9 +64,8 @@ class TerminalDownloadWorker(
|
|||
)
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||
if (cookiesFile.exists()){
|
||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
}
|
||||
|
||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||
|
|
@ -144,7 +143,6 @@ class TerminalDownloadWorker(
|
|||
return runBlocking {
|
||||
if (logDownloads) logRepo.update(it.out, logItem.id)
|
||||
dao.updateLog(it.out, itemId.toLong())
|
||||
Thread.sleep(1000)
|
||||
dao.delete(itemId.toLong())
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
|
||||
|
|
@ -156,7 +154,6 @@ class TerminalDownloadWorker(
|
|||
if (it.message != null){
|
||||
if (logDownloads) logRepo.update(it.message!!, logItem.id)
|
||||
dao.updateLog(it.message!!, itemId.toLong())
|
||||
Thread.sleep(1000)
|
||||
dao.delete(itemId.toLong())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView android:id="@+id/download_recyclerview"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -1,66 +1,216 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/already_exists_constraint"
|
||||
<com.google.android.material.card.MaterialCardView android:id="@+id/download_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:checkable="true"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:layout_height="wrap_content">
|
||||
app:checkedIcon="@null"
|
||||
app:strokeColor="?attr/colorPrimary"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
app:strokeWidth="0dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/downloads_image_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="?attr/colorSurfaceVariant"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintHorizontal_weight="0.3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_title"
|
||||
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:cornerRadius="10dp"
|
||||
android:autoLink="all"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/already_exists_buttons"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/already_exists_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_edit"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_edit"
|
||||
android:contentDescription="@string/edit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/already_exists_title"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
android:layout_margin="5dp"
|
||||
android:background="#80000000"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/downloads_image_view"
|
||||
app:layout_constraintEnd_toEndOf="@+id/downloads_image_view" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_delete"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/Remove"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/download_item_data"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/baseline_delete_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/already_exists_title" />
|
||||
app:layout_constraintHorizontal_weight="0.7"
|
||||
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/horizontalScrollView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/already_exists_buttons"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="title,already_exists_buttons" />
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/horizontalScrollView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/barrier">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_note"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/already_exists_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_edit"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_edit"
|
||||
android:contentDescription="@string/edit"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_delete"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/Remove"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/baseline_delete_24"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@
|
|||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/multiple_top_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:scrollbars="none">
|
||||
|
|
@ -182,6 +181,7 @@
|
|||
android:ellipsize="end"
|
||||
android:gravity="bottom|end"
|
||||
android:maxLength="10"
|
||||
android:layout_marginEnd="5dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/format_note"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@
|
|||
android:layout_marginEnd="4dp"
|
||||
android:background="@color/light_grey"
|
||||
android:clickable="false"
|
||||
android:gravity="bottom|end"
|
||||
android:maxLength="10"
|
||||
android:layout_marginTop="10dp"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
|
|
@ -115,18 +115,26 @@
|
|||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/recyclerviewhistorys"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="100dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerviewhistorys"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include layout="@layout/no_results"
|
||||
android:visibility="gone" />
|
||||
|
|
|
|||
|
|
@ -1,121 +1,105 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/playlist_card_constraintLayout"
|
||||
<com.google.android.material.card.MaterialCardView android:id="@+id/playlist_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:layout_height="wrap_content">
|
||||
app:strokeWidth="0dp"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/download_card_view"
|
||||
android:layout_width="0dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="3dp"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkBox"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeWidth="0dp">
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="10dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/downloads_image_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="?attr/colorSurfaceVariant"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintHorizontal_weight="0.3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#80000000"
|
||||
android:clickable="false"
|
||||
android:layout_margin="5dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:textStyle="bold"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/downloads_image_view"
|
||||
app:layout_constraintEnd_toEndOf="@+id/downloads_image_view" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/downloads_image_view"
|
||||
android:clickable="false"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintHorizontal_weight="0.4"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,16:9"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
/>
|
||||
android:id="@+id/download_item_data"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkBox"
|
||||
app:layout_constraintHorizontal_weight="0.7"
|
||||
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#80000000"
|
||||
android:clickable="false"
|
||||
android:layout_margin="5dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:textStyle="bold"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12sp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/downloads_image_view"
|
||||
app:layout_constraintEnd_toEndOf="@+id/downloads_image_view" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/download_item_data"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintHorizontal_weight="0.7"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:scrollbars="none"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
||||
<CheckBox
|
||||
android:id="@+id/checkBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
|
@ -19,16 +18,16 @@
|
|||
android:id="@+id/bottomAppBar"
|
||||
style="@style/Widget.Material3.BottomAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="70dp"
|
||||
app:menu="@menu/select_playlist_menu"
|
||||
android:layout_gravity="bottom">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/from_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginStart="50dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:hint="@string/start">
|
||||
|
||||
|
|
@ -41,7 +40,7 @@
|
|||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/to_textinput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/end"
|
||||
|
|
@ -58,12 +57,20 @@
|
|||
|
||||
</com.google.android.material.bottomappbar.BottomAppBar>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/check_all"
|
||||
android:layout_width="wrap_content"
|
||||
app:icon="@drawable/ic_select_all"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_anchor="@id/bottomAppBar"/>
|
||||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Primary"
|
||||
android:layout_width="55dp"
|
||||
app:elevation="0dp"
|
||||
android:elevation="0dp"
|
||||
app:borderWidth="0dp"
|
||||
android:contentDescription="@string/select_all"
|
||||
android:layout_height="55dp"
|
||||
app:layout_anchor="@id/bottomAppBar"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:src="@drawable/ic_select_all"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download_video" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
|
@ -79,44 +86,45 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="20dp">
|
||||
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:elevation="0dp">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
app:menu="@menu/playlist_download_menu"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="6dp"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:navigationIcon="@drawable/baseline_close_24" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<LinearLayout
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/select"
|
||||
android:textSize="25sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_ok_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/count"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/configure_download"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_ok_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottomsheet_ok_button"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:text="@string/ok"
|
||||
app:icon="@drawable/ic_check"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appbar"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -124,9 +132,8 @@
|
|||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/downloadMultipleRecyclerview"
|
||||
android:layout_width="match_parent"
|
||||
tools:listitem="@layout/playlist_item"
|
||||
android:paddingTop="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="80dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<item
|
||||
android:id="@+id/select_between"
|
||||
android:visible="false"
|
||||
android:title="@string/select_between"
|
||||
android:icon="@drawable/ic_select_between"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/delete_results"
|
||||
android:title="@string/remove_results"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<item
|
||||
android:id="@+id/select_between"
|
||||
android:visible="false"
|
||||
android:title="@string/select_between"
|
||||
android:icon="@drawable/ic_select_between"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/delete_results"
|
||||
android:title="@string/remove_results"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:theme="@style/BaseTheme">
|
||||
|
||||
<item
|
||||
android:id="@+id/select_between"
|
||||
android:visible="false"
|
||||
android:title="@string/select_between"
|
||||
android:icon="@drawable/ic_select_between"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:title="@string/invert_selected"
|
||||
android:icon="@drawable/ic_terminal"
|
||||
app:showAsAction="never"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<item
|
||||
android:id="@+id/select_between"
|
||||
android:visible="false"
|
||||
android:title="@string/select_between"
|
||||
android:icon="@drawable/ic_select_between"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/delete_results"
|
||||
android:title="@string/remove_results"
|
||||
|
|
|
|||
|
|
@ -375,4 +375,6 @@
|
|||
<string name="all_queries_finished">All Queries have been processed</string>
|
||||
<string name="select_between">Select Items Between</string>
|
||||
<string name="reset_processed_links">Reset Already Processed Links</string>
|
||||
<string name="no_download_fragments">Don\'t download as Fragments</string>
|
||||
<string name="no_download_fragments_summary">Don\'t use .part files and write directly into output file</string>
|
||||
</resources>
|
||||
|
|
@ -36,6 +36,15 @@
|
|||
app:title="@string/cache_first" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/baseline_save_alt_24"
|
||||
android:key="no_part"
|
||||
app:summary="@string/no_download_fragments_summary"
|
||||
app:title="@string/no_download_fragments" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
|
|
|
|||
|
|
@ -2,241 +2,249 @@
|
|||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_money"
|
||||
app:key="use_sponsorblock"
|
||||
app:title="@string/use_sponsorblock" />
|
||||
<PreferenceCategory android:title="@string/general">
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_money"
|
||||
app:key="use_sponsorblock"
|
||||
app:title="@string/use_sponsorblock" />
|
||||
|
||||
<MultiSelectListPreference
|
||||
app:defaultValue="@array/sponsorblock_settings_default"
|
||||
app:icon="@drawable/ic_money"
|
||||
android:dependency="use_sponsorblock"
|
||||
app:dialogTitle="@string/select_sponsorblock_filtering"
|
||||
app:entries="@array/sponsorblock_settings_entries"
|
||||
app:entryValues="@array/sponsorblock_settings_values"
|
||||
app:key="sponsorblock_filters"
|
||||
app:summary="@string/select_sponsorblock_filtering"
|
||||
app:title="SponsorBlock" />
|
||||
<MultiSelectListPreference
|
||||
app:defaultValue="@array/sponsorblock_settings_default"
|
||||
app:icon="@drawable/ic_money"
|
||||
android:dependency="use_sponsorblock"
|
||||
app:dialogTitle="@string/select_sponsorblock_filtering"
|
||||
app:entries="@array/sponsorblock_settings_entries"
|
||||
app:entryValues="@array/sponsorblock_settings_values"
|
||||
app:key="sponsorblock_filters"
|
||||
app:summary="@string/select_sponsorblock_filtering"
|
||||
app:title="SponsorBlock" />
|
||||
|
||||
<EditTextPreference
|
||||
android:dependency="use_sponsorblock"
|
||||
app:icon="@drawable/ic_money"
|
||||
app:key="sponsorblock_url"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue=""
|
||||
app:title="@string/sponsorblock_api_url" />
|
||||
<EditTextPreference
|
||||
android:dependency="use_sponsorblock"
|
||||
app:icon="@drawable/ic_money"
|
||||
app:key="sponsorblock_url"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue=""
|
||||
app:title="@string/sponsorblock_api_url" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_clock"
|
||||
app:key="mtime"
|
||||
app:summary="@string/enable_mtime_summary"
|
||||
app:title="@string/enable_mtime" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_clock"
|
||||
app:key="mtime"
|
||||
app:summary="@string/enable_mtime_summary"
|
||||
app:title="@string/enable_mtime" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/baseline_video_metadata"
|
||||
app:key="embed_metadata"
|
||||
app:summary="@string/embed_metadata_summary"
|
||||
app:title="@string/embed_metadata" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/baseline_description_24"
|
||||
app:key="write_description"
|
||||
android:summary="@string/write_description_summary"
|
||||
app:title="@string/write_description" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_subtitles"
|
||||
app:key="embed_subtitles"
|
||||
app:summary="@string/embed_subs_summary"
|
||||
app:title="@string/embed_subtitles" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_terminal"
|
||||
app:key="use_extra_commands"
|
||||
android:summary="@string/use_extra_commands_summary"
|
||||
app:title="@string/use_extra_commands" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_subtitles_alternative"
|
||||
app:key="write_subtitles"
|
||||
app:summary="@string/save_subs_desc"
|
||||
app:title="@string/save_subs" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_subtitles_alternative"
|
||||
app:key="write_auto_subtitles"
|
||||
app:summary="@string/save_subs_desc"
|
||||
app:title="@string/save_auto_subs" />
|
||||
<PreferenceCategory android:title="@string/audio">
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/baseline_video_metadata"
|
||||
app:key="embed_metadata"
|
||||
app:summary="@string/embed_metadata_summary"
|
||||
app:title="@string/embed_metadata" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_subject_24"
|
||||
app:key="subs_lang"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue="en.*,.*-orig"
|
||||
app:title="@string/subtitle_languages" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_image"
|
||||
app:key="embed_thumbnail"
|
||||
app:summary="@string/embed_thumb_summary"
|
||||
app:title="@string/embed_thumb" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:dependency="embed_thumbnail"
|
||||
app:icon="@drawable/ic_cut"
|
||||
app:key="crop_thumbnail"
|
||||
app:summary="@string/crop_thumb_summary"
|
||||
app:title="@string/crop_thumb" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_cut"
|
||||
app:key="force_keyframes"
|
||||
app:summary="@string/force_keyframes_summary"
|
||||
app:title="@string/force_keyframes" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="srt"
|
||||
android:entries="@array/sub_formats"
|
||||
android:entryValues="@array/sub_formats_values"
|
||||
android:icon="@drawable/baseline_format_quote_24"
|
||||
app:key="sub_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/subtitle_format" />
|
||||
<PreferenceCategory android:title="@string/video">
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_subtitles"
|
||||
app:key="embed_subtitles"
|
||||
app:summary="@string/embed_subs_summary"
|
||||
app:title="@string/embed_subtitles" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/baseline_description_24"
|
||||
app:key="write_description"
|
||||
android:summary="@string/write_description_summary"
|
||||
app:title="@string/write_description" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_subtitles_alternative"
|
||||
app:key="write_subtitles"
|
||||
app:summary="@string/save_subs_desc"
|
||||
app:title="@string/save_subs" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_image"
|
||||
app:key="embed_thumbnail"
|
||||
app:summary="@string/embed_thumb_summary"
|
||||
app:title="@string/embed_thumb" />
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_subtitles_alternative"
|
||||
app:key="write_auto_subtitles"
|
||||
app:summary="@string/save_subs_desc"
|
||||
app:title="@string/save_auto_subs" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
android:dependency="embed_thumbnail"
|
||||
app:icon="@drawable/ic_cut"
|
||||
app:key="crop_thumbnail"
|
||||
app:summary="@string/crop_thumb_summary"
|
||||
app:title="@string/crop_thumb" />
|
||||
<Preference
|
||||
android:icon="@drawable/baseline_subject_24"
|
||||
app:key="subs_lang"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue="en.*,.*-orig"
|
||||
app:title="@string/subtitle_languages" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_cut"
|
||||
app:key="force_keyframes"
|
||||
app:summary="@string/force_keyframes_summary"
|
||||
app:title="@string/force_keyframes" />
|
||||
<ListPreference
|
||||
android:defaultValue="srt"
|
||||
android:entries="@array/sub_formats"
|
||||
android:entryValues="@array/sub_formats_values"
|
||||
android:icon="@drawable/baseline_format_quote_24"
|
||||
app:key="sub_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/subtitle_format" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_image"
|
||||
app:key="write_thumbnail"
|
||||
app:summary="@string/save_thumb_summary"
|
||||
app:title="@string/save_thumb" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="png"
|
||||
android:entries="@array/thumbnail_containers"
|
||||
android:entryValues="@array/thumbnail_containers_values"
|
||||
android:icon="@drawable/ic_image"
|
||||
app:key="thumbnail_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/thumbnail_format" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_chapters"
|
||||
app:key="add_chapters"
|
||||
app:summary="@string/add_chapters_summary"
|
||||
app:title="@string/add_chapters" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/format">
|
||||
<ListPreference
|
||||
android:defaultValue="Default"
|
||||
android:entries="@array/audio_containers"
|
||||
android:entryValues="@array/audio_containers_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:key="audio_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/audio_format" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="Default"
|
||||
android:entries="@array/video_containers"
|
||||
android:entryValues="@array/video_containers_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:key="video_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/video_format" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="avc|h264"
|
||||
android:entries="@array/video_codec"
|
||||
android:entryValues="@array/video_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:key="video_codec"
|
||||
app:title="@string/preferred_video_codec" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="m4a|mp4a|aac"
|
||||
android:entries="@array/audio_codec"
|
||||
android:entryValues="@array/audio_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:key="audio_codec"
|
||||
app:title="@string/preferred_audio_codec" />
|
||||
|
||||
<ListPreference
|
||||
app:icon="@drawable/ic_language"
|
||||
app:dialogTitle="@string/preferred_audio_language"
|
||||
app:entries="@array/language_names"
|
||||
app:entryValues="@array/language_codes"
|
||||
app:key="audio_language"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/preferred_audio_language" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_music"
|
||||
app:key="use_audio_quality"
|
||||
app:title="@string/custom_audio_quality" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_image"
|
||||
app:key="write_thumbnail"
|
||||
app:summary="@string/save_thumb_summary"
|
||||
app:title="@string/save_thumb" />
|
||||
<SeekBarPreference
|
||||
android:defaultValue="0"
|
||||
android:max="10"
|
||||
android:dependency="use_audio_quality"
|
||||
app:key="audio_quality"
|
||||
app:min="0"
|
||||
app:showSeekBarValue="true"
|
||||
app:summary="@string/audio_quality_summary"
|
||||
app:title="@string/audio_quality" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="png"
|
||||
android:entries="@array/thumbnail_containers"
|
||||
android:entryValues="@array/thumbnail_containers_values"
|
||||
android:icon="@drawable/ic_image"
|
||||
app:key="thumbnail_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/thumbnail_format" />
|
||||
<ListPreference
|
||||
android:defaultValue="best"
|
||||
android:entries="@array/video_formats"
|
||||
android:entryValues="@array/video_formats_values"
|
||||
android:icon="@drawable/ic_video"
|
||||
app:key="video_quality"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/video_quality" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="format_id"
|
||||
app:defaultValue=""
|
||||
app:summary="@string/preferred_format_id_summary"
|
||||
app:title="@string/preferred_format_id" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_chapters"
|
||||
app:key="add_chapters"
|
||||
app:summary="@string/add_chapters_summary"
|
||||
app:title="@string/add_chapters" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="Default"
|
||||
android:entries="@array/audio_containers"
|
||||
android:entryValues="@array/audio_containers_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:key="audio_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/audio_format" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="Default"
|
||||
android:entries="@array/video_containers"
|
||||
android:entryValues="@array/video_containers_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:key="video_format"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/video_format" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="avc|h264"
|
||||
android:entries="@array/video_codec"
|
||||
android:entryValues="@array/video_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:key="video_codec"
|
||||
app:title="@string/preferred_video_codec" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="m4a|mp4a|aac"
|
||||
android:entries="@array/audio_codec"
|
||||
android:entryValues="@array/audio_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:key="audio_codec"
|
||||
app:title="@string/preferred_audio_codec" />
|
||||
|
||||
<ListPreference
|
||||
app:icon="@drawable/ic_language"
|
||||
app:dialogTitle="@string/preferred_audio_language"
|
||||
app:entries="@array/language_names"
|
||||
app:entryValues="@array/language_codes"
|
||||
app:key="audio_language"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/preferred_audio_language" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:icon="@drawable/ic_music"
|
||||
app:key="use_audio_quality"
|
||||
app:title="@string/custom_audio_quality" />
|
||||
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="0"
|
||||
android:max="10"
|
||||
android:dependency="use_audio_quality"
|
||||
app:key="audio_quality"
|
||||
app:min="0"
|
||||
app:showSeekBarValue="true"
|
||||
app:summary="@string/audio_quality_summary"
|
||||
app:title="@string/audio_quality" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="best"
|
||||
android:entries="@array/video_formats"
|
||||
android:entryValues="@array/video_formats_values"
|
||||
android:icon="@drawable/ic_video"
|
||||
app:key="video_quality"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/video_quality" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="format_id"
|
||||
app:defaultValue=""
|
||||
app:summary="@string/preferred_format_id_summary"
|
||||
app:title="@string/preferred_format_id" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="format_id_audio"
|
||||
app:defaultValue=""
|
||||
app:summary="@string/preferred_format_id_summary"
|
||||
app:title="@string/preferred_format_id" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
app:icon="@drawable/ic_terminal"
|
||||
app:key="use_extra_commands"
|
||||
android:summary="@string/use_extra_commands_summary"
|
||||
app:title="@string/use_extra_commands" />
|
||||
<EditTextPreference
|
||||
app:key="format_id_audio"
|
||||
app:defaultValue=""
|
||||
app:summary="@string/preferred_format_id_summary"
|
||||
app:title="@string/preferred_format_id" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
app:title="@string/auto_update_ytdlp" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="piped"
|
||||
android:defaultValue="yt-dlp"
|
||||
android:entries="@array/formats_source"
|
||||
android:entryValues="@array/formats_source_values"
|
||||
android:icon="@drawable/baseline_manage_search_24"
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ buildscript {
|
|||
coroutineVer = '1.7.3'
|
||||
retrofitVer = "2.9.0"
|
||||
kodeinVer = "7.16.0"
|
||||
navVer = "2.7.6"
|
||||
media3_version = "1.2.0"
|
||||
navVer = "2.7.7"
|
||||
media3_version = "1.2.1"
|
||||
agp_version = '8.1.0'
|
||||
agp_version1 = '7.4.2'
|
||||
roomVer = '2.5.2'
|
||||
|
|
|
|||
Loading…
Reference in a new issue