diff --git a/app/build.gradle b/app/build.gradle
index 4db043aa..d38a1712 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1257f698..2f02a4bb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,19 +4,23 @@
-
+
-
-
+
-
+
@@ -71,6 +75,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- 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(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()
+ }
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt
index 9bd915b7..313329f3 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt
@@ -59,7 +59,7 @@ interface DownloadDao {
fun getProcessingDownloadsList() : List
@Query("SELECT * FROM downloads WHERE status='Active'")
- fun getActiveDownloadsList() : List
+ suspend fun getActiveDownloadsList() : List
@Query("SELECT * FROM downloads WHERE status in('Active','Queued','QueuedPaused','ActivePaused','PausedReQueued')")
fun getActiveAndQueuedDownloadsList() : List
@@ -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) : List
+
+ @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
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt
index 7726d340..cc0728bd 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ResultDao.kt
@@ -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>
@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
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt
index f1592553..945fc29a 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt
@@ -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)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt
index 9f13a414..21afdb7c 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/HistoryRepository.kt
@@ -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 {
+ 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)
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
index 0368e712..7294969e 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
@@ -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)
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt
index 74c6546e..c597d191 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CommandTemplateViewModel.kt
@@ -179,4 +179,15 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi
}
}
+ fun getRecordsBetweenTwoItems(item1: Long, item2: Long) : List {
+ 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 }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt
index aac001af..0e7c902d 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt
@@ -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>
@@ -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()
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)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
index 8a640305..64461768 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
@@ -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 {
+ return dao.getQueuedIDsBetweenTwoItems(item1, item2)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt
index 93adf859..8f8a9d7b 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/HistoryViewModel.kt
@@ -117,4 +117,15 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
repository.clearDeletedHistory()
}
+ fun getRecordsBetweenTwoItems(item1: Long, item2: Long) : List {
+ 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 }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
index 61c8eac6..6d8d034f 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
@@ -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>
private val infoUtil: InfoUtil
private val notificationUtil: NotificationUtil
+ private val dao: ResultDao
data class ResultsUiState(
var processing: Boolean,
var errorMessage: Pair?,
@@ -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().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{
+ return dao.getResultsBetweenTwoItems(item1, item2)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
index 8ab63b6a..77af5037 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
@@ -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? = null
- private var selectedObjects: ArrayList? = null
+ private lateinit var selectedObjects: ArrayList
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()
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()
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt
index adee2e73..6d8b2165 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt
@@ -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, 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