diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/14.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/14.json index 1a7044f5..a03ef5bd 100644 --- a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/14.json +++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/14.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 14, - "identityHash": "5cea521e1a26473b2ec5298791f10ac4", + "identityHash": "5be3b7ae26c7716f2192470a3a9249a8", "entities": [ { "tableName": "results", @@ -587,12 +587,131 @@ }, "indices": [], "foreignKeys": [] + }, + { + "tableName": "observeSources", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `status` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyWeekDay` TEXT NOT NULL, `everyMonthDay` INTEGER NOT NULL, `everyTime` INTEGER NOT NULL, `startsTime` INTEGER NOT NULL, `startsMonth` TEXT NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `alreadyProcessedLinks` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloadItemTemplate", + "columnName": "downloadItemTemplate", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyNr", + "columnName": "everyNr", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "everyCategory", + "columnName": "everyCategory", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyWeekDay", + "columnName": "everyWeekDay", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "everyMonthDay", + "columnName": "everyMonthDay", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "everyTime", + "columnName": "everyTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startsTime", + "columnName": "startsTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "startsMonth", + "columnName": "startsMonth", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "endsDate", + "columnName": "endsDate", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "endsAfterCount", + "columnName": "endsAfterCount", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "runCount", + "columnName": "runCount", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "retryMissingDownloads", + "columnName": "retryMissingDownloads", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "alreadyProcessedLinks", + "columnName": "alreadyProcessedLinks", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5cea521e1a26473b2ec5298791f10ac4')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5be3b7ae26c7716f2192470a3a9249a8')" ] } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1de615b5..dd009739 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -202,8 +202,9 @@ - - + + + ) = Gson().toJson(s) + + @TypeConverter + fun stringtoMutableListofStrings(s: String) = Gson().fromJson(s, Array::class.java).toMutableList() } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt index 847460f5..8c51f85e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt @@ -21,7 +21,8 @@ import com.deniscerri.ytdlnis.database.models.* TemplateShortcut::class, CookieItem::class, LogItem::class, - TerminalItem::class + TerminalItem::class, + ObserveSourcesItem::class ], version = 14, autoMigrations = [ @@ -37,7 +38,7 @@ import com.deniscerri.ytdlnis.database.models.* AutoMigration (from = 10, to = 11), AutoMigration (from = 11, to = 12), AutoMigration (from = 12, to = 13), - //AutoMigration (from = 13, to = 14), MANUALLY HANDLED + // AutoMigration (from = 13, to = 14) MANUALLY HANDLED ] ) abstract class DBManager : RoomDatabase(){ @@ -49,6 +50,7 @@ abstract class DBManager : RoomDatabase(){ abstract val cookieDao: CookieDao abstract val logDao: LogDao abstract val terminalDao: TerminalDao + abstract val observeSourcesDao: ObserveSourcesDao enum class SORTING{ DESC, ASC diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/Migrations.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/Migrations.kt index 0943ca2b..e551df41 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/Migrations.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/Migrations.kt @@ -1,39 +1,7 @@ package com.deniscerri.ytdlnis.database import android.annotation.SuppressLint -import android.app.Dialog -import android.content.Context -import android.content.res.Resources -import android.graphics.Color -import android.graphics.Outline -import android.graphics.drawable.ShapeDrawable -import android.graphics.drawable.shapes.OvalShape -import android.media.MediaMetadataRetriever -import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION -import android.net.Uri -import android.util.DisplayMetrics -import android.view.MotionEvent -import android.view.View -import android.view.ViewGroup -import android.view.ViewOutlineProvider -import android.widget.EditText -import android.widget.TextView -import androidx.annotation.Px -import androidx.core.view.updateLayoutParams -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.withStarted -import androidx.recyclerview.widget.RecyclerView import androidx.room.migration.Migration -import androidx.work.impl.Migration_12_13 -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.neo.highlight.core.Highlight -import com.neo.highlight.util.listener.HighlightTextWatcher -import com.neo.highlight.util.scheme.ColorScheme -import kotlinx.coroutines.launch -import me.zhanghai.android.fastscroll.FastScrollerBuilder -import java.io.File -import java.util.regex.Pattern object Migrations { @@ -48,7 +16,10 @@ object Migrations { val path = cursor.getString(cursor.getColumnIndex("downloadPath")) val newPath = "[\"${path.replace("\"", "\\\"").replace("'", "''")}\"]" database.execSQL("UPDATE history SET downloadPath = '${newPath}' WHERE id = $id") + } + + database.execSQL("CREATE TABLE IF NOT EXISTS `observeSources` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `status` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyWeekDay` TEXT NOT NULL, `everyMonthDay` INTEGER NOT NULL, `everyTime` INTEGER NOT NULL, `startsTime` INTEGER NOT NULL, `startsMonth` TEXT NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `alreadyProcessedLinks` TEXT NOT NULL)") } ) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt index f570b5e7..d5ad77f0 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt @@ -9,6 +9,9 @@ interface CookieDao { @Query("SELECT * FROM cookies ORDER BY id DESC") fun getAllCookies() : List + @Query("SELECT * FROM cookies WHERE url = :url LIMIT 1") + fun getByURL(url: String) : CookieItem + @Query("SELECT * FROM cookies ORDER BY id DESC") fun getAllCookiesFlow() : Flow> 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 413c1613..302c4bb0 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 @@ -16,6 +16,9 @@ interface DownloadDao { @Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')") fun getActiveDownloads() : Flow> + @Query("SELECT * FROM downloads WHERE status = 'Processing'") + fun getProcessingDownloads() : Flow> + @Query("SELECT COUNT(*) FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')") fun getActiveDownloadsCountFlow() : Flow @@ -25,9 +28,29 @@ interface DownloadDao { @Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)") fun getDownloadsCountByStatus(status : List) : Int + + @Query(""" + SELECT COUNT(*) FROM downloads WHERE status = 'Processing' + UNION + SELECT COUNT(*) FROM downloads WHERE status ='Processing' AND type = + (SELECT type from downloads WHERE status = 'Processing' ORDER BY id LIMIT 1) + """) + fun getProcessingDownloadsCountByType() : List + + + @Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)") + suspend fun updateItemsToProcessing(ids: List) + + + @Query("SELECT * FROM downloads WHERE status = 'Processing' ORDER BY id LIMIT 1") + fun getFirstProcessingDownload() : DownloadItem + @Query("SELECT * FROM downloads WHERE status in('Active','ActivePaused','PausedReQueued')") fun getActiveAndPausedDownloadsList() : List + @Query("SELECT * FROM downloads WHERE status = 'Processing'") + fun getProcessingDownloadsList() : List + @Query("SELECT * FROM downloads WHERE status='Active'") fun getActiveDownloadsList() : List @@ -126,6 +149,9 @@ interface DownloadDao { @Query("DELETE FROM downloads WHERE status='Saved'") suspend fun deleteSaved() + @Query("DELETE FROM downloads WHERE status='Processing'") + suspend fun deleteProcessing() + @Query("DELETE FROM downloads WHERE id in (:list)") suspend fun deleteAllWithIDs(list: List) @@ -174,12 +200,18 @@ interface DownloadDao { @Query("Select id from downloads where id not in (:list) and status in (:status)") fun getDownloadIDsNotPresentInList(list: List, status: List) : List + @Query("Select url from downloads where status in (:status)") + fun getURLsByStatus(status: List) : List + @Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)") fun resetScheduleTimeForItems(list: List) @Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)") fun reQueueDownloadItems(list: List) + @Query("Update downloads SET status='Saved' WHERE status='Processing'") + fun updateProcessingtoSavedStatus() + @Transaction fun putAtTopOfTheQueue(id: Long){ val downloads = getQueuedDownloadsList() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt new file mode 100644 index 00000000..2ee57b32 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/ObserveSourcesDao.kt @@ -0,0 +1,36 @@ +package com.deniscerri.ytdlnis.database.dao + +import androidx.room.* +import com.deniscerri.ytdlnis.database.models.CookieItem +import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem +import kotlinx.coroutines.flow.Flow + +@Dao +interface ObserveSourcesDao { + @Query("SELECT * FROM observeSources ORDER BY id DESC") + fun getAllSources() : List + + @Query("SELECT * FROM observeSources WHERE url = :url LIMIT 1") + fun getByURL(url: String) : ObserveSourcesItem + + @Query("SELECT * FROM observeSources WHERE id = :id LIMIT 1") + fun getByID(id: Long) : ObserveSourcesItem + + @Query("SELECT * FROM observeSources ORDER BY id DESC") + fun getAllSourcesFlow() : Flow> + + @Query("SELECT EXISTS(SELECT * FROM observeSources WHERE url=:url LIMIT 1)") + fun checkIfExistsWithSameURL(url: String) : Boolean + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(item: ObserveSourcesItem) : Long + + @Query("DELETE FROM observeSources") + suspend fun deleteAll() + + @Query("DELETE FROM observeSources WHERE id=:itemId") + suspend fun delete(itemId: Long) + + @Update(onConflict = OnConflictStrategy.REPLACE) + suspend fun update(item: ObserveSourcesItem) +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt index a924eaee..d9a338ba 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/Format.kt @@ -27,5 +27,7 @@ data class Format( @SerializedName(value = "url") var url: String? = "", @SerializedName(value = "language", alternate = ["audioTrackLocale"]) - val lang: String? = "" + val lang: String? = "", + @SerializedName(value = "tbr", alternate = ["bitrate"]) + var tbr: String? = "" ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt new file mode 100644 index 00000000..4241b58c --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/ObserveSourcesItem.kt @@ -0,0 +1,37 @@ +package com.deniscerri.ytdlnis.database.models + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository +import kotlinx.parcelize.Parcelize +import java.time.Month +import java.util.Calendar +import java.util.Date + +@Entity(tableName = "observeSources") +@Parcelize +data class ObserveSourcesItem( + @PrimaryKey(autoGenerate = true) + var id: Long, + var name: String, + var url: String, + var downloadItemTemplate: DownloadItem, + var status: ObserveSourcesRepository.SourceStatus, + var everyNr: Int, + var everyCategory: ObserveSourcesRepository.EveryCategory, + val everyWeekDay: List, + val everyMonthDay: Int, + val everyTime: Long, + var startsTime: Long, + val startsMonth: Month, + @ColumnInfo(defaultValue = "0") + var endsDate: Long, + @ColumnInfo(defaultValue = "0") + var endsAfterCount: Int, + @ColumnInfo(defaultValue = "0") + var runCount: Int, + var retryMissingDownloads: Boolean, + var alreadyProcessedLinks : MutableList +) : Parcelable diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt index 3b2217fe..9fad348a 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/VideoPreferences.kt @@ -10,6 +10,7 @@ data class VideoPreferences ( var splitByChapters: Boolean = false, var sponsorBlockFilters: ArrayList = arrayListOf(), var writeSubs: Boolean = false, + var writeAutoSubs: Boolean = false, var subsLanguages: String = "en.*,.*-orig", var audioFormatIDs : ArrayList = arrayListOf(), var removeAudio: Boolean = false diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt index f14b9558..b42699a9 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt @@ -11,6 +11,10 @@ class CookieRepository(private val cookieDao: CookieDao) { return cookieDao.getAllCookies() } + fun getByURL(url: String) : CookieItem { + return cookieDao.getByURL(url) + } + suspend fun insert(item: CookieItem) : Long{ if (! cookieDao.checkIfExistsWithSameURL(item.url)){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt index 2e9e390c..b8f31293 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt @@ -18,6 +18,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) { pagingSourceFactory = {downloadDao.getAllDownloads()} ) val activeDownloads : Flow> = downloadDao.getActiveDownloads().distinctUntilChanged() + val processingDownloads : Flow> = downloadDao.getProcessingDownloads().distinctUntilChanged() val queuedDownloads : Pager = Pager( config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1), pagingSourceFactory = {downloadDao.getQueuedDownloads()} @@ -38,7 +39,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) { val activeDownloadsCount : Flow = downloadDao.getActiveDownloadsCountFlow() enum class Status { - Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved + Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing } suspend fun insert(item: DownloadItem) : Long { @@ -58,7 +59,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) { private fun deleteCache(items: List) { val cacheDir = FileUtil.getCachePath(App.instance) items.forEach { - File(cacheDir, it.id.toString()).deleteRecursively() + runCatching { File(cacheDir, it.id.toString()).deleteRecursively() } } } @@ -84,6 +85,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) { return downloadDao.getActiveAndPausedDownloadsList() } + fun getProcessingDownloads() : List { + return downloadDao.getProcessingDownloadsList() + } + fun getActiveAndQueuedDownloads() : List { return downloadDao.getActiveAndQueuedDownloadsList() } @@ -124,6 +129,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) { downloadDao.deleteSaved() } + suspend fun deleteProcessing(){ + downloadDao.deleteProcessing() + } + suspend fun deleteAllWithIDs(ids: List){ downloadDao.deleteAllWithIDs(ids) 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 54cc9fd5..a90d876b 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 @@ -26,8 +26,8 @@ class HistoryRepository(private val historyDao: HistoryDao) { return historyDao.getAllHistoryByURL(url) } - fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) : List { - return when(sortType){ + fun getFiltered(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()) @@ -39,6 +39,11 @@ class HistoryRepository(private val historyDao: HistoryDao) { } } } + if(notDeleted){ + filtered = filtered.filter { it.downloadPath.any { it2 -> FileUtil.exists(it2) } } + } + + return filtered } suspend fun insert(item: HistoryItem){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt new file mode 100644 index 00000000..9ee8bf3d --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ObserveSourcesRepository.kt @@ -0,0 +1,60 @@ +package com.deniscerri.ytdlnis.database.repository + +import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao +import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem +import kotlinx.coroutines.flow.Flow + +class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) { + val items : Flow> = observeSourcesDao.getAllSourcesFlow() + enum class SourceStatus { + ACTIVE, STOPPED + } + + enum class EveryCategory { + DAY, WEEK, MONTH + } + + companion object { + val everyCategoryName = mapOf( + EveryCategory.DAY to R.string.day, + EveryCategory.WEEK to R.string.week, + EveryCategory.MONTH to R.string.month + ) + } + + + fun getAll() : List { + return observeSourcesDao.getAllSources() + } + + fun getByURL(url: String) : ObserveSourcesItem { + return observeSourcesDao.getByURL(url) + } + + fun getByID(id: Long) : ObserveSourcesItem { + return observeSourcesDao.getByID(id) + } + + + suspend fun insert(item: ObserveSourcesItem) : Long{ + if (!observeSourcesDao.checkIfExistsWithSameURL(item.url)){ + return observeSourcesDao.insert(item) + } + return -1 + } + + suspend fun delete(item: ObserveSourcesItem){ + observeSourcesDao.delete(item.id) + } + + + suspend fun deleteAll(){ + observeSourcesDao.deleteAll() + } + + suspend fun update(item: ObserveSourcesItem){ + observeSourcesDao.update(item) + } + +} \ No newline at end of file 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 5b5e6af5..afe8b8ee 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 @@ -65,6 +65,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co val items : ArrayList = arrayListOf() do { val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "") + val ids = resultDao.insertMultiple(tmp.videos.toList()) + ids.forEachIndexed { index, id -> + tmp.videos[index]?.id = id + } items.addAll(tmp.videos) val tmpToken = tmp.nextPageToken if (tmpToken.isEmpty()) break @@ -72,10 +76,6 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co nextPageToken = tmpToken } while (true) itemCount.value = items.size - val ids = resultDao.insertMultiple(items.toList()) - ids.forEachIndexed { index, id -> - items[index]?.id = id - } return items } 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 b10a588a..dde43c31 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 @@ -17,6 +17,7 @@ import com.deniscerri.ytdlnis.BuildConfig 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.CookiesFragment import com.deniscerri.ytdlnis.ui.more.WebViewActivity import com.deniscerri.ytdlnis.util.FileUtil import kotlinx.coroutines.Dispatchers @@ -43,6 +44,10 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a return repository.getAll() } + fun getByURL(url: String) : CookieItem { + return repository.getByURL(url) + } + suspend fun insert(item: CookieItem) : Long { return repository.insert(item) } 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 80648819..1321e52e 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 @@ -10,6 +10,7 @@ import android.net.ConnectivityManager import android.os.Looper import android.util.DisplayMetrics import android.widget.Toast +import androidx.compose.animation.core.updateTransition import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData @@ -17,6 +18,7 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.preference.PreferenceManager import androidx.work.Constraints +import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder @@ -39,10 +41,13 @@ import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.models.VideoPreferences import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.repository.HistoryRepository +import com.deniscerri.ytdlnis.ui.downloadcard.FormatTuple +import com.deniscerri.ytdlnis.util.DownloadUtil import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.work.AlarmScheduler import com.deniscerri.ytdlnis.work.DownloadWorker +import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -57,7 +62,7 @@ import java.util.Locale import java.util.concurrent.TimeUnit -class DownloadViewModel(application: Application) : AndroidViewModel(application) { +class DownloadViewModel(private val application: Application) : AndroidViewModel(application) { private val dbManager: DBManager val repository : DownloadRepository private val sharedPreferences: SharedPreferences @@ -66,6 +71,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val allDownloads : Flow> val queuedDownloads : Flow> val activeDownloads : Flow> + val processingDownloads : Flow> val activeDownloadsCount : Flow val cancelledDownloads : Flow> val erroredDownloads : Flow> @@ -89,19 +95,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application private val dao: DownloadDao private val historyRepository: HistoryRepository - data class DownloadsUiState( - var errorMessage: Pair?, - //action title, type, data - var actions: MutableList?>>? + data class AlreadyExistsUIState( + var historyItems: MutableList, + var downloadItems : MutableList ) - enum class DownloadsAction { - DOWNLOAD_ANYWAY - } - - val uiState: MutableStateFlow = MutableStateFlow(DownloadsUiState( - errorMessage = null, - actions = null + val alreadyExistsUiState: MutableStateFlow = MutableStateFlow(AlreadyExistsUIState( + historyItems = mutableListOf(), + downloadItems = mutableListOf() )) enum class Type { @@ -126,6 +127,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application allDownloads = repository.allDownloads.flow queuedDownloads = repository.queuedDownloads.flow activeDownloads = repository.activeDownloads + processingDownloads = repository.processingDownloads activeDownloadsCount = repository.activeDownloadsCount savedDownloads = repository.savedDownloads.flow cancelledDownloads = repository.cancelledDownloads.flow @@ -223,6 +225,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false) val saveSubs = sharedPreferences.getBoolean("write_subtitles", false) + val saveAutoSubs = sharedPreferences.getBoolean("write_auto_subtitles", false) val addChapters = sharedPreferences.getBoolean("add_chapters", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) @@ -261,6 +264,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application addChapters, false, ArrayList(sponsorblock), saveSubs, + saveAutoSubs, audioFormatIDs = preferredAudioFormats ) @@ -391,6 +395,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application fun createDownloadItemFromHistory(historyItem: HistoryItem) : DownloadItem { val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false) val saveSubs = sharedPreferences.getBoolean("write_subtitles", false) + val saveAutoSubs = sharedPreferences.getBoolean("write_auto_subtitles", false) val addChapters = sharedPreferences.getBoolean("add_chapters", false) val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false) val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false) @@ -424,7 +429,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!)) - val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs) + val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, saveAutoSubs) var path = defaultPath historyItem.downloadPath.first().apply { File(this).parent?.apply { @@ -480,7 +485,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application Type.audio -> { return cloneFormat ( try { - val theFormats = formats.filter { it.format_note.contains("audio", ignoreCase = true) } + val theFormats = formats.filter { it.vcodec.isBlank() || it.vcodec == "none" } val requirements = getPreferredAudioRequirements() theFormats.maxByOrNull { f -> requirements.count{req -> req(f)} } ?: throw Exception() }catch (e: Exception){ @@ -492,7 +497,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application Type.video -> { return cloneFormat( try { - val theFormats = formats.filter { !it.format_note.contains("audio", true) }.ifEmpty { defaultVideoFormats.sortedByDescending { it.filesize } } + val theFormats = formats.filter { it.vcodec.isNotBlank() && it.vcodec != "null" }.ifEmpty { defaultVideoFormats.sortedByDescending { it.filesize } } when (videoQualityPreference) { "worst" -> { theFormats.last() @@ -587,6 +592,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application } } + fun insertToProcessing(items: List)= viewModelScope.launch(Dispatchers.IO){ + repository.deleteProcessing() + items.forEach{ + it.status = DownloadRepository.Status.Processing.toString() + repository.insert(it) + } + } + fun deleteCancelled() = viewModelScope.launch(Dispatchers.IO) { repository.deleteCancelled() } @@ -599,6 +612,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application repository.deleteSaved() } + fun deleteProcessing() = viewModelScope.launch(Dispatchers.IO) { + repository.deleteProcessing() + } + fun deleteAllWithID(ids: List) = viewModelScope.launch(Dispatchers.IO){ repository.deleteAllWithIDs(ids) } @@ -646,27 +663,31 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application suspend fun resetScheduleTimeForItemsAndStartDownload(items: List) = CoroutineScope(Dispatchers.IO).launch { dbManager.downloadDao.resetScheduleTimeForItems(items) - startDownloadWorker(emptyList()) + DownloadUtil.startDownloadWorker(emptyList(), application) } suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{ dbManager.downloadDao.putAtTopOfTheQueue(id) - startDownloadWorker(emptyList()) + DownloadUtil.startDownloadWorker(emptyList(), application) } suspend fun reQueueDownloadItems(items: List) = CoroutineScope(Dispatchers.IO).launch { dbManager.downloadDao.reQueueDownloadItems(items) - startDownloadWorker(emptyList()) + DownloadUtil.startDownloadWorker(emptyList(), application) } - suspend fun queueDownloads(items: List, ignoreDuplicate : Boolean = false) = CoroutineScope(Dispatchers.IO).launch { + suspend fun queueDownloads(items: List, ign : Boolean = false) : Pair, List> { val context = App.instance val alarmScheduler = AlarmScheduler(context) val activeAndQueuedDownloads = withContext(Dispatchers.IO){ repository.getActiveAndQueuedDownloads() } val queuedItems = mutableListOf() - val existing = mutableListOf() + val existingDownloads = mutableListOf() + val existingHistory = mutableListOf() + + var ignoreDuplicate = ign + if (sharedPreferences.getBoolean("download_archive", false)) ignoreDuplicate = true //if scheduler is on val useScheduler = sharedPreferences.getBoolean("use_scheduler", false) @@ -680,122 +701,85 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application items.forEach { if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString() - val currentCommand = infoUtil.buildYoutubeDLRequest(it) - val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand) - val existingDownload = activeAndQueuedDownloads.firstOrNull{d -> - d.id = 0 - d.logID = null - d.customFileNameTemplate = it.customFileNameTemplate - d.status = DownloadRepository.Status.Queued.toString() - d.toString() == it.toString() - } - if (existingDownload != null && !ignoreDuplicate) { - it.id = existingDownload.id - existing.add(it) - }else{ - //check if downloaded and file exists - val history = withContext(Dispatchers.IO){ - historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } } + var alreadyExists = false + + if(!ignoreDuplicate){ + val currentCommand = infoUtil.buildYoutubeDLRequest(it) + val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand) + val existingDownload = activeAndQueuedDownloads.firstOrNull{d -> + d.id = 0 + d.logID = null + d.customFileNameTemplate = it.customFileNameTemplate + d.status = DownloadRepository.Status.Queued.toString() + d.toString() == it.toString() } - val existingHistory = history.firstOrNull { - h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") - } - - if (existingHistory != null && !ignoreDuplicate){ - it.id = existingHistory.id - existing.add(it) + if (existingDownload != null){ + it.status = DownloadRepository.Status.Saved.toString() + val id = runBlocking { + repository.insert(it) + } + alreadyExists = true + existingDownloads.add(id) }else{ - if (it.id == 0L){ - val id = runBlocking { - repository.insert(it) - } - it.id = id - }else if (it.status == DownloadRepository.Status.Queued.toString()){ - withContext(Dispatchers.IO){ - repository.update(it) - } + //check if downloaded and file exists + val history = withContext(Dispatchers.IO){ + historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } } } - queuedItems.add(it) + val existingHistoryItem = history.firstOrNull { + h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") + } + + if (existingHistoryItem != null){ + alreadyExists = true + existingHistory.add(existingHistoryItem.id) + } + } + } + + if (!alreadyExists){ + if (it.id == 0L){ + val id = runBlocking { + repository.insert(it) + } + it.id = id + }else if (it.status == DownloadRepository.Status.Queued.toString()){ + withContext(Dispatchers.IO){ + repository.update(it) + } + } + + queuedItems.add(it) + } + + } + + if (existingDownloads.isNotEmpty() || existingHistory.isNotEmpty()){ + alreadyExistsUiState.update { u -> u.copy(existingHistory, existingDownloads) } + } + + if (queuedItems.isNotEmpty()){ + if (!useScheduler || alarmScheduler.isDuringTheScheduledTime()){ + DownloadUtil.startDownloadWorker(queuedItems, context) + + if(!useScheduler){ + queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach { + try{ + updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao) + }catch (ignored: Exception){} + } + }else{ + queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach { + try{ + updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao) + }catch (ignored: Exception){} + } } } } - if (existing.isNotEmpty()){ - uiState.update { u -> u.copy( - errorMessage = Pair(R.string.download_already_exists, R.string.download_already_exists_summary), - actions = mutableListOf( - Triple(R.string.download, DownloadsAction.DOWNLOAD_ANYWAY, existing), - ) - ) - } - } - - if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){ - startDownloadWorker(queuedItems) - - if(!useScheduler){ - queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach { - try{ - updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao) - }catch (ignored: Exception){} - } - }else{ - queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach { - try{ - updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao) - }catch (ignored: Exception){} - } - } - } - } - - @SuppressLint("RestrictedApi") - private suspend fun startDownloadWorker(queuedItems: List) { - val context = App.instance - val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true) - val workManager = WorkManager.getInstance(context) - - val currentWork = workManager.getWorkInfosByTag("download").await() - if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){ - - val currentTime = System.currentTimeMillis() - var delay = 0L - if (queuedItems.isNotEmpty()){ - delay = if (queuedItems[0].downloadStartTime != 0L){ - queuedItems[0].downloadStartTime.minus(currentTime) - } else 0 - if (delay <= 60000L) delay = 0L - } - - - val workConstraints = Constraints.Builder() - if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED) - - val workRequest = OneTimeWorkRequestBuilder() - .addTag("download") - .setConstraints(workConstraints.build()) - .setInitialDelay(delay, TimeUnit.MILLISECONDS) - - queuedItems.forEach { - workRequest.addTag(it.id.toString()) - } - - workManager.enqueueUniqueWork( - System.currentTimeMillis().toString(), - ExistingWorkPolicy.REPLACE, - workRequest.build() - ) - - } - - val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered - if (!allowMeteredNetworks && isCurrentNetworkMetered){ - Looper.prepare().run { - Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show() - } - } + return Pair(existingDownloads, existingHistory) } fun getQueuedCollectedFileSize() : Long { @@ -814,6 +798,132 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() }) } + fun moveProcessingToSavedCategory(){ + dao.updateProcessingtoSavedStatus() + } + + suspend fun downloadProcessingDownloads(timeInMillis: Long = 0){ + repository.getProcessingDownloads().apply { + if (timeInMillis > 0){ + this.forEach { + it.downloadStartTime = timeInMillis + } + } + + queueDownloads(this) + } + } + + suspend fun updateProcessingFormat(selectedFormats: List): List { + val items = repository.getProcessingDownloads() + items.forEachIndexed { index, i -> + i.format = selectedFormats[index].format + if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) } + updateDownload(i) + } + + return items.map { it.format.filesize } + } + + suspend fun updateProcessingCommandFormat(format: Format){ + val items = repository.getProcessingDownloads() + items.forEach { i -> + i.format = format + updateDownload(i) + } + } + + suspend fun updateProcessingDownloadPath(path: String){ + val items = repository.getProcessingDownloads() + items.forEach { i -> + i.downloadPath = path + updateDownload(i) + } + } + + fun getProcessingDownloadsCount() : Int { + return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Processing.toString())) + } + + fun getProcessingDownloads() : List { + return repository.getProcessingDownloads() + } + + + suspend fun updateProcessingAllFormats(formatCollection: List>) { + val items = repository.getProcessingDownloads() + items.forEachIndexed { index, i -> + i.allFormats.clear() + if (formatCollection.size == items.size && formatCollection[index].isNotEmpty()) { + runCatching { + i.allFormats.addAll(formatCollection[index]) + } + } + i.format = getFormat(i.allFormats, i.type) + kotlin.runCatching { + dbManager.resultDao.getResultByURL(i.url)?.apply { + this.formats = formatCollection[index].toMutableList() + dbManager.resultDao.update(this) + } + } + updateDownload(i) + } + } + + suspend fun continueUpdatingFormatsOnBackground(){ + dao.getProcessingDownloadsList().apply { + this.forEach { + it.status = DownloadRepository.Status.Saved.toString() + updateDownload(it) + } + + val ids = this.map { it.id } + val id = System.currentTimeMillis().toInt() + val workRequest = OneTimeWorkRequestBuilder() + .setInputData( + Data.Builder() + .putLongArray("ids", ids.toLongArray()) + .putInt("id", id) + .build()) + .addTag("updateFormats") + .build() + val context = App.instance + WorkManager.getInstance(context).enqueueUniqueWork( + id.toString(), + ExistingWorkPolicy.REPLACE, + workRequest + ) + } + } + + suspend fun updateProcessingType(newType: Type) { + repository.getProcessingDownloads().apply { + val new = switchDownloadType(this, newType) + new.forEach { + updateDownload(it) + } + } + } + + fun checkIfAllProcessingItemsHaveSameType() : Pair { + val counts = dao.getProcessingDownloadsCountByType() + val sameType = counts.size == 1 || counts[0] == counts[1] + val first = dao.getFirstProcessingDownload() + return Pair(sameType, first.type) + } + + + suspend fun updateItemsWithIdsToProcessingStatus(ids: List) : Type { + repository.deleteProcessing() + dao.updateItemsToProcessing(ids) + val first = dao.getFirstProcessingDownload() + return first.type + } + + fun getURLsByStatus(list: List) : List { + return dao.getURLsByStatus(list.map { it.toString() }) + } + private fun updateDownloadItem( downloadItem: DownloadItem, infoUtil: InfoUtil, 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 c38a6b97..cb8646c2 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 @@ -22,6 +22,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) val websiteFilter = MutableLiveData("") private val queryFilter = MutableLiveData("") private val formatFilter = MutableLiveData("") + private val notDeletedFilter = MutableLiveData(false) val allItems : LiveData> private var _items = MediatorLiveData>() @@ -32,19 +33,23 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) allItems = repository.items.asLiveData() _items.addSource(allItems){ - filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!) + filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!) } _items.addSource(formatFilter){ - filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!) + filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!) } _items.addSource(sortType){ - filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!) + filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!) } _items.addSource(websiteFilter){ - filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!) + filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!) } _items.addSource(queryFilter){ - filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!) + filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!) + } + + _items.addSource(notDeletedFilter){ + filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!) } } @@ -76,8 +81,12 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application) formatFilter.value = filter } - private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) = viewModelScope.launch(Dispatchers.IO){ - _items.postValue(repository.getFiltered(query, format, site, sortType, sort)) + fun setNotDeleted(filter: Boolean){ + notDeletedFilter.value = filter + } + + private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) = viewModelScope.launch(Dispatchers.IO){ + _items.postValue(repository.getFiltered(query, format, site, sortType, sort, notDeleted)) } fun getAll() : List { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt new file mode 100644 index 00000000..5d07cd3d --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ObserveSourcesViewModel.kt @@ -0,0 +1,142 @@ +package com.deniscerri.ytdlnis.database.viewmodel + +import android.app.AlarmManager +import android.app.Application +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import androidx.preference.PreferenceManager +import com.deniscerri.ytdlnis.database.DBManager +import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem +import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository +import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver +import com.deniscerri.ytdlnis.util.DownloadUtil +import com.deniscerri.ytdlnis.util.Extensions.closestValue +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.time.Month +import java.util.Calendar + +class ObserveSourcesViewModel(private val application: Application) : AndroidViewModel(application) { + private val repository: ObserveSourcesRepository + val items: LiveData> + private val alarmManager : AlarmManager + private val preferences : SharedPreferences + + init { + val dao = DBManager.getInstance(application).observeSourcesDao + repository = ObserveSourcesRepository(dao) + items = repository.items.asLiveData() + alarmManager = application.getSystemService(AlarmManager::class.java) + preferences = PreferenceManager.getDefaultSharedPreferences(application) + } + + fun getAll(): List { + return repository.getAll() + } + + fun getByURL(url: String) : ObserveSourcesItem { + return repository.getByURL(url) + } + + fun getByID(id: Long) : ObserveSourcesItem { + return repository.getByID(id) + } + + suspend fun insert(item: ObserveSourcesItem) : Long { + if (item.id > 0) { + repository.update(item) + observeTask(item) + return item.id + } + + val id = repository.insert(item) + item.id = id + if (id > 0) observeTask(item) + return id + } + + fun delete(item: ObserveSourcesItem) = viewModelScope.launch(Dispatchers.IO) { + repository.delete(item) + } + + fun deleteAll() = viewModelScope.launch(Dispatchers.IO) { + repository.deleteAll() + } + + suspend fun update(item: ObserveSourcesItem) { + repository.update(item) + } + + + private fun observeTask(it: ObserveSourcesItem){ + val id = it.id + val c = Calendar.getInstance() + val date = Calendar.getInstance() + date.timeInMillis = it.startsTime + val hourMin = Calendar.getInstance() + hourMin.timeInMillis = it.everyTime + c.set(Calendar.DAY_OF_MONTH, date.get(Calendar.DAY_OF_MONTH)) + c.set(Calendar.MONTH, date.get(Calendar.MONTH)) + c.set(Calendar.YEAR, date.get(Calendar.YEAR)) + c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY)) + c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE)) + + + DownloadUtil.cancelObservationTaskByID(application, id) + + val intent = Intent(application, ObserveAlarmReceiver::class.java) + intent.putExtra("id", id) + if (it.everyNr == 0) it.everyNr = 1 + + when(it.everyCategory){ + ObserveSourcesRepository.EveryCategory.DAY -> { + alarmManager.setExact( + AlarmManager.RTC, + c.timeInMillis, + PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + ) + } + ObserveSourcesRepository.EveryCategory.WEEK -> { + if (it.everyWeekDay.isNotEmpty()){ + val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString() + val followingWeekDay = (it.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: it.everyWeekDay.minBy { it.toInt() }).toInt() + c.set(Calendar.DAY_OF_WEEK, followingWeekDay) + if(c.timeInMillis < System.currentTimeMillis()){ + c.add(Calendar.DAY_OF_MONTH, 7) + } + } + + alarmManager.setExact( + AlarmManager.RTC, + c.timeInMillis, + PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + ) + } + ObserveSourcesRepository.EveryCategory.MONTH -> { + val theMonthIndex = Month.values().indexOf(it.startsMonth) + val currentMonthIndex = c.get(Calendar.MONTH) + if (theMonthIndex != currentMonthIndex){ + c.set(Calendar.MONTH, theMonthIndex) + if (c.timeInMillis < System.currentTimeMillis()){ + c.add(Calendar.YEAR, 1) + } + } + alarmManager.setExact( + AlarmManager.RTC, + c.timeInMillis, + PendingIntent.getBroadcast(application, it.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + ) + } + } + + } + + +} \ 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 ef11f50d..c24f2dcf 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 @@ -105,7 +105,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a else -> res.addAll(repository.getDefault(inputQuery, resetResults)) } } catch (e: Exception) { - if (e !is kotlinx.coroutines.CancellationException){ + if (updateResultDataJob?.isCancelled == false){ uiState.update {it.copy( processing = false, errorMessage = Pair(R.string.no_results, e.message.toString()), diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt new file mode 100644 index 00000000..84ef1d1c --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ObserveAlarmReceiver.kt @@ -0,0 +1,38 @@ +package com.deniscerri.ytdlnis.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import com.deniscerri.ytdlnis.work.DownloadWorker +import com.deniscerri.ytdlnis.work.ObserveSourceWorker +import java.util.concurrent.TimeUnit + +class ObserveAlarmReceiver : BroadcastReceiver() { + override fun onReceive(p0: Context?, p1: Intent?) { + p1?.apply { + val sourceID = p1.getLongExtra("id", 0) + if (sourceID == 0L) return + + val workConstraints = Constraints.Builder() + val workRequest = OneTimeWorkRequestBuilder() + .addTag("observeSources") + .addTag(sourceID.toString()) + .setConstraints(workConstraints.build()) + .setInitialDelay(1000L, TimeUnit.MILLISECONDS) + .setInputData(Data.Builder().putLong("id", sourceID).build()) + + WorkManager.getInstance(p0!!).enqueueUniqueWork( + sourceID.toString(), + ExistingWorkPolicy.REPLACE, + workRequest.build() + ) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt index 18436b84..f233735d 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt @@ -1,6 +1,7 @@ package com.deniscerri.ytdlnis.receiver import android.Manifest +import android.annotation.SuppressLint import android.content.Context import android.content.DialogInterface import android.content.Intent @@ -97,6 +98,7 @@ class ShareActivity : BaseActivity() { val navHostFragment = supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment navController = navHostFragment.findNavController() navController.addOnDestinationChangedListener(object: NavController.OnDestinationChangedListener{ + @SuppressLint("RestrictedApi") override fun onDestinationChanged( controller: NavController, destination: NavDestination, diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStartReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt similarity index 92% rename from app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStartReceiver.kt rename to app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt index 4ba50807..612e7480 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStartReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStartReceiver.kt @@ -1,4 +1,4 @@ -package com.deniscerri.ytdlnis.receiver +package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers import android.content.BroadcastReceiver import android.content.Context @@ -13,7 +13,6 @@ import java.util.concurrent.TimeUnit class AlarmStartReceiver : BroadcastReceiver() { override fun onReceive(p0: Context?, p1: Intent?) { - Log.e("assa", "Start") val workConstraints = Constraints.Builder() val workRequest = OneTimeWorkRequestBuilder() .addTag("download") diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStopReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStopReceiver.kt similarity index 96% rename from app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStopReceiver.kt rename to app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStopReceiver.kt index b8f8d7f7..11f77246 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/AlarmStopReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/downloadAlarmReceivers/AlarmStopReceiver.kt @@ -1,4 +1,4 @@ -package com.deniscerri.ytdlnis.receiver +package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers import android.content.BroadcastReceiver import android.content.Context 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 ec4a4669..e39e6f96 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -1,5 +1,6 @@ package com.deniscerri.ytdlnis.ui +import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.Activity import android.content.* @@ -14,6 +15,7 @@ import android.util.Log import android.util.Patterns import android.view.* import android.view.View.* +import android.view.animation.Interpolator import android.widget.* import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity @@ -35,14 +37,15 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.R -import com.deniscerri.ytdlnis.database.models.DownloadItem 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.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 import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.ThemeUtil @@ -56,11 +59,11 @@ import com.google.android.material.chip.ChipGroup import com.google.android.material.color.MaterialColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton -import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.search.SearchBar import com.google.android.material.search.SearchView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -76,13 +79,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti private var downloadSelectedFab: ExtendedFloatingActionButton? = null private var downloadAllFab: ExtendedFloatingActionButton? = null - private var clipboardFab: FloatingActionButton? = null + private var clipboardFab: ExtendedFloatingActionButton? = null private var homeFabs: CoordinatorLayout? = null private var infoUtil: InfoUtil? = null private var downloadQueue: ArrayList? = null private lateinit var resultViewModel : ResultViewModel private lateinit var downloadViewModel : DownloadViewModel + private lateinit var historyViewModel : HistoryViewModel private var fragmentView: View? = null private var activity: Activity? = null @@ -126,7 +130,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti uiHandler = Handler(Looper.getMainLooper()) selectedObjects = ArrayList() - downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] + downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] + historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java] downloadQueue = ArrayList() resultsList = mutableListOf() @@ -167,7 +172,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti searchSuggestionsRecyclerView = view.findViewById(R.id.search_suggestions_recycler) searchSuggestionsRecyclerView?.layoutManager = LinearLayoutManager(context) searchSuggestionsRecyclerView?.adapter = searchSuggestionsAdapter - searchSuggestionsRecyclerView?.enableFastScroll() + searchSuggestionsRecyclerView?.itemAnimator = null resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] @@ -263,6 +268,30 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti } } + lifecycleScope.launch { + launch{ + downloadViewModel.alreadyExistsUiState.collectLatest { res -> + if (res.downloadItems.isNotEmpty() || res.historyItems.isNotEmpty()) { + withContext(Dispatchers.Main){ + kotlin.runCatching { + UiUtil.handleExistingDownloadsResponse( + requireActivity(), + requireActivity().lifecycleScope, + requireActivity().supportFragmentManager, + res, + downloadViewModel, + historyViewModel) + } + } + downloadViewModel.alreadyExistsUiState.value = DownloadViewModel.AlreadyExistsUIState( + mutableListOf(), + mutableListOf() + ) + } + } + } + } + } override fun onResume() { @@ -278,15 +307,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){ arguments?.remove("showDownloadsWithUpdatedFormats") CoroutineScope(Dispatchers.IO).launch { - val ids = arguments?.getLongArray("downloadIds") - val items = mutableListOf() - ids?.forEach { - items.add(downloadViewModel.getItemByID(it)) - downloadViewModel.deleteDownload(it) - } + val ids = arguments?.getLongArray("downloadIds") ?: return@launch + val type = downloadViewModel.updateItemsWithIdsToProcessingStatus(ids.toList()) withContext(Dispatchers.Main){ findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf( - Pair("downloads", items) + Pair("type", type) )) } } @@ -304,6 +329,12 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti clipboardFab?.isVisible = false initSearch(searchView!!) } + + lifecycleScope.launch { + clipboardFab?.extend() + delay(1000) + clipboardFab?.shrink() + } } } } @@ -679,11 +710,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti downloadViewModel.turnResultItemsToDownloadItems(resultsList!!) } if (sharedPreferences!!.getBoolean("download_card", true)) { - findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf( - Pair("downloads", downloadList) - )) + CoroutineScope(Dispatchers.IO).launch { + downloadViewModel.insertToProcessing(downloadList) + } + + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(Pair("type", downloadList[0].type))) } else { - downloadViewModel.queueDownloads(downloadList) + downloadList.chunked(100).forEach { + downloadViewModel.queueDownloads(it) + } } } } @@ -741,8 +776,12 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti } if (sharedPreferences!!.getBoolean("download_card", true)) { + CoroutineScope(Dispatchers.IO).launch { + downloadViewModel.insertToProcessing(downloadList) + } + findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf( - Pair("downloads", downloadList) + Pair("type", downloadList[0].type) )) } else { downloadViewModel.queueDownloads(downloadList) @@ -827,8 +866,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti chip.text = text chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK)) chip.setOnClickListener { + if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE queriesChipGroup!!.removeView(chip) - if (queriesChipGroup!!.childCount == 0) queriesChipGroup!!.visibility = View.GONE } queriesChipGroup!!.addView(chip) } @@ -849,7 +888,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() } deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> resultViewModel.removeSearchQueryFromHistory(text) - searchSuggestionsAdapter?.notifyItemRemoved(position) + updateSearchViewItems(searchView!!.editText.text) } deleteDialog.show() } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt index f3260fd3..8b7ed9c4 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadAdapter.kt @@ -22,7 +22,9 @@ import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.util.Extensions import com.deniscerri.ytdlnis.util.Extensions.dp +import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView @@ -61,6 +63,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView + card.popup() card.tag = "${item!!.id}##card" val uiHandler = Handler(Looper.getMainLooper()) val thumbnail = card.findViewById(R.id.image_view) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt index 95756b2b..98a43063 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/ActiveDownloadMinifiedAdapter.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository +import com.deniscerri.ytdlnis.util.Extensions.popup import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView @@ -56,6 +57,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView + card.popup() + val uiHandler = Handler(Looper.getMainLooper()) val thumbnail = card.findViewById(R.id.image_view) 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 new file mode 100644 index 00000000..684929c6 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/adapter/AlreadyExistsAdapter.kt @@ -0,0 +1,107 @@ +package com.deniscerri.ytdlnis.ui.adapter + +import android.app.Activity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +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.database.models.HistoryItem +import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel +import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog +import com.deniscerri.ytdlnis.util.Extensions.popup +import com.deniscerri.ytdlnis.util.UiUtil +import com.google.android.material.floatingactionbutton.FloatingActionButton +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter, AlreadyExistsAdapter.ViewHolder>(AsyncDifferConfig.Builder( + DIFF_CALLBACK +).build()) { + private val onItemClickListener: OnItemClickListener + private val activity: Activity + + init { + this.onItemClickListener = onItemClickListener + this.activity = activity + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val cardView: ConstraintLayout + + init { + cardView = itemView as ConstraintLayout + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val cardView = LayoutInflater.from(parent.context) + .inflate(R.layout.already_exists_item, parent, false) + return ViewHolder(cardView) + } + + 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 editBtn = alreadyExistsItem.findViewById