major stuff pre 1.6.2
This commit is contained in:
parent
e2ad3569f1
commit
8d9d06ab13
105 changed files with 4285 additions and 863 deletions
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -202,8 +202,9 @@
|
|||
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.CancelWorkReceiver" />
|
||||
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
||||
<receiver android:name=".receiver.AlarmStartReceiver" />
|
||||
<receiver android:name=".receiver.AlarmStopReceiver" />
|
||||
<receiver android:name=".receiver.downloadAlarmReceivers.AlarmStartReceiver" />
|
||||
<receiver android:name=".receiver.downloadAlarmReceivers.AlarmStopReceiver" />
|
||||
<receiver android:name=".receiver.ObserveAlarmReceiver" />
|
||||
|
||||
<activity
|
||||
android:name=".receiver.ResumeActivity"
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ class MainActivity : BaseActivity() {
|
|||
if (navigationView is NavigationView){
|
||||
(navigationView as NavigationView).setupWithNavController(navController)
|
||||
//terminate button
|
||||
(navigationView as NavigationView).menu.getItem(7).setOnMenuItemClickListener {
|
||||
(navigationView as NavigationView).menu.getItem(8).setOnMenuItemClickListener {
|
||||
if (preferences.getBoolean("ask_terminate_app", true)){
|
||||
var doNotShowAgain = false
|
||||
val terminateDialog = MaterialAlertDialogBuilder(this)
|
||||
|
|
@ -204,7 +204,7 @@ class MainActivity : BaseActivity() {
|
|||
true
|
||||
}
|
||||
//settings button
|
||||
(navigationView as NavigationView).menu.getItem(8).setOnMenuItemClickListener {
|
||||
(navigationView as NavigationView).menu.getItem(9).setOnMenuItemClickListener {
|
||||
val intent = Intent(context, SettingsActivity::class.java)
|
||||
startActivity(intent)
|
||||
true
|
||||
|
|
@ -244,7 +244,7 @@ class MainActivity : BaseActivity() {
|
|||
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
|
||||
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
|
||||
val version = YoutubeDL.getInstance().version(context)
|
||||
Snackbar.make(findViewById(android.R.id.content),
|
||||
Snackbar.make(findViewById(R.id.frame_layout),
|
||||
this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]",
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
|
@ -254,6 +254,8 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||
super.onSaveInstanceState(savedInstanceState)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database
|
|||
import androidx.room.TypeConverter
|
||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
|
|
@ -57,4 +58,19 @@ class Converters {
|
|||
val gson = Gson()
|
||||
return gson.toJson(list)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun downloadItemToString(downloadItem: DownloadItem) : String {
|
||||
val gson = Gson()
|
||||
return gson.toJson(downloadItem)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun stringToDownloadItem(s: String) = Gson().fromJson(s, DownloadItem::class.java)
|
||||
|
||||
@TypeConverter
|
||||
fun mutableListOfStringsToString(s: MutableList<String>) = Gson().toJson(s)
|
||||
|
||||
@TypeConverter
|
||||
fun stringtoMutableListofStrings(s: String) = Gson().fromJson(s, Array<String>::class.java).toMutableList()
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ interface CookieDao {
|
|||
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
||||
fun getAllCookies() : List<CookieItem>
|
||||
|
||||
@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<List<CookieItem>>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
||||
fun getActiveDownloadsCountFlow() : Flow<Int>
|
||||
|
||||
|
|
@ -25,9 +28,29 @@ interface DownloadDao {
|
|||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||
fun getDownloadsCountByStatus(status : List<String>) : 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<Int>
|
||||
|
||||
|
||||
@Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)")
|
||||
suspend fun updateItemsToProcessing(ids: List<Long>)
|
||||
|
||||
|
||||
@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<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
fun getProcessingDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
||||
|
|
@ -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<Long>)
|
||||
|
||||
|
|
@ -174,12 +200,18 @@ interface DownloadDao {
|
|||
@Query("Select id from downloads where id not in (:list) and status in (:status)")
|
||||
fun getDownloadIDsNotPresentInList(list: List<Long>, status: List<String>) : List<Long>
|
||||
|
||||
@Query("Select url from downloads where status in (:status)")
|
||||
fun getURLsByStatus(status: List<String>) : List<String>
|
||||
|
||||
@Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
|
||||
fun resetScheduleTimeForItems(list: List<Long>)
|
||||
|
||||
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
||||
fun reQueueDownloadItems(list: List<Long>)
|
||||
|
||||
@Query("Update downloads SET status='Saved' WHERE status='Processing'")
|
||||
fun updateProcessingtoSavedStatus()
|
||||
|
||||
@Transaction
|
||||
fun putAtTopOfTheQueue(id: Long){
|
||||
val downloads = getQueuedDownloadsList()
|
||||
|
|
|
|||
|
|
@ -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<ObserveSourcesItem>
|
||||
|
||||
@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<List<ObserveSourcesItem>>
|
||||
|
||||
@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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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<String>,
|
||||
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<String>
|
||||
) : Parcelable
|
||||
|
|
@ -10,6 +10,7 @@ data class VideoPreferences (
|
|||
var splitByChapters: Boolean = false,
|
||||
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
||||
var writeSubs: Boolean = false,
|
||||
var writeAutoSubs: Boolean = false,
|
||||
var subsLanguages: String = "en.*,.*-orig",
|
||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||
var removeAudio: Boolean = false
|
||||
|
|
|
|||
|
|
@ -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)){
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
pagingSourceFactory = {downloadDao.getAllDownloads()}
|
||||
)
|
||||
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads().distinctUntilChanged()
|
||||
val processingDownloads : Flow<List<DownloadItem>> = downloadDao.getProcessingDownloads().distinctUntilChanged()
|
||||
val queuedDownloads : Pager<Int, DownloadItemSimple> = 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<Int> = 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<DownloadItem>) {
|
||||
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<DownloadItem> {
|
||||
return downloadDao.getProcessingDownloadsList()
|
||||
}
|
||||
|
||||
fun getActiveAndQueuedDownloads() : List<DownloadItem> {
|
||||
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<Long>){
|
||||
downloadDao.deleteAllWithIDs(ids)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<HistoryItem> {
|
||||
return when(sortType){
|
||||
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) : List<HistoryItem> {
|
||||
var filtered = when(sortType){
|
||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
||||
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(query, format, site, sort.toString())
|
||||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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<List<ObserveSourcesItem>> = 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<ObserveSourcesItem> {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -65,6 +65,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
val items : ArrayList<ResultItem?> = 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PagingData<DownloadItem>>
|
||||
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val activeDownloads : Flow<List<DownloadItem>>
|
||||
val processingDownloads : Flow<List<DownloadItem>>
|
||||
val activeDownloadsCount : Flow<Int>
|
||||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
|
|
@ -89,19 +95,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
private val dao: DownloadDao
|
||||
private val historyRepository: HistoryRepository
|
||||
|
||||
data class DownloadsUiState(
|
||||
var errorMessage: Pair<Int, Int>?,
|
||||
//action title, type, data
|
||||
var actions: MutableList<Triple<Int, DownloadsAction, List<DownloadItem>?>>?
|
||||
data class AlreadyExistsUIState(
|
||||
var historyItems: MutableList<Long>,
|
||||
var downloadItems : MutableList<Long>
|
||||
)
|
||||
|
||||
enum class DownloadsAction {
|
||||
DOWNLOAD_ANYWAY
|
||||
}
|
||||
|
||||
val uiState: MutableStateFlow<DownloadsUiState> = MutableStateFlow(DownloadsUiState(
|
||||
errorMessage = null,
|
||||
actions = null
|
||||
val alreadyExistsUiState: MutableStateFlow<AlreadyExistsUIState> = 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<DownloadItem>)= 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<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||
repository.deleteAllWithIDs(ids)
|
||||
}
|
||||
|
|
@ -646,27 +663,31 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
|
||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = 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<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
dbManager.downloadDao.reQueueDownloadItems(items)
|
||||
startDownloadWorker(emptyList())
|
||||
DownloadUtil.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun queueDownloads(items: List<DownloadItem>, ignoreDuplicate : Boolean = false) = CoroutineScope(Dispatchers.IO).launch {
|
||||
suspend fun queueDownloads(items: List<DownloadItem>, ign : Boolean = false) : Pair<List<Long>, List<Long>> {
|
||||
val context = App.instance
|
||||
val alarmScheduler = AlarmScheduler(context)
|
||||
val activeAndQueuedDownloads = withContext(Dispatchers.IO){
|
||||
repository.getActiveAndQueuedDownloads()
|
||||
}
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
val existing = mutableListOf<DownloadItem>()
|
||||
val existingDownloads = mutableListOf<Long>()
|
||||
val existingHistory = mutableListOf<Long>()
|
||||
|
||||
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<DownloadItem>) {
|
||||
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<DownloadWorker>()
|
||||
.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<FormatTuple>): List<Long> {
|
||||
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<DownloadItem> {
|
||||
return repository.getProcessingDownloads()
|
||||
}
|
||||
|
||||
|
||||
suspend fun updateProcessingAllFormats(formatCollection: List<List<Format>>) {
|
||||
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<UpdatePlaylistFormatsWorker>()
|
||||
.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<Boolean, Type> {
|
||||
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<Long>) : Type {
|
||||
repository.deleteProcessing()
|
||||
dao.updateItemsToProcessing(ids)
|
||||
val first = dao.getFirstProcessingDownload()
|
||||
return first.type
|
||||
}
|
||||
|
||||
fun getURLsByStatus(list: List<DownloadRepository.Status>) : List<String> {
|
||||
return dao.getURLsByStatus(list.map { it.toString() })
|
||||
}
|
||||
|
||||
private fun updateDownloadItem(
|
||||
downloadItem: DownloadItem,
|
||||
infoUtil: InfoUtil,
|
||||
|
|
|
|||
|
|
@ -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<List<HistoryItem>>
|
||||
private var _items = MediatorLiveData<List<HistoryItem>>()
|
||||
|
|
@ -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<HistoryItem> {
|
||||
|
|
|
|||
|
|
@ -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<List<ObserveSourcesItem>>
|
||||
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<ObserveSourcesItem> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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<ObserveSourceWorker>()
|
||||
.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()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<DownloadWorker>()
|
||||
.addTag("download")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.deniscerri.ytdlnis.receiver
|
||||
package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
|
|
@ -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<ResultItem>? = 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<DownloadItem>()
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ImageView>(R.id.image_view)
|
||||
|
|
|
|||
|
|
@ -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<ImageView>(R.id.image_view)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Pair<DownloadItem, Long?>, 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<Button>(R.id.already_exists_edit)
|
||||
val deleteBtn = alreadyExistsItem.findViewById<Button>(R.id.already_exists_delete)
|
||||
|
||||
val ttle = alreadyExistsItem.findViewById<Button>(R.id.already_exists_title)
|
||||
ttle.text = item.first.title
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
}
|
||||
|
||||
ttle.setOnLongClickListener {
|
||||
onItemClickListener.onDeleteItem(item.first, position, item.second)
|
||||
true
|
||||
}
|
||||
|
||||
editBtn.setOnClickListener {
|
||||
onItemClickListener.onEditItem(item.first, position)
|
||||
}
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
onItemClickListener.onDeleteItem(item.first, position, item.second)
|
||||
}
|
||||
|
||||
ttle.setOnClickListener(null)
|
||||
if (item.second != null){
|
||||
ttle.setOnClickListener {
|
||||
onItemClickListener.onShowHistoryItem(item.second!!)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onEditItem(downloadItem: DownloadItem, position: Int)
|
||||
fun onDeleteItem(downloadItem: DownloadItem, position: Int, historyID: Long?)
|
||||
fun onShowHistoryItem(id: Long)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK: DiffUtil.ItemCallback<Pair<DownloadItem, Long?>> = object : DiffUtil.ItemCallback<Pair<DownloadItem, Long?>>() {
|
||||
override fun areItemsTheSame(oldItem: Pair<DownloadItem, Long?>, newItem: Pair<DownloadItem, Long?>): Boolean {
|
||||
return oldItem.first.id == newItem.first.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Pair<DownloadItem, Long?>, newItem: Pair<DownloadItem, Long?>): Boolean {
|
||||
return oldItem.first.id == newItem.first.id && oldItem.first.title == newItem.first.title && oldItem.first.author == newItem.first.author && oldItem.first.thumb == newItem.first.thumb
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.squareup.picasso.Picasso
|
||||
|
|
@ -54,13 +55,16 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
card.popup()
|
||||
if (item == null) return
|
||||
card.tag = item.id.toString()
|
||||
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
// THUMBNAIL ----------------------------------
|
||||
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||
val imageURL = item?.thumb
|
||||
val imageURL = item.thumb
|
||||
if (!imageURL.isNullOrBlank()) {
|
||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||
} else {
|
||||
|
|
@ -72,7 +76,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
}
|
||||
|
||||
val duration = card.findViewById<TextView>(R.id.duration)
|
||||
duration.text = item!!.duration
|
||||
duration.text = item.duration
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
|
|
@ -120,7 +124,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
|
||||
|
||||
btn.setOnClickListener {
|
||||
onItemClickListener.onButtonClick(item.url)
|
||||
onItemClickListener.onButtonClick(item.id)
|
||||
}
|
||||
|
||||
when(item.type) {
|
||||
|
|
@ -136,18 +140,18 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
}
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onCardClick(item.url)
|
||||
onItemClickListener.onCardClick(item.id)
|
||||
}
|
||||
|
||||
card.setOnLongClickListener {
|
||||
onItemClickListener.onDelete(item.url); true
|
||||
onItemClickListener.onDelete(item.id); true
|
||||
}
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onButtonClick(itemURL: String)
|
||||
fun onCardClick(itemURL: String)
|
||||
fun onDelete(itemURL: String)
|
||||
fun onButtonClick(id: Long)
|
||||
fun onCardClick(id: Long)
|
||||
fun onDelete(id: Long)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
@ -157,7 +161,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
|
||||
return oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.type == newItem.type && oldItem.format == newItem.format
|
||||
return oldItem.title == newItem.title &&
|
||||
oldItem.author == newItem.author &&
|
||||
oldItem.type == newItem.type &&
|
||||
oldItem.format == newItem.format
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CookieItem?, CookieAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
|
|
@ -41,6 +42,7 @@ class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.item
|
||||
card.popup()
|
||||
|
||||
val title = card.findViewById<TextView>(R.id.title)
|
||||
title.text = item?.url
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
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
|
||||
|
|
@ -50,6 +51,7 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.item
|
||||
card.popup()
|
||||
|
||||
val title = card.findViewById<TextView>(R.id.title)
|
||||
title.text = item?.title
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
||||
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
|
||||
|
|
@ -55,6 +56,8 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
card.popup()
|
||||
|
||||
if (item == null) return
|
||||
card.tag = item.id.toString()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ import android.os.Looper
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
|
|
@ -24,9 +22,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
|
|
@ -86,6 +82,8 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
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<ImageView>(R.id.downloads_image_view)
|
||||
|
|
@ -116,7 +114,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
author.text = item.author
|
||||
|
||||
val length = card.findViewById<TextView>(R.id.length)
|
||||
length.text = item.duration
|
||||
length.text = if(item.downloadPath.size == 1) item.duration else "[${item.downloadPath.size}]"
|
||||
|
||||
|
||||
// TIME DOWNLOADED ----------------------------------
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.adapter
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Color
|
||||
|
|
@ -18,11 +19,14 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.Extensions
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.squareup.picasso.Picasso
|
||||
|
||||
|
||||
class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, HomeAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
DIFF_CALLBACK
|
||||
).build()) {
|
||||
|
|
@ -55,6 +59,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val video = getItem(position)
|
||||
val card = holder.cardView
|
||||
card.popup()
|
||||
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
package com.deniscerri.ytdlnis.ui.adapter
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.util.TypedValue
|
||||
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.core.view.isVisible
|
||||
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.ObserveSourcesItem
|
||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.text.DateFormatSymbols
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ObserveSourcesItem?, ObserveSourcesAdapter.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: MaterialCardView
|
||||
|
||||
init {
|
||||
cardView = itemView.findViewById(R.id.observe_sources_card)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val cardView = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.observe_sources_item, parent, false)
|
||||
return ViewHolder(cardView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
card.popup()
|
||||
|
||||
if (item == null) return
|
||||
card.tag = item.url
|
||||
holder.itemView.tag = item.url
|
||||
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
|
||||
|
||||
// TITLE ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
var title = item.name
|
||||
if (title.length > 100) {
|
||||
title = title.substring(0, 40) + "..."
|
||||
}
|
||||
itemTitle.text = title
|
||||
|
||||
//URL
|
||||
val url = card.findViewById<TextView>(R.id.url)
|
||||
url.text = item.url
|
||||
|
||||
//INFO
|
||||
val info = card.findViewById<Chip>(R.id.info)
|
||||
|
||||
val c = Calendar.getInstance()
|
||||
val hourMin = Calendar.getInstance()
|
||||
hourMin.timeInMillis = item.everyTime
|
||||
c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY))
|
||||
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||
|
||||
when(item.everyCategory){
|
||||
ObserveSourcesRepository.EveryCategory.DAY -> {
|
||||
c.add(Calendar.DAY_OF_MONTH, item.everyNr)
|
||||
}
|
||||
ObserveSourcesRepository.EveryCategory.WEEK -> {
|
||||
if(item.everyWeekDay.isEmpty()){
|
||||
c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}else{
|
||||
val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString()
|
||||
val followingWeekDay = (item.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: item.everyWeekDay.minBy { it.toInt() }).toInt()
|
||||
c.set(Calendar.DAY_OF_WEEK, followingWeekDay)
|
||||
if (item.everyNr > 1){
|
||||
c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}
|
||||
}
|
||||
}
|
||||
ObserveSourcesRepository.EveryCategory.MONTH -> {
|
||||
c.add(Calendar.MONTH, item.everyNr)
|
||||
c.set(Calendar.DAY_OF_MONTH, item.everyMonthDay)
|
||||
}
|
||||
}
|
||||
val weekdays = DateFormatSymbols(Locale.getDefault()).shortWeekdays
|
||||
val text = "${weekdays[c.get(Calendar.DAY_OF_WEEK)]}, ${SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(c.timeInMillis)}"
|
||||
info.text = text
|
||||
|
||||
//CHECK MISSING
|
||||
val checkMissing = card.findViewById<Button>(R.id.check_missing)
|
||||
checkMissing.isVisible = item.retryMissingDownloads
|
||||
|
||||
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.download_progress)
|
||||
progressBar.isIndeterminate = true
|
||||
progressBar.isVisible = false
|
||||
|
||||
// BUTTON ----------------------------------
|
||||
val btn = card.findViewById<FloatingActionButton>(R.id.search)
|
||||
btn.isEnabled = true
|
||||
if (item.status == ObserveSourcesRepository.SourceStatus.STOPPED){
|
||||
info.isVisible = false
|
||||
checkMissing.isVisible = false
|
||||
|
||||
btn.setImageResource(R.drawable.exomedia_ic_play_arrow_white)
|
||||
btn.setOnClickListener {
|
||||
btn.isEnabled = false
|
||||
progressBar.isVisible = true
|
||||
progressBar.animate()
|
||||
onItemClickListener.onItemStart(item, position)
|
||||
}
|
||||
}else{
|
||||
info.isVisible = true
|
||||
checkMissing.isVisible = true
|
||||
|
||||
btn.setImageResource(R.drawable.ic_search)
|
||||
btn.setOnClickListener {
|
||||
btn.isEnabled = false
|
||||
progressBar.isVisible = true
|
||||
progressBar.animate()
|
||||
onItemClickListener.onItemSearch(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onItemClick(item)
|
||||
}
|
||||
|
||||
card.setOnLongClickListener {
|
||||
onItemClickListener.onDelete(item); true
|
||||
}
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
|
||||
fun onItemSearch(item: ObserveSourcesItem)
|
||||
fun onItemStart(item: ObserveSourcesItem, position: Int)
|
||||
fun onItemClick(item: ObserveSourcesItem)
|
||||
fun onDelete(item: ObserveSourcesItem)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIFF_CALLBACK: DiffUtil.ItemCallback<ObserveSourcesItem> = object : DiffUtil.ItemCallback<ObserveSourcesItem>() {
|
||||
override fun areItemsTheSame(oldItem: ObserveSourcesItem, newItem: ObserveSourcesItem): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ObserveSourcesItem, newItem: ObserveSourcesItem): Boolean {
|
||||
return oldItem.id == newItem.id &&
|
||||
oldItem.name == newItem.name &&
|
||||
oldItem.downloadItemTemplate == newItem.downloadItemTemplate &&
|
||||
oldItem.status == newItem.status &&
|
||||
oldItem.retryMissingDownloads == newItem.retryMissingDownloads &&
|
||||
oldItem.runCount == newItem.runCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -56,6 +57,7 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
|||
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<ImageView>(R.id.downloads_image_view)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class SearchSuggestionsAdapter(onItemClickListener: OnItemClickListener, activit
|
|||
}
|
||||
textView.setOnLongClickListener { true }
|
||||
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
|
||||
mb.setImageResource(R.drawable.ic_arrow_outward)
|
||||
mb.setOnClickListener {
|
||||
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
|
||||
}
|
||||
|
|
@ -79,6 +80,7 @@ class SearchSuggestionsAdapter(onItemClickListener: OnItemClickListener, activit
|
|||
}
|
||||
|
||||
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
|
||||
mb.setImageResource(R.drawable.ic_arrow_outward)
|
||||
mb.setOnClickListener {
|
||||
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CommandTemplate?, TemplatesAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
|
|
@ -46,6 +47,7 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.item
|
||||
card.popup()
|
||||
|
||||
val title = card.findViewById<TextView>(R.id.title)
|
||||
title.text = item?.title
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
|
|
@ -47,6 +48,8 @@ class TerminalDownloadsAdapter(onItemClickListener: OnItemClickListener, activit
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
card.popup()
|
||||
|
||||
card.tag = "${item!!.id}##card"
|
||||
|
||||
// PROGRESS BAR ----------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -22,12 +23,14 @@ import com.deniscerri.ytdlnis.database.DBManager
|
|||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -40,7 +43,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
private lateinit var fragmentAdapter : DownloadFragmentAdapter
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var commandTemplateDao: CommandTemplateDao
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
||||
private lateinit var sharedPreferences : SharedPreferences
|
||||
|
|
@ -49,7 +52,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
super.onCreate(savedInstanceState)
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
||||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||
onDownloadItemUpdateListener = listener
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
}
|
||||
|
|
@ -84,7 +87,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
var commandTemplateNr = 0
|
||||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateNr = commandTemplateDao.getTotalNumber()
|
||||
commandTemplateNr = commandTemplateViewModel.getTotalNumber()
|
||||
if(commandTemplateNr <= 0){
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
|
||||
|
|
@ -137,7 +140,19 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
if (tab!!.position == 2 && commandTemplateNr == 0){
|
||||
tabLayout.selectTab(tabLayout.getTabAt(1))
|
||||
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
|
||||
val s = Snackbar.make(view, getString(R.string.add_template_first), Snackbar.LENGTH_LONG)
|
||||
val snackbarView: View = s.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
s.setAction(R.string.new_template){
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(item = null, context = requireActivity(), lifeCycle = this@ConfigureDownloadBottomSheetDialog, commandTemplateViewModel = commandTemplateViewModel){
|
||||
commandTemplateNr = 1
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 1f
|
||||
tabLayout.selectTab(tabLayout.getTabAt(2))
|
||||
}
|
||||
}
|
||||
s.show()
|
||||
}else if (tab.position == 1 && isAudioOnly){
|
||||
tabLayout.selectTab(tabLayout.getTabAt(0))
|
||||
Toast.makeText(context, getString(R.string.audio_only_item), Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -191,15 +206,15 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
return when(selectedTabPosition){
|
||||
0 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.downloadItem
|
||||
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||
}
|
||||
1 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||
f.downloadItem
|
||||
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||
}
|
||||
else -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
|
||||
f.downloadItem
|
||||
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -209,21 +224,27 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
val prevDownloadItem = getDownloadItem(
|
||||
if (viewPager2.currentItem == 1) 0 else 1
|
||||
)
|
||||
|
||||
result.title = prevDownloadItem.title
|
||||
result.author = prevDownloadItem.author
|
||||
currentDownloadItem.title = prevDownloadItem.title
|
||||
currentDownloadItem.author = prevDownloadItem.author
|
||||
fragmentAdapter.setTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
|
||||
when(viewPager2.currentItem){
|
||||
0 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
kotlin.runCatching {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
f.updateSelectedAudioFormat(getDownloadItem(0).format)
|
||||
kotlin.runCatching {
|
||||
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
f.updateSelectedAudioFormat(getDownloadItem(0).format)
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
kotlin.runCatching {
|
||||
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
|
|||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
|
@ -203,7 +204,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
//poll video progress
|
||||
lifecycleScope.launch {
|
||||
videoProgress(player).collect { p ->
|
||||
val currentTime = infoUtil.formatIntegerDuration(p, Locale.US)
|
||||
val currentTime = p.toStringDuration(Locale.US)
|
||||
durationText.text = "$currentTime / ${item.duration}"
|
||||
val startTimestamp = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
||||
if (toTextInput.editText!!.text.isNotBlank()){
|
||||
|
|
@ -310,11 +311,11 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
val endValue = (endSeconds.toFloat() / endTimestamp) * 100
|
||||
|
||||
if (seconds == 0) {
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
||||
}else if (startValue > 100){
|
||||
startTimestamp = 0
|
||||
seconds = 0
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
||||
startValue = 0F
|
||||
}
|
||||
|
||||
|
|
@ -350,14 +351,14 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
var endValue = (seconds.toFloat() / endTimestamp) * 100
|
||||
if (endValue > 100F){
|
||||
endTimestamp = timeSeconds
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||
endValue = 100F
|
||||
}
|
||||
|
||||
if (seconds == 0) {
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||
}else if (endValue <= rangeSlider.valueFrom.toInt()){
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||
}
|
||||
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(toTextInput.editText!!.text.toString())
|
||||
|
|
@ -402,8 +403,8 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
|||
val startTimestamp = (values[0].toInt() * timeSeconds) / 100
|
||||
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
|
||||
|
||||
val startTimestampString = infoUtil.formatIntegerDuration(startTimestamp, Locale.US)
|
||||
val endTimestampString = infoUtil.formatIntegerDuration(endTimestamp, Locale.US)
|
||||
val startTimestampString = startTimestamp.toStringDuration(Locale.US)
|
||||
val endTimestampString = endTimestamp.toStringDuration(Locale.US)
|
||||
|
||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestampString)
|
||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
|
|
@ -18,6 +19,7 @@ import android.widget.Toast
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -48,7 +50,7 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
|
||||
|
||||
class DownloadAudioFragment(private var resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "") : Fragment(), GUISync {
|
||||
class DownloadAudioFragment(private var resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "", private var nonSpecific: Boolean = false) : Fragment(), GUISync {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -63,6 +65,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
lateinit var author : TextInputLayout
|
||||
lateinit var preferences: SharedPreferences
|
||||
lateinit var shownFields: List<String>
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
@ -95,7 +98,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
|
||||
try {
|
||||
title = view.findViewById(R.id.title_textinput)
|
||||
title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
|
||||
title.visibility = if (shownFields.contains("title") && !nonSpecific) View.VISIBLE else View.GONE
|
||||
if (title.editText?.text?.isEmpty() == true){
|
||||
title.editText!!.setText(downloadItem.title)
|
||||
title.endIconMode = END_ICON_NONE
|
||||
|
|
@ -109,7 +112,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
})
|
||||
|
||||
author = view.findViewById(R.id.author_textinput)
|
||||
author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
|
||||
author.visibility = if (shownFields.contains("author") && !nonSpecific) View.VISIBLE else View.GONE
|
||||
if (author.editText?.text?.isEmpty() == true){
|
||||
author.editText!!.setText(downloadItem.author)
|
||||
author.endIconMode = END_ICON_NONE
|
||||
|
|
@ -185,13 +188,22 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
}
|
||||
}
|
||||
}
|
||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.vcodec.isBlank() || it.vcodec == "none"})
|
||||
|
||||
val containers = requireContext().resources.getStringArray(R.array.audio_containers)
|
||||
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
||||
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
|
||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||
container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
|
||||
if (nonSpecific){
|
||||
val param = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
1.0f
|
||||
)
|
||||
container.layoutParams = param
|
||||
container.setPadding(0)
|
||||
}
|
||||
val containerAutoCompleteTextView =
|
||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
||||
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.isClickable = false
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
|
||||
|
||||
updateItem.visibility = View.GONE
|
||||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
}
|
||||
|
||||
//remove outdated player url of 1hr so it can refetch it in the cut player
|
||||
|
|
@ -393,27 +393,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch{
|
||||
downloadViewModel.uiState.collectLatest { res ->
|
||||
if (res.errorMessage != null) {
|
||||
withContext(Dispatchers.Main){
|
||||
kotlin.runCatching {
|
||||
UiUtil.handleDownloadsResponse(
|
||||
requireActivity(),
|
||||
requireActivity().lifecycleScope,
|
||||
requireActivity().supportFragmentManager,
|
||||
res,
|
||||
downloadViewModel,
|
||||
historyViewModel)
|
||||
}
|
||||
}
|
||||
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
||||
errorMessage = null,
|
||||
actions = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
resultViewModel.uiState.collectLatest { res ->
|
||||
|
|
@ -436,7 +416,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
shimmerLoadingSubtitle.visibility = View.VISIBLE
|
||||
shimmerLoading.startShimmer()
|
||||
shimmerLoadingSubtitle.startShimmer()
|
||||
updateItem.visibility = View.GONE
|
||||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
}else{
|
||||
title.visibility = View.VISIBLE
|
||||
subtitle.visibility = View.VISIBLE
|
||||
|
|
@ -652,5 +632,13 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
lifecycleScope.launch {
|
||||
resultViewModel.cancelUpdateItemData()
|
||||
resultViewModel.cancelUpdateFormatsItemData()
|
||||
}
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,15 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO){
|
||||
if (currentDownloadItem != null && currentDownloadItem!!.type == DownloadViewModel.Type.command){
|
||||
if (currentDownloadItem != null){
|
||||
currentDownloadItem?.apply {
|
||||
if (type != DownloadViewModel.Type.command){
|
||||
type = DownloadViewModel.Type.command
|
||||
}
|
||||
|
||||
format = downloadViewModel.getFormat(allFormats, DownloadViewModel.Type.command)
|
||||
}
|
||||
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ class DownloadFragmentAdapter (
|
|||
fragmentManager : FragmentManager,
|
||||
lifecycle : Lifecycle,
|
||||
private var result: ResultItem,
|
||||
private var downloadItem: DownloadItem?
|
||||
private var downloadItem: DownloadItem?,
|
||||
private var nonSpecific: Boolean = false
|
||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
|
||||
fun setResultItem(res: ResultItem){
|
||||
|
|
@ -35,8 +36,8 @@ class DownloadFragmentAdapter (
|
|||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when(position){
|
||||
0 -> DownloadAudioFragment(result, downloadItem)
|
||||
1 -> DownloadVideoFragment(result, downloadItem)
|
||||
0 -> DownloadAudioFragment(result, downloadItem,"", nonSpecific)
|
||||
1 -> DownloadVideoFragment(result, downloadItem,"", nonSpecific)
|
||||
else -> DownloadCommandFragment(result, downloadItem)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import android.content.SharedPreferences
|
|||
import android.content.res.Configuration
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -34,23 +35,21 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.ConfigureMultipleDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
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.receiver.ShareActivity
|
||||
import com.deniscerri.ytdlnis.ui.adapter.ConfigureMultipleDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
|
@ -82,16 +81,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
private lateinit var bottomAppBar: BottomAppBar
|
||||
private lateinit var filesize : TextView
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
private lateinit var items: MutableList<DownloadItem>
|
||||
|
||||
private lateinit var type: DownloadViewModel.Type
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
}
|
||||
|
|
@ -102,20 +99,15 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33){
|
||||
arguments?.getParcelableArrayList("downloads", DownloadItem::class.java)
|
||||
}else{
|
||||
arguments?.getParcelableArrayList<DownloadItem>("downloads")
|
||||
}.apply {
|
||||
arguments?.getSerializable("type").apply {
|
||||
if (this == null){
|
||||
dismiss()
|
||||
return
|
||||
}else{
|
||||
items = this
|
||||
type = this as DownloadViewModel.Type
|
||||
}
|
||||
}
|
||||
|
||||
if (items.isEmpty()) dismiss()
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
|
|
@ -138,42 +130,52 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
recyclerView.adapter = listAdapter
|
||||
recyclerView.enableFastScroll()
|
||||
|
||||
|
||||
view.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
||||
setOnClickListener {
|
||||
recyclerView.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("multipledownloadcard")){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
|
||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
||||
filesize = view.findViewById<TextView>(R.id.filesize)
|
||||
filesize = view.findViewById(R.id.filesize)
|
||||
|
||||
|
||||
scheduleBtn.setOnClickListener{
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
||||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
items.forEach { item ->
|
||||
item.downloadStartTime = it.timeInMillis
|
||||
}
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(items)
|
||||
val first = items.first()
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.downloadProcessingDownloads(cal.timeInMillis)
|
||||
}
|
||||
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(cal.timeInMillis)
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
|
||||
dismiss()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
download!!.setOnClickListener {
|
||||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(items)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.downloadProcessingDownloads()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
download.setOnLongClickListener {
|
||||
|
|
@ -182,9 +184,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
lifecycleScope.launch{
|
||||
items.map { it.status = DownloadRepository.Status.Saved.toString() }
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insertAll(items)
|
||||
downloadViewModel.moveProcessingToSavedCategory()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
|
@ -195,7 +196,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
||||
when(items.first().type){
|
||||
when(type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
}
|
||||
|
|
@ -212,72 +213,28 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
val formatListener = object : OnFormatClickListener {
|
||||
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
||||
items.forEachIndexed { index, i ->
|
||||
i.format = selectedFormats[index].format
|
||||
if (i.type == DownloadViewModel.Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingFormat(selectedFormats)
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
updateFileSize(items.map { it.format.filesize })
|
||||
}
|
||||
|
||||
|
||||
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
val formatCollection = mutableListOf<List<Format>>()
|
||||
allFormats.forEach {f ->
|
||||
formatCollection.add(f.mapTo(mutableListOf()) {it.copy()})
|
||||
}
|
||||
items.forEachIndexed { index, i ->
|
||||
i.allFormats.clear()
|
||||
if (formatCollection.size == items.size && formatCollection[index].isNotEmpty()) {
|
||||
runCatching {
|
||||
i.allFormats.addAll(formatCollection[index])
|
||||
}
|
||||
}
|
||||
i.format = downloadViewModel.getFormat(i.allFormats, i.type)
|
||||
kotlin.runCatching {
|
||||
val resultItem = resultViewModel.getItemByURL(i.url)!!
|
||||
resultItem.formats = formatCollection[index].toMutableList()
|
||||
resultViewModel.update(resultItem)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
updateFileSize(items.map { it.format.filesize })
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingAllFormats(allFormats)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onContinueOnBackground() {
|
||||
requireActivity().lifecycleScope.launch {
|
||||
val ids = mutableListOf<Long>()
|
||||
runBlocking {
|
||||
items.map { it.status = DownloadRepository.Status.Saved.toString() }
|
||||
ids.addAll(downloadViewModel.repository.insertAll(items))
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.continueUpdatingFormatsOnBackground()
|
||||
}
|
||||
val id = System.currentTimeMillis().toInt()
|
||||
val workRequest = OneTimeWorkRequestBuilder<UpdatePlaylistFormatsWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putLongArray("ids", ids.toLongArray())
|
||||
.putInt("id", id)
|
||||
.build())
|
||||
.addTag("updateFormats")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(requireContext()).enqueueUniqueWork(
|
||||
id.toString(),
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
workRequest
|
||||
)
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
if (items[0].type == DownloadViewModel.Type.command){
|
||||
if (type == DownloadViewModel.Type.command){
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}
|
||||
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
|
|
@ -311,39 +268,38 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
|
||||
audio!!.setOnClickListener {
|
||||
items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.audio).toMutableList()
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
updateFileSize(items.map { it.format.filesize })
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
video!!.setOnClickListener {
|
||||
items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.video).toMutableList()
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
updateFileSize(items.map { it.format.filesize })
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command!!.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.command).toMutableList()
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
|
||||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
updateFileSize(items.map { it.format.filesize })
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -359,11 +315,15 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
}
|
||||
R.id.format -> {
|
||||
if (! items.all { it.type == items[0].type }){
|
||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
if (items.first().type == DownloadViewModel.Type.command){
|
||||
lifecycleScope.launch {
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||
}
|
||||
if (!res.first){
|
||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
|
||||
if (res.second == DownloadViewModel.Type.command){
|
||||
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel) {
|
||||
val format = Format(
|
||||
it.first().title,
|
||||
|
|
@ -375,15 +335,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
it.joinToString(" ") { c -> c.content }
|
||||
)
|
||||
|
||||
items.forEach { f ->
|
||||
f.format = format
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingCommandFormat(format)
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
lifecycleScope.launch {
|
||||
}else{
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
}
|
||||
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
||||
val commonFormats = withContext(Dispatchers.IO){
|
||||
flatFormatCollection.groupingBy { it.format_id }.eachCount().filter { it.value == items.size }.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }.map { it.value }
|
||||
|
|
@ -402,182 +361,227 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
R.id.more -> {
|
||||
if (! items.all { it.type == items[0].type }) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getString(R.string.format_filtering_hint),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}else{
|
||||
val scale = resources.displayMetrics.density
|
||||
val padding = (40*scale*0.5f).toInt()
|
||||
lifecycleScope.launch {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||
}
|
||||
if (!res.first) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getString(R.string.format_filtering_hint),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}else{
|
||||
val scale = resources.displayMetrics.density
|
||||
val padding = (40*scale*0.5f).toInt()
|
||||
|
||||
when(items[0].type){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.adjust_audio)
|
||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
when(res.second){
|
||||
DownloadViewModel.Type.audio -> {
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.adjust_audio)
|
||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
UiUtil.configureAudio(
|
||||
sheetView,
|
||||
requireActivity(),
|
||||
items,
|
||||
embedThumbClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.embedThumb = enabled
|
||||
}
|
||||
},
|
||||
cropThumbClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.cropThumb = enabled
|
||||
}
|
||||
},
|
||||
splitByChaptersClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.splitByChapters = enabled
|
||||
}
|
||||
},
|
||||
filenameTemplateSet = {template ->
|
||||
items.forEach {
|
||||
it.customFileNameTemplate = template
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
items.forEach { it.audioPreferences.sponsorBlockFilters.clear() }
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
items.forEach { it.audioPreferences.sponsorBlockFilters.add(values[i]) }
|
||||
}
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
updateDataClicked = {},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
items.forEach { it.extraCommands = c }
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
}
|
||||
)
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.adjust_video)
|
||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
UiUtil.configureVideo(
|
||||
sheetView,
|
||||
requireActivity(),
|
||||
items,
|
||||
embedSubsClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.embedSubs = checked }
|
||||
},
|
||||
addChaptersClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.addChapters = checked }
|
||||
},
|
||||
splitByChaptersClicked = { checked ->
|
||||
items.forEach { it.videoPreferences.splitByChapters = checked }
|
||||
},
|
||||
saveThumbnailClicked = {checked ->
|
||||
items.forEach { it.SaveThumb = checked }
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
items.forEach { it.videoPreferences.sponsorBlockFilters.clear() }
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
items.forEach { it.videoPreferences.sponsorBlockFilters.add(values[i]) }
|
||||
UiUtil.configureAudio(
|
||||
sheetView,
|
||||
requireActivity(),
|
||||
items,
|
||||
embedThumbClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.embedThumb = enabled
|
||||
}
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
updateDataClicked = {},
|
||||
filenameTemplateSet = { checked ->
|
||||
items.forEach { it.customFileNameTemplate = checked }
|
||||
},
|
||||
saveSubtitlesClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.writeSubs = checked }
|
||||
},
|
||||
subtitleLanguagesSet = {value ->
|
||||
items.forEach { it.videoPreferences.subsLanguages = value }
|
||||
},
|
||||
removeAudioClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.removeAudio = checked }
|
||||
},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
items.forEach { it.extraCommands = c }
|
||||
bottomSheet.dismiss()
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
cropThumbClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.cropThumb = enabled
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
splitByChaptersClicked = {enabled ->
|
||||
items.forEach {
|
||||
it.audioPreferences.splitByChapters = enabled
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
filenameTemplateSet = {template ->
|
||||
items.forEach {
|
||||
it.customFileNameTemplate = template
|
||||
}
|
||||
requireActivity().lifecycleScope.launch {
|
||||
items.forEach { downloadViewModel.updateDownload(it) }
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
items.forEach { it.audioPreferences.sponsorBlockFilters.clear() }
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
items.forEach { it.audioPreferences.sponsorBlockFilters.add(values[i]) }
|
||||
}
|
||||
}
|
||||
requireActivity().lifecycleScope.launch {
|
||||
items.forEach { downloadViewModel.updateDownload(it) }
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
updateDataClicked = {},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
items.forEach { it.extraCommands = c }
|
||||
requireActivity().lifecycleScope.launch {
|
||||
items.forEach { downloadViewModel.updateDownload(it) }
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
}
|
||||
)
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.adjust_video)
|
||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||
|
||||
val items = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloads()
|
||||
}
|
||||
)
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
DownloadViewModel.Type.command -> {
|
||||
}
|
||||
UiUtil.configureVideo(
|
||||
sheetView,
|
||||
requireActivity(),
|
||||
items,
|
||||
embedSubsClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.embedSubs = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
addChaptersClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.addChapters = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
splitByChaptersClicked = { checked ->
|
||||
items.forEach { it.videoPreferences.splitByChapters = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
saveThumbnailClicked = {checked ->
|
||||
items.forEach { it.SaveThumb = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
items.forEach { it.videoPreferences.sponsorBlockFilters.clear() }
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
items.forEach { it.videoPreferences.sponsorBlockFilters.add(values[i]) }
|
||||
}
|
||||
}
|
||||
requireActivity().lifecycleScope.launch {
|
||||
items.forEach { downloadViewModel.updateDownload(it) }
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
updateDataClicked = {},
|
||||
filenameTemplateSet = { checked ->
|
||||
items.forEach { it.customFileNameTemplate = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
saveSubtitlesClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.writeSubs = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
saveAutoSubtitlesClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.writeAutoSubs = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
subtitleLanguagesSet = {value ->
|
||||
items.forEach { it.videoPreferences.subsLanguages = value }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
removeAudioClicked = {checked ->
|
||||
items.forEach { it.videoPreferences.removeAudio = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
items.forEach { it.extraCommands = c }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
||||
}
|
||||
)
|
||||
|
||||
bottomSheet.show()
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
DownloadViewModel.Type.command -> {
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
downloadViewModel.uiState.collectLatest { res ->
|
||||
if (res.errorMessage != null) {
|
||||
withContext(Dispatchers.Main){
|
||||
kotlin.runCatching {
|
||||
UiUtil.handleDownloadsResponse(
|
||||
requireActivity(),
|
||||
requireActivity().lifecycleScope,
|
||||
requireActivity().supportFragmentManager,
|
||||
res,
|
||||
downloadViewModel,
|
||||
historyViewModel)
|
||||
}
|
||||
}
|
||||
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
||||
errorMessage = null,
|
||||
actions = null
|
||||
)
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.processingDownloads.collectLatest {
|
||||
listAdapter.submitList(it)
|
||||
withContext(Dispatchers.Main){
|
||||
updateFileSize(it.map { it2 -> it2.format.filesize })
|
||||
}
|
||||
|
||||
if (it.isNotEmpty()){
|
||||
if (it.all { it2 -> it2.type == it[0].type }) {
|
||||
withContext(Dispatchers.Main) {
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
if (it[0].type != DownloadViewModel.Type.command) {
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bottomAppBar.menu[1].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -586,7 +590,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
private fun updateFileSize(items: List<Long>){
|
||||
if (items.all { it > 0L }){
|
||||
filesize.visibility = View.VISIBLE
|
||||
filesize.text = "${getString(R.string.file_size)}: ~ ${FileUtil.convertFileSize(items.sum())}"
|
||||
filesize.text = "${getString(R.string.file_size)}: >~ ${FileUtil.convertFileSize(items.sum())}"
|
||||
}else{
|
||||
filesize.visibility = View.GONE
|
||||
}
|
||||
|
|
@ -603,18 +607,21 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
items.forEach {
|
||||
it.downloadPath = result.data?.data.toString()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.updateProcessingDownloadPath(result.data?.data.toString())
|
||||
}
|
||||
|
||||
val path = FileUtil.formatPath(result.data!!.data.toString())
|
||||
Snackbar.make(requireView(),getString(R.string.changed_path_for_everyone_to) + " " + path, Snackbar.LENGTH_LONG).show()
|
||||
Toast.makeText(requireContext(),getString(R.string.changed_path_for_everyone_to) + " " + path, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onButtonClick(itemURL: String) {
|
||||
override fun onButtonClick(id: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = items.find { it.url == itemURL } ?: return@launch
|
||||
val position = items.indexOf(item)
|
||||
var item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(id)
|
||||
}
|
||||
|
||||
val bottomSheet = BottomSheetDialog(requireContext())
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
|
@ -635,26 +642,31 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
|
||||
audio!!.setOnClickListener {
|
||||
items[position] = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.audio).first()
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyItemChanged(position)
|
||||
bottomSheet.cancel()
|
||||
lifecycleScope.launch {
|
||||
item = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.audio).first()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
video!!.setOnClickListener {
|
||||
items[position] = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.video).first()
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyItemChanged(position)
|
||||
bottomSheet.cancel()
|
||||
lifecycleScope.launch {
|
||||
item = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.video).first()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
command!!.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
items[position] = withContext(Dispatchers.IO){
|
||||
downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.command).first()
|
||||
item = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.command).first()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
listAdapter.submitList(items.toList())
|
||||
listAdapter.notifyItemChanged(position)
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -670,60 +682,71 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCardClick(itemURL: String) {
|
||||
override fun onCardClick(id: Long) {
|
||||
lifecycleScope.launch{
|
||||
val downloadItem = items.find { it.url == itemURL }
|
||||
|
||||
val downloadItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(id)
|
||||
}
|
||||
|
||||
val resultItem = withContext(Dispatchers.IO){
|
||||
resultViewModel.getItemByURL(downloadItem!!.url)!!
|
||||
resultViewModel.getItemByURL(downloadItem.url)!!
|
||||
}
|
||||
if (parentFragmentManager.findFragmentByTag("configureDownloadSingleSheet") == null){
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem!!, this@DownloadMultipleBottomSheetDialog)
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem, this@DownloadMultipleBottomSheetDialog)
|
||||
bottomSheet.show(parentFragmentManager, "configureDownloadSingleSheet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDelete(itemURL: String) {
|
||||
val deletedItem = items.find { it.url == itemURL } ?: return
|
||||
val position = items.indexOf(deletedItem)
|
||||
UiUtil.showGenericDeleteDialog(requireContext(), deletedItem.title){
|
||||
items.remove(deletedItem)
|
||||
listAdapter.submitList(items.toList())
|
||||
if (items.isNotEmpty()){
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
items.add(position, deletedItem)
|
||||
listAdapter.submitList(items.toList())
|
||||
}.show()
|
||||
}else{
|
||||
dismiss()
|
||||
override fun onDelete(id: Long) {
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(id)
|
||||
} ?: return@launch
|
||||
|
||||
UiUtil.showGenericDeleteDialog(requireContext(), deletedItem.title){
|
||||
lifecycleScope.launch {
|
||||
val count = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloadsCount()
|
||||
}
|
||||
downloadViewModel.deleteDownload(id)
|
||||
|
||||
if (count > 1){
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}
|
||||
}.show()
|
||||
}else{
|
||||
dismiss()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onClick(p0: View?) {
|
||||
}
|
||||
|
||||
override fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) {
|
||||
val i = items.indexOf(items.find { it.url == item.url })
|
||||
items[i] = item
|
||||
if (! items.all { it.type == items[0].type }){
|
||||
bottomAppBar.menu[1].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
}else{
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
if (items[0].type != DownloadViewModel.Type.command){
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
listAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
if (items.count { it.type == DownloadViewModel.Type.command } == 0){
|
||||
bottomAppBar.menu[2].icon?.alpha = 255
|
||||
}else{
|
||||
bottomAppBar.menu[2].icon?.alpha = 30
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
requireActivity().lifecycleScope.launch {
|
||||
downloadViewModel.deleteProcessing()
|
||||
}
|
||||
|
||||
listAdapter.submitList(items.toList())
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
|
|
@ -734,22 +757,29 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
val deletedItem = items[position]
|
||||
items.remove(deletedItem)
|
||||
listAdapter.submitList(items.toList())
|
||||
if (items.isNotEmpty()){
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
items.add(position, deletedItem)
|
||||
listAdapter.submitList(items.toList())
|
||||
}.show()
|
||||
}else{
|
||||
dismiss()
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
val count = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getProcessingDownloadsCount()
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.deleteDownload(deletedItem.id)
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
downloadViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
}else{
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import android.widget.TextView
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -34,23 +35,19 @@ import com.deniscerri.ytdlnis.util.FileUtil
|
|||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
|
||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
||||
import com.google.android.material.textfield.TextInputLayout.EndIconMode
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
class DownloadVideoFragment(private var resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "") : Fragment(), GUISync {
|
||||
class DownloadVideoFragment(private var resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "", private var nonSpecific: Boolean = false) : Fragment(), GUISync {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -67,6 +64,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
lateinit var downloadItem: DownloadItem
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
@ -100,7 +98,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
try {
|
||||
title = view.findViewById(R.id.title_textinput)
|
||||
title.visibility = if (shownFields.contains("title")) View.VISIBLE else View.GONE
|
||||
title.visibility = if (shownFields.contains("title") && !nonSpecific) View.VISIBLE else View.GONE
|
||||
if (title.editText?.text?.isEmpty() == true){
|
||||
title.editText!!.setText(downloadItem.title)
|
||||
title.endIconMode = END_ICON_NONE
|
||||
|
|
@ -114,7 +112,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
})
|
||||
|
||||
author = view.findViewById(R.id.author_textinput)
|
||||
author.visibility = if (shownFields.contains("author")) View.VISIBLE else View.GONE
|
||||
author.visibility = if (shownFields.contains("author") && !nonSpecific) View.VISIBLE else View.GONE
|
||||
if (author.editText?.text?.isEmpty() == true){
|
||||
author.editText!!.setText(downloadItem.author)
|
||||
author.endIconMode = END_ICON_NONE
|
||||
|
|
@ -200,6 +198,15 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
val containers = requireContext().resources.getStringArray(R.array.video_containers)
|
||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||
container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
|
||||
if (nonSpecific){
|
||||
val param = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
1.0f
|
||||
)
|
||||
container.layoutParams = param
|
||||
container.setPadding(0)
|
||||
}
|
||||
val containerAutoCompleteTextView =
|
||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||
|
|
@ -329,6 +336,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
saveSubtitlesClicked = {
|
||||
downloadItem.videoPreferences.writeSubs = it
|
||||
},
|
||||
saveAutoSubtitlesClicked = {
|
||||
downloadItem.videoPreferences.writeAutoSubs = it
|
||||
},
|
||||
subtitleLanguagesSet = {
|
||||
downloadItem.videoPreferences.subsLanguages = it
|
||||
},
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
|
||||
val refreshBtn = view.findViewById<Button>(R.id.format_refresh)
|
||||
if (!hasGenericFormats || items.isEmpty()) refreshBtn.visibility = View.GONE
|
||||
if (!hasGenericFormats || items.isEmpty() || items.first()?.url?.isEmpty() == true) refreshBtn.visibility = View.GONE
|
||||
|
||||
|
||||
refreshBtn.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,530 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.ClipboardManager
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.ui.text.capitalize
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ObserveSourcesViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.w3c.dom.Text
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Month
|
||||
import java.time.format.TextStyle
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||
private lateinit var tabLayout: TabLayout
|
||||
private lateinit var viewPager2: ViewPager2
|
||||
private lateinit var fragmentAdapter : DownloadFragmentAdapter
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var observeSourcesViewModel: ObserveSourcesViewModel
|
||||
private lateinit var historyViewModel: HistoryViewModel
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var commandTemplateViewModel : CommandTemplateViewModel
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var sharedPreferences : SharedPreferences
|
||||
private lateinit var view: View
|
||||
|
||||
private lateinit var type: Type
|
||||
private var currentItem: ObserveSourcesItem? = null
|
||||
|
||||
|
||||
private lateinit var okButton: MaterialButton
|
||||
private lateinit var everyNr: TextInputLayout
|
||||
private lateinit var title: TextInputLayout
|
||||
private lateinit var url: TextInputLayout
|
||||
private lateinit var everyCat: AutoCompleteTextView
|
||||
private lateinit var everyTime: TextInputLayout
|
||||
private lateinit var weekDays: ChipGroup
|
||||
private lateinit var everyMonthDay: TextInputLayout
|
||||
private lateinit var startTime: TextInputLayout
|
||||
private lateinit var startMonth: TextInputLayout
|
||||
private lateinit var endsNever: RadioButton
|
||||
private lateinit var endsOn: RadioButton
|
||||
private lateinit var endsAfter: RadioButton
|
||||
private lateinit var endsOnTime: TextInputLayout
|
||||
private lateinit var endsAfterNr: TextInputLayout
|
||||
private lateinit var retryMissingDownloads: MaterialSwitch
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
observeSourcesViewModel = ViewModelProvider(requireActivity())[ObserveSourcesViewModel::class.java]
|
||||
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
type = arguments?.getSerializable("type") as Type
|
||||
currentItem = if (Build.VERSION.SDK_INT >= 33){
|
||||
arguments?.getParcelable("item", ObserveSourcesItem::class.java)
|
||||
}else{
|
||||
arguments?.getParcelable<ObserveSourcesItem>("item")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
val downloadItem = getDownloadItem()
|
||||
arguments?.putSerializable("type", downloadItem.type)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi", "InflateParams")
|
||||
override fun setupDialog(dialog: Dialog, style: Int) {
|
||||
super.setupDialog(dialog, style)
|
||||
view = LayoutInflater.from(context).inflate(R.layout.observe_sources_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||
val displayMetrics = DisplayMetrics()
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
if(resources.getBoolean(R.bool.isTablet) || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE){
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = displayMetrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||
|
||||
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
|
||||
isNestedScrollingEnabled = false
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
}
|
||||
|
||||
var commandTemplateNr = 0
|
||||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateNr = commandTemplateViewModel.getTotalNumber()
|
||||
if(commandTemplateNr <= 0){
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fragmentManager = parentFragmentManager
|
||||
fragmentAdapter = DownloadFragmentAdapter(
|
||||
fragmentManager,
|
||||
lifecycle,
|
||||
ResultItem(0, "", "", "", "", "", "", "", mutableListOf(), "", null, null, null, 0),
|
||||
currentItem?.downloadItemTemplate,
|
||||
true
|
||||
)
|
||||
|
||||
viewPager2.adapter = fragmentAdapter
|
||||
viewPager2.isSaveFromParentEnabled = false
|
||||
|
||||
when(type) {
|
||||
Type.audio -> {
|
||||
tabLayout.getTabAt(0)!!.select()
|
||||
viewPager2.setCurrentItem(0, false)
|
||||
}
|
||||
Type.video -> {
|
||||
tabLayout.getTabAt(1)!!.select()
|
||||
viewPager2.setCurrentItem(1, false)
|
||||
}
|
||||
else -> {
|
||||
tabLayout.getTabAt(2)!!.select()
|
||||
viewPager2.postDelayed( {
|
||||
viewPager2.setCurrentItem(2, false)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString("last_used_download_type",
|
||||
type.toString())
|
||||
}
|
||||
|
||||
|
||||
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
if (tab!!.position == 2 && commandTemplateNr == 0){
|
||||
tabLayout.selectTab(tabLayout.getTabAt(1))
|
||||
val s = Snackbar.make(view, getString(R.string.add_template_first), Snackbar.LENGTH_LONG)
|
||||
val snackbarView: View = s.view
|
||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||
snackTextView.maxLines = 9999999
|
||||
s.setAction(R.string.new_template){
|
||||
UiUtil.showCommandTemplateCreationOrUpdatingSheet(item = null, context = requireActivity(), lifeCycle = this@ObserveSourcesBottomSheetDialog, commandTemplateViewModel = commandTemplateViewModel){
|
||||
commandTemplateNr = 1
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 1f
|
||||
tabLayout.selectTab(tabLayout.getTabAt(2))
|
||||
}
|
||||
}
|
||||
s.show()
|
||||
}
|
||||
else{
|
||||
viewPager2.setCurrentItem(tab.position, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {
|
||||
}
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
}
|
||||
})
|
||||
|
||||
viewPager2.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
tabLayout.selectTab(tabLayout.getTabAt(position))
|
||||
runCatching {
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString("last_used_download_type",
|
||||
listOf(Type.audio, Type.video, Type.command)[position].toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
|
||||
|
||||
|
||||
title = view.findViewById(R.id.title_textinput)
|
||||
url = view.findViewById(R.id.url_textinput)
|
||||
everyNr = view.findViewById(R.id.every_textinput)
|
||||
everyCat = view.findViewById(R.id.everyCat)
|
||||
everyTime = view.findViewById(R.id.every_time)
|
||||
weekDays = view.findViewById(R.id.weekdays)
|
||||
everyMonthDay = view.findViewById(R.id.everyMonthDay_textInput)
|
||||
startTime = view.findViewById(R.id.start_time)
|
||||
startMonth = view.findViewById(R.id.startsMonth_textInput)
|
||||
endsNever = view.findViewById(R.id.never)
|
||||
endsOn = view.findViewById(R.id.on)
|
||||
endsOnTime = view.findViewById(R.id.on_date)
|
||||
endsAfter = view.findViewById(R.id.after)
|
||||
endsAfterNr = view.findViewById(R.id.after_nr)
|
||||
retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads)
|
||||
okButton = view.findViewById(R.id.okButton)
|
||||
|
||||
title.editText!!.setText(currentItem?.name ?: "")
|
||||
title.editText?.doAfterTextChanged { checkIfValid() }
|
||||
|
||||
url.apply {
|
||||
editText!!.setText(currentItem?.url ?: "")
|
||||
editText?.doAfterTextChanged {
|
||||
checkIfValid()
|
||||
if (url.editText!!.text.isNotEmpty()){
|
||||
url.endIconDrawable = AppCompatResources.getDrawable(requireActivity(), R.drawable.ic_delete_all)
|
||||
}else{
|
||||
url.endIconDrawable = AppCompatResources.getDrawable(requireActivity(), R.drawable.ic_clipboard)
|
||||
}
|
||||
}
|
||||
setEndIconOnClickListener {
|
||||
if(url.editText!!.text.isEmpty()){
|
||||
val clipboard: ClipboardManager =
|
||||
requireActivity().getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
url.editText!!.setText(clipboard.primaryClip?.getItemAt(0)?.text)
|
||||
}else{
|
||||
url.editText!!.setText("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
everyNr.editText?.setText(currentItem?.everyNr?.toString() ?: "1")
|
||||
everyNr.editText?.doAfterTextChanged { checkIfValid() }
|
||||
|
||||
val cats = ObserveSourcesRepository.everyCategoryName.map { getString(it.value) }
|
||||
val adapter = ArrayAdapter(requireActivity(),android.R.layout.simple_dropdown_item_1line, cats)
|
||||
everyCat.doAfterTextChanged {
|
||||
weekDays.isVisible = it.toString() == cats[1]
|
||||
startMonth.isVisible = it.toString() == cats[2]
|
||||
startTime.isVisible = !startMonth.isVisible
|
||||
everyMonthDay.isVisible = startMonth.isVisible
|
||||
}
|
||||
everyCat.setAdapter(adapter)
|
||||
if (currentItem != null){
|
||||
val idx = ObserveSourcesRepository.EveryCategory.values().indexOf(currentItem!!.everyCategory)
|
||||
everyCat.setText(cats[idx], false)
|
||||
}else{
|
||||
everyCat.setText(cats.first(), false)
|
||||
}
|
||||
|
||||
everyTime.apply {
|
||||
editText?.apply {
|
||||
isFocusable = false
|
||||
isClickable = false
|
||||
setOnClickListener{
|
||||
UiUtil.showTimePicker(fragmentManager){
|
||||
everyTime.editText?.setText(
|
||||
SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "HHmm"), Locale.getDefault()).format(it.timeInMillis)
|
||||
)
|
||||
everyTime.tag = it.timeInMillis
|
||||
checkIfValid()
|
||||
}
|
||||
}
|
||||
if (currentItem != null){
|
||||
setText(SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "HHmm"), Locale.getDefault()).format(currentItem?.everyTime))
|
||||
everyTime.tag = currentItem?.everyTime
|
||||
checkIfValid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val calendar = Calendar.getInstance()
|
||||
//using 2024 because it starts with a monday
|
||||
calendar.set(Calendar.YEAR, 2024) // Set the year
|
||||
calendar.set(Calendar.MONTH, Calendar.JANUARY) // Set the month (Note: Months are zero-based)
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1) // Set the day of the month
|
||||
calendar.firstDayOfWeek = Calendar.MONDAY
|
||||
|
||||
val daysOfWeek = mutableListOf<String>()
|
||||
val daysOfWeekTags = mutableListOf<String>()
|
||||
for (i in 0..6) {
|
||||
val dayName = calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault())!!
|
||||
daysOfWeek.add(dayName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() })
|
||||
daysOfWeekTags.add(calendar.get(Calendar.DAY_OF_WEEK).toString())
|
||||
calendar.add(Calendar.DAY_OF_WEEK, 1)
|
||||
}
|
||||
weekDays.children.forEachIndexed { index, view ->
|
||||
(view as Chip).text = daysOfWeek[index]
|
||||
(view as Chip).tag = daysOfWeekTags[index]
|
||||
}
|
||||
|
||||
if (currentItem != null){
|
||||
weekDays.children.forEach { view ->
|
||||
(view as Chip).isChecked = currentItem!!.everyWeekDay.contains(view.tag.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val everyMonthDayAutoCompleteTextView = view.findViewById<AutoCompleteTextView>(R.id.everyMonthDay)
|
||||
everyMonthDayAutoCompleteTextView.setText(
|
||||
currentItem?.everyMonthDay?.toString() ?: Calendar.getInstance().get(Calendar.DAY_OF_MONTH).toString()
|
||||
, false)
|
||||
|
||||
val months = mutableListOf<String>()
|
||||
for (i in 0..11){
|
||||
months.add(calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())!!.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() })
|
||||
calendar.add(Calendar.MONTH, 1)
|
||||
}
|
||||
|
||||
val startMonthAutoCompleteTextView = startMonth.findViewById<AutoCompleteTextView>(R.id.startsMonth)
|
||||
startMonthAutoCompleteTextView.setAdapter(ArrayAdapter(requireActivity(),android.R.layout.simple_dropdown_item_1line, months))
|
||||
if (currentItem == null){
|
||||
startMonthAutoCompleteTextView.setText(months.first(), false)
|
||||
}else{
|
||||
calendar.set(Calendar.MONTH, Month.values().indexOf(currentItem!!.startsMonth))
|
||||
startMonthAutoCompleteTextView.setText(calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault())!!.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }, false)
|
||||
}
|
||||
|
||||
startTime.apply {
|
||||
editText?.apply {
|
||||
isFocusable = false
|
||||
isClickable = true
|
||||
setOnClickListener {
|
||||
UiUtil.showDatePickerOnly(fragmentManager){
|
||||
startTime.editText?.setText(
|
||||
SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy"), Locale.getDefault()).format(it.timeInMillis)
|
||||
)
|
||||
startTime.tag = it.timeInMillis
|
||||
checkIfValid()
|
||||
}
|
||||
}
|
||||
val now = Calendar.getInstance().timeInMillis
|
||||
startTime.editText?.setText(
|
||||
SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy"), Locale.getDefault()).format(now)
|
||||
)
|
||||
startTime.tag = now
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
endsOnTime.apply {
|
||||
editText?.apply {
|
||||
isFocusable = false
|
||||
isClickable = true
|
||||
setText(
|
||||
SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy"), Locale.getDefault()).format(
|
||||
Calendar.getInstance().apply { add(Calendar.MONTH, 5) }.timeInMillis)
|
||||
)
|
||||
tag = Calendar.getInstance().apply { add(Calendar.MONTH, 5) }.timeInMillis
|
||||
setOnClickListener {
|
||||
UiUtil.showDatePickerOnly(fragmentManager){
|
||||
endsOnTime.editText?.setText(
|
||||
SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy"), Locale.getDefault()).format(it.timeInMillis)
|
||||
)
|
||||
endsOnTime.tag = it.timeInMillis
|
||||
checkIfValid()
|
||||
}
|
||||
}
|
||||
if (currentItem != null && currentItem!!.endsDate > 0){
|
||||
setText(SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "HHmm"), Locale.getDefault()).format(currentItem?.endsDate))
|
||||
endsOnTime.tag = currentItem?.endsDate
|
||||
checkIfValid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
endsAfterNr.editText?.setText("10")
|
||||
endsAfterNr.editText?.doAfterTextChanged { checkIfValid() }
|
||||
if (currentItem != null && currentItem!!.endsAfterCount > 0){
|
||||
endsAfterNr.editText?.setText(currentItem!!.endsAfterCount.toString())
|
||||
}
|
||||
|
||||
endsNever.setOnClickListener {
|
||||
endsOn.isChecked = false
|
||||
endsAfter.isChecked = false
|
||||
checkIfValid()
|
||||
}
|
||||
|
||||
endsOn.setOnClickListener {
|
||||
endsNever.isChecked = false
|
||||
endsAfter.isChecked = false
|
||||
checkIfValid()
|
||||
}
|
||||
if ((currentItem?.endsDate ?: 0) > 0){
|
||||
endsOn.performClick()
|
||||
}
|
||||
|
||||
endsAfter.setOnClickListener {
|
||||
endsOn.isChecked = false
|
||||
endsNever.isChecked = false
|
||||
checkIfValid()
|
||||
}
|
||||
if ((currentItem?.endsAfterCount ?: 0) > 0){
|
||||
endsAfter.performClick()
|
||||
}
|
||||
|
||||
if (currentItem != null) okButton.text = getString(R.string.update)
|
||||
okButton.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
val item: DownloadItem = getDownloadItem()
|
||||
|
||||
|
||||
var ends = 0L
|
||||
if(endsOn.isChecked){
|
||||
ends = endsOnTime.tag as Long
|
||||
}
|
||||
|
||||
var endsAfterCount = 0
|
||||
if(endsAfter.isChecked){
|
||||
endsAfterCount = endsAfterNr.editText!!.text.toString().toInt()
|
||||
}
|
||||
|
||||
val observeItem = ObserveSourcesItem(
|
||||
currentItem?.id ?: 0,
|
||||
title.editText!!.text.toString(),
|
||||
url.editText!!.text.toString(),
|
||||
item,
|
||||
ObserveSourcesRepository.SourceStatus.ACTIVE,
|
||||
everyNr.editText!!.text.toString().toInt(),
|
||||
ObserveSourcesRepository.EveryCategory.values()[adapter.getPosition(everyCat.text.toString())],
|
||||
weekDays.children.filter { (it as Chip).isChecked }.map { it.tag.toString() }.toList(),
|
||||
everyMonthDay.editText!!.text.toString().toInt(),
|
||||
everyTime.tag as Long,
|
||||
startTime.tag as Long,
|
||||
Month.values()[startMonthAutoCompleteTextView.selectionStart],
|
||||
ends,
|
||||
endsAfterCount,
|
||||
0,
|
||||
retryMissingDownloads.isChecked,
|
||||
mutableListOf()
|
||||
)
|
||||
withContext(Dispatchers.IO){
|
||||
observeSourcesViewModel.insert(observeItem)
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
checkIfValid()
|
||||
}
|
||||
|
||||
private fun checkIfValid(){
|
||||
var valid = title.editText?.text?.isNotBlank() == true
|
||||
&& url.editText?.text?.isNotBlank() == true
|
||||
&& everyNr.editText?.text?.isNotBlank() == true
|
||||
&& everyTime.editText?.text?.isNotBlank() == true
|
||||
&& startTime.editText?.text?.isNotBlank() == true
|
||||
|
||||
if (endsOn.isChecked && endsOnTime.editText?.text?.isBlank() == true) valid = false
|
||||
else if (endsAfter.isChecked && endsAfterNr.editText?.text?.isBlank() == true) valid = false
|
||||
|
||||
okButton.isEnabled = valid
|
||||
}
|
||||
|
||||
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
|
||||
return when(selectedTabPosition){
|
||||
0 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.downloadItem
|
||||
}
|
||||
1 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||
f.downloadItem
|
||||
}
|
||||
else -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
|
||||
f.downloadItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.ui.adapter.PlaylistAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||
|
|
@ -31,6 +32,7 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -197,11 +199,13 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
|||
downloadItems.add(i)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadMultipleBottomSheetDialog, bundleOf(
|
||||
Pair("downloads", downloadItems)
|
||||
))
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.insertToProcessing(downloadItems)
|
||||
}
|
||||
|
||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
||||
Pair("type", downloadItems[0].type)
|
||||
))
|
||||
}
|
||||
|
||||
dismiss()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -17,8 +18,10 @@ import androidx.viewpager2.widget.ViewPager2
|
|||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
|
|
@ -136,6 +139,24 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
downloadViewModel.deleteSaved()
|
||||
}
|
||||
}
|
||||
R.id.copy_urls -> {
|
||||
lifecycleScope.launch {
|
||||
val tabStatus = mapOf(
|
||||
0 to listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused, DownloadRepository.Status.PausedReQueued),
|
||||
1 to listOf(DownloadRepository.Status.Queued, DownloadRepository.Status.Queued),
|
||||
2 to listOf(DownloadRepository.Status.Cancelled),
|
||||
3 to listOf(DownloadRepository.Status.Error),
|
||||
4 to listOf(DownloadRepository.Status.Saved),
|
||||
)
|
||||
tabStatus[tabLayout.selectedTabPosition]?.apply {
|
||||
val urls = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getURLsByStatus(this@apply)
|
||||
}
|
||||
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}catch (e: Exception){
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import com.google.android.material.chip.Chip
|
|||
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.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
|
@ -80,7 +81,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
private var fragmentContext: Context? = null
|
||||
private var layoutinflater: LayoutInflater? = null
|
||||
private var topAppBar: MaterialToolbar? = null
|
||||
private var recyclerView: RecyclerView? = null
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private var historyAdapter: HistoryAdapter? = null
|
||||
private var sortSheet: BottomSheetDialog? = null
|
||||
private var uiHandler: Handler? = null
|
||||
|
|
@ -128,8 +129,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
requireActivity()
|
||||
)
|
||||
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
||||
recyclerView?.adapter = historyAdapter
|
||||
recyclerView?.enableFastScroll()
|
||||
recyclerView.adapter = historyAdapter
|
||||
recyclerView.enableFastScroll()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("history")){
|
||||
|
|
@ -137,7 +138,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
recyclerView?.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
recyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
noResults?.visibility = GONE
|
||||
|
||||
|
||||
|
|
@ -185,11 +186,11 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
|
||||
|
||||
lifecycleScope.launch{
|
||||
downloadViewModel.uiState.collectLatest { res ->
|
||||
if (res.errorMessage != null) {
|
||||
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
|
||||
if (res.downloadItems.isNotEmpty() || res.historyItems.isNotEmpty()) {
|
||||
withContext(Dispatchers.Main){
|
||||
kotlin.runCatching {
|
||||
UiUtil.handleDownloadsResponse(
|
||||
UiUtil.handleExistingDownloadsResponse(
|
||||
requireActivity(),
|
||||
requireActivity().lifecycleScope,
|
||||
requireActivity().supportFragmentManager,
|
||||
|
|
@ -198,9 +199,9 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
historyViewModel)
|
||||
}
|
||||
}
|
||||
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
||||
errorMessage = null,
|
||||
actions = null
|
||||
downloadViewModel.alreadyExistsUiState.value = DownloadViewModel.AlreadyExistsUIState(
|
||||
mutableListOf(),
|
||||
mutableListOf()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -396,6 +397,17 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
command.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
val notDeleted = fragmentView!!.findViewById<Chip>(R.id.not_deleted_chip)
|
||||
notDeleted.setOnClickListener {
|
||||
if (notDeleted.isChecked) {
|
||||
historyViewModel.setNotDeleted(true)
|
||||
notDeleted.isChecked = true
|
||||
} else {
|
||||
historyViewModel.setNotDeleted(false)
|
||||
notDeleted.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWebsiteChips(list : List<HistoryItem>) {
|
||||
|
|
@ -577,7 +589,18 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
historyAdapter!!.notifyItemChanged(position)
|
||||
UiUtil.showRemoveHistoryItemDialog(deletedItem!!, requireActivity(),
|
||||
delete = { item, deleteFile ->
|
||||
historyViewModel.delete(item, deleteFile)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
historyViewModel.delete(item, deleteFile)
|
||||
}
|
||||
|
||||
if (!deleteFile){
|
||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
historyViewModel.insert(deletedItem)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
ItemTouchHelper.RIGHT -> {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
|||
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType
|
||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.chip.Chip
|
||||
|
|
@ -39,6 +39,7 @@ import com.google.android.material.textfield.TextInputLayout
|
|||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
|
@ -162,38 +163,51 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
}
|
||||
|
||||
private fun showDialog(url: String){
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.cookies))
|
||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
|
||||
val text = if (url.isNullOrBlank()) "https://" else url
|
||||
editText.setText(text)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.get_cookies)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
val myIntent = Intent(requireContext(), WebViewActivity::class.java)
|
||||
myIntent.putExtra("url", editText.text.toString())
|
||||
startActivity(myIntent)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
var item = withContext(Dispatchers.IO){
|
||||
cookiesViewModel.getByURL(url)
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
val dialog = builder.create()
|
||||
editText.doOnTextChanged { text, start, before, count ->
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.cookies))
|
||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
|
||||
val text = if (url.isNullOrBlank()) "https://" else url
|
||||
editText.setText(text)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setNeutralButton(
|
||||
getString(android.R.string.copy)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
UiUtil.copyToClipboard(item.content, requireActivity())
|
||||
}
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.get_cookies)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
val myIntent = Intent(requireContext(), WebViewActivity::class.java)
|
||||
myIntent.putExtra("url", editText.text.toString())
|
||||
startActivity(myIntent)
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { dialog: DialogInterface?, which: Int -> }
|
||||
val dialog = builder.create()
|
||||
editText.doOnTextChanged { text, start, before, count ->
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
dialog.show()
|
||||
val imm = mainActivity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
}
|
||||
dialog.show()
|
||||
val imm = mainActivity.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editText!!.postDelayed({
|
||||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class MoreFragment : Fragment() {
|
|||
private lateinit var commandTemplates: TextView
|
||||
private lateinit var downloadQueue: TextView
|
||||
private lateinit var cookies: TextView
|
||||
private lateinit var observeSources: TextView
|
||||
private lateinit var terminateApp: TextView
|
||||
private lateinit var settings: TextView
|
||||
private lateinit var mainActivity: MainActivity
|
||||
|
|
@ -58,6 +59,7 @@ class MoreFragment : Fragment() {
|
|||
commandTemplates = view.findViewById(R.id.command_templates)
|
||||
downloadQueue = view.findViewById(R.id.download_queue)
|
||||
cookies = view.findViewById(R.id.cookies)
|
||||
observeSources = view.findViewById(R.id.observe_sources)
|
||||
terminateApp = view.findViewById(R.id.terminate)
|
||||
settings = view.findViewById(R.id.settings)
|
||||
|
||||
|
|
@ -82,6 +84,10 @@ class MoreFragment : Fragment() {
|
|||
findNavController().navigate(R.id.cookiesFragment)
|
||||
}
|
||||
|
||||
observeSources.setOnClickListener {
|
||||
findNavController().navigate(R.id.observeSourcesFragment)
|
||||
}
|
||||
|
||||
terminateApp.setOnClickListener {
|
||||
if (mainSharedPreferences.getBoolean("ask_terminate_app", true)){
|
||||
var doNotShowAgain = false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,225 @@
|
|||
package com.deniscerri.ytdlnis.ui.more
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.ObserveSourcesViewModel
|
||||
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
||||
import com.deniscerri.ytdlnis.ui.adapter.ObserveSourcesAdapter
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickListener {
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var listAdapter: ObserveSourcesAdapter
|
||||
private lateinit var topAppBar: MaterialToolbar
|
||||
private lateinit var observeSourcesViewModel: ObserveSourcesViewModel
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var noResults : RelativeLayout
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var preferences: SharedPreferences
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
mainActivity = activity as MainActivity
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
return inflater.inflate(R.layout.fragment_observe_sources, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
topAppBar = view.findViewById(R.id.toolbar)
|
||||
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
||||
noResults = view.findViewById(R.id.no_results)
|
||||
|
||||
listAdapter =
|
||||
ObserveSourcesAdapter(
|
||||
this,
|
||||
mainActivity
|
||||
)
|
||||
val newSource = view.findViewById<Chip>(R.id.newSource)
|
||||
recyclerView = view.findViewById(R.id.source_recyclerView)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
recyclerView.adapter = listAdapter
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("cookies")){
|
||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
|
||||
observeSourcesViewModel = ViewModelProvider(this)[ObserveSourcesViewModel::class.java]
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
observeSourcesViewModel.items.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty()) noResults.visibility = View.VISIBLE
|
||||
else noResults.visibility = View.GONE
|
||||
listAdapter.submitList(it)
|
||||
}
|
||||
initMenu()
|
||||
|
||||
newSource.setOnClickListener {
|
||||
showDialog(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initMenu() {
|
||||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
R.id.delete_sources -> {
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.confirm_delete_history))
|
||||
deleteDialog.setMessage(getString(R.string.confirm_delete_sources_desc))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
observeSourcesViewModel.deleteAll()
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog(url: String?){
|
||||
lifecycleScope.launch {
|
||||
val bundle = Bundle()
|
||||
bundle.putSerializable("type", downloadViewModel.getDownloadType(
|
||||
DownloadViewModel.Type.valueOf(preferences.getString("download_type", "auto")!!), "")
|
||||
)
|
||||
|
||||
if (url != null){
|
||||
val item = withContext(Dispatchers.IO){
|
||||
observeSourcesViewModel.getByURL(url)
|
||||
}
|
||||
bundle.putParcelable("item", item)
|
||||
}
|
||||
|
||||
findNavController().navigate(R.id.observeSourcesBottomSheetDialog, bundle)
|
||||
}
|
||||
|
||||
}
|
||||
override fun onItemSearch(item: ObserveSourcesItem) {
|
||||
runCatching {
|
||||
val intent = Intent(context, ObserveAlarmReceiver::class.java)
|
||||
intent.putExtra("id", item.id)
|
||||
requireActivity().sendBroadcast(intent)
|
||||
}.onFailure {
|
||||
Snackbar.make(requireView(), it.message.toString(), Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemStart(item: ObserveSourcesItem, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
item.status = ObserveSourcesRepository.SourceStatus.ACTIVE
|
||||
item.runCount = 0
|
||||
observeSourcesViewModel.insert(item)
|
||||
}
|
||||
listAdapter.notifyItemChanged(position)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onItemClick(item: ObserveSourcesItem) {
|
||||
showDialog(item.url)
|
||||
}
|
||||
|
||||
override fun onDelete(item: ObserveSourcesItem) {
|
||||
UiUtil.showGenericDeleteDialog(requireContext(), item.name){
|
||||
observeSourcesViewModel.delete(item)
|
||||
}
|
||||
}
|
||||
|
||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT ) {
|
||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val url = viewHolder.itemView.tag.toString()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
observeSourcesViewModel.getByURL(url)
|
||||
}
|
||||
listAdapter.notifyItemChanged(position)
|
||||
onDelete(deletedItem)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChildDraw(
|
||||
c: Canvas,
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
dX: Float,
|
||||
dY: Float,
|
||||
actionState: Int,
|
||||
isCurrentlyActive: Boolean
|
||||
) {
|
||||
RecyclerViewSwipeDecorator.Builder(
|
||||
context,
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
.addSwipeLeftBackgroundColor(Color.RED)
|
||||
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
|
||||
.create()
|
||||
.decorate()
|
||||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.ActionBar
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
|
|
@ -13,10 +14,12 @@ import android.widget.HorizontalScrollView
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
||||
|
|
@ -39,6 +42,7 @@ class DownloadLogFragment : Fragment() {
|
|||
private lateinit var copyLog : ExtendedFloatingActionButton
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var logViewModel: LogViewModel
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -47,6 +51,7 @@ class DownloadLogFragment : Fragment() {
|
|||
): View? {
|
||||
mainActivity = activity as MainActivity
|
||||
mainActivity.hideBottomNavigation()
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
return inflater.inflate(R.layout.fragment_download_log, container, false)
|
||||
}
|
||||
|
||||
|
|
@ -141,9 +146,13 @@ class DownloadLogFragment : Fragment() {
|
|||
slider?.apply {
|
||||
this.valueFrom = 0f
|
||||
this.valueTo = 10f
|
||||
this.value = 2f
|
||||
this.value = sharedPreferences.getFloat("log_zoom", 2f)
|
||||
content.setCustomTextSize(this.value + 13f)
|
||||
this.addOnChangeListener { slider, value, fromUser ->
|
||||
content.setCustomTextSize(value + 13f)
|
||||
sharedPreferences.edit(true){
|
||||
putFloat("log_zoom", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.preference.Preference
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers.AlarmStartReceiver
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||
import java.util.Calendar
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import android.widget.ScrollView
|
|||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
|
|
@ -278,10 +279,15 @@ class TerminalFragment : Fragment() {
|
|||
slider?.apply {
|
||||
this.valueFrom = 0f
|
||||
this.valueTo = 10f
|
||||
this.value = 2f
|
||||
this.value = sharedPreferences.getFloat("terminal_zoom", 2f)
|
||||
output?.setCustomTextSize(this.value + 13f)
|
||||
input?.setCustomTextSize(this.value + 13f)
|
||||
this.addOnChangeListener { slider, value, fromUser ->
|
||||
output?.setCustomTextSize(value + 13f)
|
||||
input?.setCustomTextSize(value + 13f)
|
||||
sharedPreferences.edit(true){
|
||||
putFloat("terminal_zoom", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Looper
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.await
|
||||
import com.deniscerri.ytdlnis.R
|
||||
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.Extensions.closestValue
|
||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||
import java.time.Month
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
object DownloadUtil {
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context) {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
|
||||
val currentWork = workManager.getWorkInfosByTag("download").await()
|
||||
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
var delay = 0L
|
||||
if (queuedItems.isNotEmpty()){
|
||||
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<DownloadWorker>()
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelObservationTaskByID(context: Context, id: Long){
|
||||
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
||||
alarmManager.cancel(
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
id.toInt(),
|
||||
Intent(context, ObserveAlarmReceiver::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
|
|
@ -11,25 +12,26 @@ import android.graphics.drawable.shapes.OvalShape
|
|||
import android.media.MediaMetadataRetriever
|
||||
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.view.animation.Interpolator
|
||||
import android.widget.EditText
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Px
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.whenStarted
|
||||
import androidx.lifecycle.withStarted
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.neo.highlight.core.Highlight
|
||||
import com.neo.highlight.util.listener.HighlightTextWatcher
|
||||
import com.neo.highlight.util.scheme.ColorScheme
|
||||
|
|
@ -37,7 +39,9 @@ import kotlinx.coroutines.launch
|
|||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
object Extensions {
|
||||
|
|
@ -110,6 +114,7 @@ object Extensions {
|
|||
drawable.paint.color = context.getColor(android.R.color.transparent)
|
||||
|
||||
FastScrollerBuilder(this)
|
||||
.useMd2Style()
|
||||
.setTrackDrawable(drawable)
|
||||
.build()
|
||||
}
|
||||
|
|
@ -187,4 +192,41 @@ object Extensions {
|
|||
this.setTextSize(TypedValue.COMPLEX_UNIT_SP, newSize)
|
||||
}
|
||||
|
||||
fun Int.toStringDuration(locale: Locale): String {
|
||||
var format = String.format(
|
||||
locale,
|
||||
"%02d:%02d:%02d",
|
||||
this / 3600,
|
||||
this % 3600 / 60,
|
||||
this % 60
|
||||
)
|
||||
// 00:00:00
|
||||
if (this < 600) format = format.substring(4) else if (this < 3600) format =
|
||||
format.substring(3) else if (this < 36000) format = format.substring(1)
|
||||
return format
|
||||
}
|
||||
|
||||
|
||||
fun View.popup(){
|
||||
val animator = ValueAnimator.ofFloat( 0.75f, 1f)
|
||||
animator.addUpdateListener { animation: ValueAnimator ->
|
||||
val value = animation.animatedValue as Float
|
||||
this.scaleX = value
|
||||
this.scaleY = value
|
||||
}
|
||||
animator.interpolator = Extensions.CustomInterpolator()
|
||||
animator.setDuration(300)
|
||||
animator.start()
|
||||
}
|
||||
|
||||
fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) }
|
||||
|
||||
class CustomInterpolator : Interpolator {
|
||||
override fun getInterpolation(input: Float): Float {
|
||||
// Adjust this curve as needed for desired animation feel
|
||||
return (Math.pow((input - 1).toDouble(), 5.0) + 1).toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -286,6 +286,10 @@ object FileUtil {
|
|||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/Command"
|
||||
}
|
||||
|
||||
fun getDownloadArchivePath(context: Context) : String {
|
||||
return context.cacheDir.absolutePath + "/download_archive.txt"
|
||||
}
|
||||
|
||||
fun getDefaultTerminalPath() : String {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/TERMINAL_CACHE"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
|
|||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.Extensions.getIntByAny
|
||||
import com.deniscerri.ytdlnis.util.Extensions.getStringByAny
|
||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
@ -122,7 +123,6 @@ class InfoUtil(private val context: Context) {
|
|||
val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))
|
||||
itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||
itm?.playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||
itm?.playlistIndex = (i+1)
|
||||
items.add(itm)
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ class InfoUtil(private val context: Context) {
|
|||
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
||||
}
|
||||
|
||||
val duration = formatIntegerDuration(obj.getInt("duration"), Locale.US)
|
||||
val duration = obj.getInt("duration").toStringDuration(Locale.US)
|
||||
val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
||||
val formats : ArrayList<Format> = ArrayList()
|
||||
|
||||
|
|
@ -206,6 +206,9 @@ class InfoUtil(private val context: Context) {
|
|||
}else{
|
||||
formatObj.format_note = formatObj.format_note + " Audio"
|
||||
}
|
||||
if (!formatObj.tbr.isNullOrBlank()){
|
||||
formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k"
|
||||
}
|
||||
|
||||
}catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
@ -223,6 +226,9 @@ class InfoUtil(private val context: Context) {
|
|||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||
try{
|
||||
formatObj.vcodec = format.getString("codec")
|
||||
if (!formatObj.tbr.isNullOrBlank()){
|
||||
formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k"
|
||||
}
|
||||
}catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
@ -453,6 +459,10 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--compat-options", "manifest-filesize-approx")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
|
|
@ -505,6 +515,10 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("-R", "1")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
}
|
||||
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
|
|
@ -588,6 +602,10 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--compat-options", "manifest-filesize-approx")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
|
|
@ -625,7 +643,7 @@ class InfoUtil(private val context: Context) {
|
|||
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
||||
var duration = jsonObject.getIntByAny("duration").toString()
|
||||
if (duration != "-1"){
|
||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
||||
}
|
||||
|
||||
var thumb: String? = ""
|
||||
|
|
@ -724,6 +742,7 @@ class InfoUtil(private val context: Context) {
|
|||
}catch (ignored: Exception){}
|
||||
}
|
||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||
|
||||
if (formatProper.format_note == null) continue
|
||||
if (format.has("format_note")){
|
||||
if (!formatProper!!.format_note.contains("audio only", true)) {
|
||||
|
|
@ -736,10 +755,20 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
if (formatProper.format_note == "storyboard") continue
|
||||
formatProper.container = format.getString("ext")
|
||||
if (formatProper.tbr == "None") formatProper.tbr = ""
|
||||
if (!formatProper.tbr.isNullOrBlank()){
|
||||
formatProper.tbr = formatProper.tbr + "k"
|
||||
}
|
||||
|
||||
if(formatProper.vcodec.isEmpty() || formatProper.vcodec == "null"){
|
||||
if(formatProper.acodec.isEmpty() || formatProper.acodec == "null"){
|
||||
formatProper.vcodec = format.getStringByAny("video_ext", "ext").ifEmpty { "unknown" }
|
||||
}
|
||||
}
|
||||
|
||||
formats.add(formatProper)
|
||||
}
|
||||
}
|
||||
|
||||
return formats
|
||||
}
|
||||
|
||||
|
|
@ -751,6 +780,10 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("-R", "1")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
|
|
@ -780,7 +813,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
var duration = jsonObject.getIntByAny("duration").toString()
|
||||
if (duration != "-1"){
|
||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
||||
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
||||
}
|
||||
|
||||
var thumb: String? = ""
|
||||
|
|
@ -871,19 +904,7 @@ class InfoUtil(private val context: Context) {
|
|||
return duration
|
||||
}
|
||||
|
||||
fun formatIntegerDuration(dur: Int, locale: Locale): String {
|
||||
var format = String.format(
|
||||
locale,
|
||||
"%02d:%02d:%02d",
|
||||
dur / 3600,
|
||||
dur % 3600 / 60,
|
||||
dur % 60
|
||||
)
|
||||
// 00:00:00
|
||||
if (dur < 600) format = format.substring(4) else if (dur < 3600) format =
|
||||
format.substring(3) else if (dur < 36000) format = format.substring(1)
|
||||
return format
|
||||
}
|
||||
|
||||
|
||||
fun getStreamingUrlAndChapters(url: String) : MutableList<String?> {
|
||||
try {
|
||||
|
|
@ -904,6 +925,10 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("-R", "1")
|
||||
request.addOption("--socket-timeout", "5")
|
||||
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
|
|
@ -955,11 +980,16 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank() || downloadItem.playlistIndex == null){
|
||||
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank()){
|
||||
YoutubeDLRequest(downloadItem.url)
|
||||
}else{
|
||||
YoutubeDLRequest(downloadItem.playlistURL!!).apply {
|
||||
addOption("-I", "${downloadItem.playlistIndex!!}:${downloadItem.playlistIndex}")
|
||||
if(downloadItem.playlistIndex == null){
|
||||
val matchPortion = downloadItem.url.split("/").last().split("=").last().split("&").first()
|
||||
addOption("--match-filter", "id~='${matchPortion}'")
|
||||
}else{
|
||||
addOption("-I", "${downloadItem.playlistIndex!!}:${downloadItem.playlistIndex}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1006,6 +1036,9 @@ class InfoUtil(private val context: Context) {
|
|||
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
||||
|
||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) request.addOption("--restrict-filenames")
|
||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||
request.addOption("-4")
|
||||
}
|
||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||
FileUtil.getCookieFile(context){
|
||||
request.addOption("--cookies", it)
|
||||
|
|
@ -1125,6 +1158,10 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("download_archive", false)){
|
||||
request.addOption("--download-archive", FileUtil.getDownloadArchivePath(context))
|
||||
}
|
||||
|
||||
val preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!!
|
||||
val aCodecPrefIndex = context.getStringArray(R.array.audio_codec_values).indexOf(preferredAudioCodec)
|
||||
val aCodecPref = runCatching { context.getStringArray(R.array.audio_codec_values_ytdlp)[aCodecPrefIndex] }.getOrElse { "" }
|
||||
|
|
@ -1365,17 +1402,23 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--sub-format", "${subFormat}/best")
|
||||
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.embedSubs) {
|
||||
request.addOption("--embed-subs")
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs){
|
||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||
if(downloadItem.videoPreferences.writeAutoSubs){
|
||||
request.addOption("--write-auto-subs")
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs || downloadItem.videoPreferences.writeAutoSubs){
|
||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (downloadItem.videoPreferences.removeAudio){
|
||||
request.addOption("--ppa", "ffmpeg:-an")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,27 @@ class NotificationUtil(var context: Context) {
|
|||
.build()
|
||||
}
|
||||
|
||||
fun createObserveSourcesNotification(title: String) : Notification {
|
||||
val notificationBuilder = getBuilder(DOWNLOAD_WORKER_CHANNEL_ID)
|
||||
|
||||
return notificationBuilder
|
||||
.setContentTitle(resources.getString(R.string.observe_sources))
|
||||
.setContentText(title)
|
||||
.setOngoing(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||
.setLargeIcon(
|
||||
BitmapFactory.decodeResource(
|
||||
resources,
|
||||
R.drawable.ic_launcher_foreground_large
|
||||
)
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.clearActions()
|
||||
.build()
|
||||
}
|
||||
|
||||
fun createDownloadServiceNotification(
|
||||
pendingIntent: PendingIntent?,
|
||||
title: String?,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import android.widget.Button
|
|||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.OptIn
|
||||
|
|
@ -33,14 +34,18 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.ui.text.toUpperCase
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
|
|
@ -53,12 +58,18 @@ import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
|||
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.AlreadyExistsAdapter
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
|
||||
import com.deniscerri.ytdlnis.util.Extensions.dp
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
|
@ -396,6 +407,14 @@ object UiUtil {
|
|||
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
|
||||
item.apply {
|
||||
findViewById<TextView>(R.id.file_name).text = file.nameWithoutExtension
|
||||
|
||||
findViewById<TextView>(R.id.duration).apply {
|
||||
val duration = file.getMediaDuration(context)
|
||||
isVisible = duration > 0
|
||||
text = duration.toStringDuration(Locale.US)
|
||||
}
|
||||
|
||||
|
||||
findViewById<TextView>(R.id.filesize).text = FileUtil.convertFileSize(file.length())
|
||||
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
|
||||
if (!file.exists()){
|
||||
|
|
@ -456,6 +475,25 @@ object UiUtil {
|
|||
}
|
||||
}
|
||||
|
||||
fun showDatePickerOnly(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
||||
val date = Calendar.getInstance()
|
||||
|
||||
val datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setCalendarConstraints(
|
||||
CalendarConstraints.Builder()
|
||||
.setValidator(DateValidatorPointForward.now())
|
||||
.build()
|
||||
)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.build()
|
||||
|
||||
datePicker.addOnPositiveButtonClickListener{
|
||||
date.timeInMillis = it
|
||||
onSubmit(date)
|
||||
}
|
||||
datePicker.show(fragmentManager, "datepicker")
|
||||
}
|
||||
|
||||
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
||||
val currentDate = Calendar.getInstance()
|
||||
val date = Calendar.getInstance()
|
||||
|
|
@ -869,6 +907,7 @@ object UiUtil {
|
|||
val formatnoteParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_note_parent)
|
||||
val fpsParent = bottomSheet.findViewById<ConstraintLayout>(R.id.fps_parent)
|
||||
val asrParent = bottomSheet.findViewById<ConstraintLayout>(R.id.asr_parent)
|
||||
val bitrateParent = bottomSheet.findViewById<ConstraintLayout>(R.id.bitrate_parent)
|
||||
|
||||
|
||||
val clicker = View.OnClickListener {
|
||||
|
|
@ -954,6 +993,13 @@ object UiUtil {
|
|||
asrParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
if (format.tbr.isNullOrBlank()) bitrateParent?.visibility = View.GONE
|
||||
else{
|
||||
bitrateParent?.findViewById<TextView>(R.id.bitrate_value)?.text = format.tbr
|
||||
bitrateParent?.setOnClickListener(clicker)
|
||||
bitrateParent?.setOnLongClickListener(longClicker)
|
||||
}
|
||||
|
||||
|
||||
|
||||
bottomSheet.show()
|
||||
|
|
@ -1139,6 +1185,7 @@ object UiUtil {
|
|||
cutClicked: (VideoCutListener) -> Unit,
|
||||
filenameTemplateSet: (String) -> Unit,
|
||||
saveSubtitlesClicked: (Boolean) -> Unit,
|
||||
saveAutoSubtitlesClicked: (Boolean) -> Unit,
|
||||
subtitleLanguagesSet: (String) -> Unit,
|
||||
removeAudioClicked: (Boolean) -> Unit,
|
||||
extraCommandsClicked: () -> Unit,
|
||||
|
|
@ -1148,6 +1195,7 @@ object UiUtil {
|
|||
|
||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
||||
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
|
||||
val saveAutoSubtitles = view.findViewById<Chip>(R.id.save_auto_subtitles)
|
||||
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
|
||||
|
||||
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
|
||||
|
|
@ -1173,6 +1221,8 @@ object UiUtil {
|
|||
if (splitByChapters.isChecked){
|
||||
items.forEach { it.videoPreferences.addChapters = false }
|
||||
addChapters.isChecked = false
|
||||
addChapters.isEnabled = false
|
||||
addChaptersClicked(false)
|
||||
}
|
||||
splitByChapters.setOnClickListener {
|
||||
if (splitByChapters.isChecked){
|
||||
|
|
@ -1230,7 +1280,7 @@ object UiUtil {
|
|||
}
|
||||
|
||||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if (items.size > 1) cut.isVisible = false
|
||||
if (items.size > 1 || items.first().url.isEmpty()) cut.isVisible = false
|
||||
else{
|
||||
if(items[0].duration.isNotEmpty()){
|
||||
val downloadItem = items[0]
|
||||
|
|
@ -1306,6 +1356,10 @@ object UiUtil {
|
|||
saveSubtitlesClicked(saveSubtitles.isChecked)
|
||||
}
|
||||
|
||||
saveAutoSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||
saveAutoSubtitlesClicked(saveAutoSubtitles.isChecked)
|
||||
}
|
||||
|
||||
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
||||
subtitleLanguages.setOnClickListener {
|
||||
val currentSubtitleLang = if (items.size == 1 || items.all { it.videoPreferences.subsLanguages == items[0].videoPreferences.subsLanguages }){
|
||||
|
|
@ -1425,7 +1479,7 @@ object UiUtil {
|
|||
}
|
||||
|
||||
val cut = view.findViewById<Chip>(R.id.cut)
|
||||
if (items.size > 1) cut.isVisible = false
|
||||
if (items.size > 1 || items.first().url.isEmpty()) cut.isVisible = false
|
||||
else{
|
||||
val downloadItem = items[0]
|
||||
if (downloadItem.duration.isNotEmpty()){
|
||||
|
|
@ -1555,136 +1609,163 @@ object UiUtil {
|
|||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun handleDownloadsResponse(context: Activity, lifecycleScope: CoroutineScope, supportFragmentManager: FragmentManager, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){
|
||||
val downloadAnywayAction = it.actions?.first { it.second == DownloadViewModel.DownloadsAction.DOWNLOAD_ANYWAY}
|
||||
if (downloadAnywayAction != null){
|
||||
if (downloadAnywayAction.third == null) return
|
||||
val downloads =downloadAnywayAction.third!!.toMutableList()
|
||||
val ids = downloadAnywayAction.third!!.map { it.id }
|
||||
val titles = downloadAnywayAction.third!!.map { it.title }
|
||||
fun handleExistingDownloadsResponse(context: Activity, lifecycleScope: CoroutineScope, supportFragmentManager: FragmentManager, it: DownloadViewModel.AlreadyExistsUIState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){
|
||||
if (it.downloadItems.isEmpty() && it.historyItems.isEmpty()) return
|
||||
|
||||
val title = context.getString(R.string.download_already_exists)
|
||||
val message = context.getString(R.string.download_already_exists_summary)
|
||||
|
||||
val title = context.getString(it.errorMessage!!.first)
|
||||
val message = context.getString(it.errorMessage!!.second)
|
||||
val sheet = BottomSheetDialog(context)
|
||||
sheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
sheet.setContentView(R.layout.download_multiple_bottom_sheet)
|
||||
sheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
||||
text = title
|
||||
textSize = 20f
|
||||
}
|
||||
sheet.findViewById<TextView>(R.id.bottom_sheet_subtitle)?.text = message
|
||||
|
||||
val errDialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
var adapter: AlreadyExistsAdapter? = null
|
||||
val list = mutableListOf<Pair<DownloadItem, Long?>>()
|
||||
val alreadyExistsClickListener = object: AlreadyExistsAdapter.OnItemClickListener {
|
||||
|
||||
var dialog: Dialog? = null
|
||||
|
||||
val linearLayout = context.layoutInflater.inflate(R.layout.already_exists_card, null) as LinearLayout
|
||||
ids.forEachIndexed { index, id ->
|
||||
val alreadyExistsItem = context.layoutInflater.inflate(R.layout.already_exists_item, null)
|
||||
alreadyExistsItem.tag = id.toString()
|
||||
val ttle = alreadyExistsItem.findViewById<Button>(R.id.already_exists_title)
|
||||
ttle.text = "${index + 1}. ${titles[index]}"
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val editBtn = alreadyExistsItem.findViewById<Button>(R.id.already_exists_edit)
|
||||
var downloadItem: DownloadItem = downloadViewModel.getItemByID(id)
|
||||
val historyItem: HistoryItem? = downloadViewModel.getHistoryItemById(id)
|
||||
|
||||
if (historyItem != null){
|
||||
val idx = downloads.indexOfFirst { it.id == historyItem.id }
|
||||
downloadItem = downloadViewModel.createDownloadItemFromHistory(historyItem)
|
||||
downloadItem.id = historyItem.id
|
||||
downloads[idx] = downloadItem
|
||||
override fun onEditItem(downloadItem: DownloadItem, position: Int) {
|
||||
val resultItem = downloadViewModel.createResultItemFromDownload(downloadItem)
|
||||
val onItemUpdated = object: ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
|
||||
override fun onDownloadItemUpdate(
|
||||
resultItemID: Long,
|
||||
item: DownloadItem
|
||||
) {
|
||||
val currentIndex = list.indexOfFirst { it.first.id == item.id }
|
||||
val current = list[currentIndex]
|
||||
list[currentIndex] = Pair(item, current.second)
|
||||
adapter?.submitList(list)
|
||||
adapter?.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem, onItemUpdated)
|
||||
bottomSheet.show(supportFragmentManager, "configureDownloadSingleSheet")
|
||||
}
|
||||
|
||||
override fun onDeleteItem(downloadItem: DownloadItem, position: Int, historyID: Long?) {
|
||||
showGenericDeleteDialog(context, downloadItem.title){
|
||||
if (historyID == null){
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.deleteDownload(downloadItem.id)
|
||||
}
|
||||
}
|
||||
list.removeAll { it.first.id == downloadItem.id }
|
||||
if (list.isEmpty()){
|
||||
sheet.dismiss()
|
||||
}
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShowHistoryItem(id: Long) {
|
||||
lifecycleScope.launch {
|
||||
val historyItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getHistoryItemById(id)
|
||||
}
|
||||
showHistoryItemDetailsCard(historyItem, context, isPresent = true,
|
||||
removeItem = { item, deleteFile ->
|
||||
historyViewModel.delete(item, deleteFile)
|
||||
},
|
||||
redownloadItem = { },
|
||||
redownloadShowDownloadCard = {}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
adapter = AlreadyExistsAdapter(alreadyExistsClickListener, context)
|
||||
sheet.findViewById<RecyclerView>(R.id.downloadMultipleRecyclerview)?.apply {
|
||||
layoutManager = GridLayoutManager(context, 1)
|
||||
this.adapter = adapter
|
||||
enableFastScroll()
|
||||
setPadding(0,20,0,0)
|
||||
}
|
||||
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
it.downloadItems.forEach {
|
||||
val downloadItem = downloadViewModel.getItemByID(it)
|
||||
list.add(Pair(downloadItem, null))
|
||||
adapter.submitList(list)
|
||||
}
|
||||
|
||||
it.historyItems.forEach {
|
||||
val historyItem = downloadViewModel.getHistoryItemById(it) ?: return@forEach
|
||||
val downloadItem = downloadViewModel.createDownloadItemFromHistory(historyItem)
|
||||
downloadItem.id = it
|
||||
list.add(Pair(downloadItem, historyItem.id))
|
||||
adapter.submitList(list)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sheet.findViewById<Button>(R.id.bottomsheet_download_button)?.apply {
|
||||
setOnClickListener {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.deleteProcessing()
|
||||
val items = list.map { it.first }
|
||||
items.forEach { it.id = 0 }
|
||||
downloadViewModel.queueDownloads(items, true)
|
||||
withContext(Dispatchers.Main){
|
||||
editBtn.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
editBtn.setOnClickListener {
|
||||
val resultItem = downloadViewModel.createResultItemFromDownload(downloadItem)
|
||||
val onItemUpdated = object: ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
|
||||
override fun onDownloadItemUpdate(
|
||||
resultItemID: Long,
|
||||
item: DownloadItem
|
||||
) {
|
||||
lifecycleScope.launch {
|
||||
val idx = downloads.indexOfFirst { it.id == item.id }
|
||||
downloads[idx] = item
|
||||
withContext(Dispatchers.Main){
|
||||
ttle.text = "${index + 1}. ${item.title}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem, onItemUpdated)
|
||||
bottomSheet.show(supportFragmentManager, "configureDownloadSingleSheet")
|
||||
}
|
||||
|
||||
if (historyItem != null){
|
||||
ttle.setOnClickListener {
|
||||
showHistoryItemDetailsCard(historyItem, context, isPresent = true,
|
||||
removeItem = { item, deleteFile ->
|
||||
historyViewModel.delete(item, deleteFile)
|
||||
},
|
||||
redownloadItem = { },
|
||||
redownloadShowDownloadCard = {}
|
||||
)
|
||||
}
|
||||
sheet.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
ttle.setOnLongClickListener {
|
||||
showGenericDeleteDialog(context, ttle.text.toString(), accepted = {
|
||||
linearLayout.removeView(alreadyExistsItem)
|
||||
if (linearLayout.childCount == 0){
|
||||
dialog?.dismiss()
|
||||
}
|
||||
})
|
||||
true
|
||||
}
|
||||
|
||||
linearLayout.addView(alreadyExistsItem)
|
||||
}
|
||||
}
|
||||
|
||||
errDialog.setView(linearLayout)
|
||||
|
||||
errDialog.setPositiveButton(downloadAnywayAction.first) { d:DialogInterface?, _:Int ->
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
linearLayout.children.forEach {view ->
|
||||
val downloadItem = downloads.first { it.id == (view.tag as String).toLong()}
|
||||
downloadItem.id = 0
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem), true)
|
||||
}
|
||||
}
|
||||
d?.dismiss()
|
||||
}
|
||||
|
||||
errDialog.setNegativeButton(R.string.schedule) { d:DialogInterface?, _:Int ->
|
||||
sheet.findViewById<Button>(R.id.bottomsheet_schedule_button)?.apply {
|
||||
setOnClickListener {
|
||||
showDatePicker(supportFragmentManager) { calendar ->
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val items = mutableListOf<DownloadItem>()
|
||||
linearLayout.children.forEach {view ->
|
||||
val downloadItem = downloads.first { it.id == (view.tag as String).toLong()}
|
||||
downloadItem.downloadStartTime = calendar.timeInMillis
|
||||
downloadItem.id = 0
|
||||
items.add(downloadItem)
|
||||
downloadViewModel.deleteProcessing()
|
||||
val items = list.map { it.first }
|
||||
items.forEach {
|
||||
it.id = 0
|
||||
it.downloadStartTime = calendar.timeInMillis
|
||||
}
|
||||
|
||||
|
||||
runBlocking {
|
||||
val chunks = items.chunked(10)
|
||||
for (c in chunks) {
|
||||
downloadViewModel.queueDownloads(c, true)
|
||||
}
|
||||
downloadViewModel.queueDownloads(items, true)
|
||||
val first = items.first()
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
||||
withContext(Dispatchers.Main){
|
||||
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
val date = SimpleDateFormat(
|
||||
DateFormat.getBestDateTimePattern(
|
||||
Locale.getDefault(),
|
||||
"ddMMMyyyy - HHmm"
|
||||
), Locale.getDefault()
|
||||
).format(first.downloadStartTime)
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.download_rescheduled_to) + " " + date,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
d?.dismiss()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
sheet.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog = errDialog.show()
|
||||
}
|
||||
|
||||
sheet.setOnDismissListener {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
downloadViewModel.deleteProcessing()
|
||||
}
|
||||
}
|
||||
|
||||
sheet.findViewById<BottomAppBar>(R.id.bottomAppBar)?.isVisible = false
|
||||
sheet.show()
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1903,14 +1984,14 @@ object UiUtil {
|
|||
|
||||
builder.setView(view)
|
||||
builder.setPositiveButton(
|
||||
context.getString(R.string.dismiss)
|
||||
context.getString(android.R.string.copy)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
copyToClipboard(txt, context)
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
context.getString(android.R.string.cancel)
|
||||
context.getString(R.string.dismiss)
|
||||
) { _: DialogInterface?, _: Int -> }
|
||||
|
||||
val dialog = builder.create()
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import android.app.PendingIntent
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.AlarmStopReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers.AlarmStartReceiver
|
||||
import com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers.AlarmStopReceiver
|
||||
import java.util.Calendar
|
||||
|
||||
class AlarmScheduler(private val context: Context) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import com.deniscerri.ytdlnis.database.models.LogItem
|
|||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
|
|
@ -228,7 +229,7 @@ class DownloadWorker(
|
|||
val file = File(this)
|
||||
var duration = downloadItem.duration
|
||||
val d = file.getMediaDuration(context)
|
||||
if (d > 0) duration = infoUtil.formatIntegerDuration(d, Locale.US)
|
||||
if (d > 0) duration = d.toStringDuration(Locale.US)
|
||||
|
||||
downloadItem.format.filesize = file.length()
|
||||
downloadItem.format.container = file.extension
|
||||
|
|
|
|||
|
|
@ -0,0 +1,255 @@
|
|||
package com.deniscerri.ytdlnis.work
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||
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 com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Calendar
|
||||
|
||||
|
||||
class ObserveSourceWorker(
|
||||
private val context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParams) {
|
||||
override suspend fun doWork(): Result {
|
||||
val notificationUtil = NotificationUtil(App.instance)
|
||||
val sourceID = inputData.getLong("id", 0)
|
||||
if (sourceID == 0L) return Result.failure()
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
|
||||
val historyRepo = HistoryRepository(dbManager.historyDao)
|
||||
val downloadRepo = DownloadRepository(dbManager.downloadDao)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val infoUtil = InfoUtil(context)
|
||||
|
||||
val item = repo.getByID(sourceID)
|
||||
if (item.status == ObserveSourcesRepository.SourceStatus.STOPPED){
|
||||
DownloadUtil.cancelObservationTaskByID(context, item.id)
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val workerID = System.currentTimeMillis().toInt()
|
||||
val notification = notificationUtil.createObserveSourcesNotification(item.name)
|
||||
val foregroundInfo = ForegroundInfo(workerID, notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
item.runCount = item.runCount + 1
|
||||
|
||||
var res = runCatching { infoUtil.getFromYTDL(item.url).toList() }.getOrElse { listOf() }
|
||||
res = if (!item.retryMissingDownloads){
|
||||
res.filter { result ->
|
||||
//all items that are not present in history
|
||||
!historyRepo.getAllByURL(result!!.url).filter { hi ->
|
||||
hi.downloadPath.any { path -> FileUtil.exists(path) }
|
||||
}.map { it.url }.contains(result.url)
|
||||
}
|
||||
}else{
|
||||
//all items that are not already processed
|
||||
res.filter { !item.alreadyProcessedLinks.contains(it!!.url) }
|
||||
}
|
||||
|
||||
|
||||
val items = mutableListOf<DownloadItem>()
|
||||
|
||||
res.forEach {
|
||||
val string = Gson().toJson(item.downloadItemTemplate, DownloadItem::class.java)
|
||||
val downloadItem = Gson().fromJson(string, DownloadItem::class.java)
|
||||
downloadItem.url = it!!.url
|
||||
downloadItem.id = 0L
|
||||
items.add(downloadItem)
|
||||
}
|
||||
|
||||
if (items.isNotEmpty()){
|
||||
|
||||
//QUEUE DOWNLOADS
|
||||
//COPY OF QUEUE DOWNLOADS IN DOWNLOAD VIEW MODEL. NEEDS TO BE UPDATED IF THAT IS UPDATED
|
||||
val context = App.instance
|
||||
val alarmScheduler = AlarmScheduler(context)
|
||||
val activeAndQueuedDownloads = downloadRepo.getActiveAndQueuedDownloads()
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
val existing = mutableListOf<DownloadItem>()
|
||||
|
||||
//if scheduler is on
|
||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||
if (useScheduler && !alarmScheduler.isDuringTheScheduledTime()){
|
||||
alarmScheduler.schedule()
|
||||
}
|
||||
|
||||
if (items.any { it.playlistTitle.isEmpty() } && items.size > 1){
|
||||
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
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) {
|
||||
it.id = existingDownload.id
|
||||
existing.add(it)
|
||||
}else{
|
||||
//check if downloaded and file exists
|
||||
val history = withContext(Dispatchers.IO){
|
||||
historyRepo.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||
}
|
||||
|
||||
val existingHistory = history.firstOrNull {
|
||||
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
||||
}
|
||||
|
||||
if (existingHistory != null){
|
||||
it.id = existingHistory.id
|
||||
existing.add(it)
|
||||
}else{
|
||||
if (it.id == 0L){
|
||||
it.id = downloadRepo.insert(it)
|
||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||
downloadRepo.update(it)
|
||||
}
|
||||
|
||||
queuedItems.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
|
||||
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, downloadRepo, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
}
|
||||
}else{
|
||||
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
||||
try{
|
||||
updateDownloadItem(it, infoUtil, downloadRepo, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.alreadyProcessedLinks.addAll(items.map { it.url })
|
||||
}
|
||||
|
||||
|
||||
if (item.runCount > item.endsAfterCount && item.endsAfterCount > 0){
|
||||
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
|
||||
withContext(Dispatchers.IO){
|
||||
repo.update(item)
|
||||
}
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (item.endsDate >= currentTime || item.endsAfterCount == item.runCount){
|
||||
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
|
||||
withContext(Dispatchers.IO){
|
||||
repo.update(item)
|
||||
}
|
||||
DownloadUtil.cancelObservationTaskByID(context, item.id)
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
scheduleForNextTime(item)
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun scheduleForNextTime(item: ObserveSourcesItem){
|
||||
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
||||
val intent = Intent(context, ObserveAlarmReceiver::class.java)
|
||||
intent.putExtra("id", item.id)
|
||||
|
||||
val c = Calendar.getInstance()
|
||||
val hourMin = Calendar.getInstance()
|
||||
hourMin.timeInMillis = item.everyTime
|
||||
c.set(Calendar.HOUR_OF_DAY, hourMin.get(Calendar.HOUR_OF_DAY))
|
||||
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||
|
||||
when(item.everyCategory){
|
||||
ObserveSourcesRepository.EveryCategory.DAY -> {
|
||||
c.add(Calendar.DAY_OF_MONTH, item.everyNr)
|
||||
}
|
||||
ObserveSourcesRepository.EveryCategory.WEEK -> {
|
||||
if(item.everyWeekDay.isEmpty()){
|
||||
c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}else{
|
||||
val weekDayID = c.get(Calendar.DAY_OF_WEEK).toString()
|
||||
val followingWeekDay = (item.everyWeekDay.firstOrNull { it.toInt() > weekDayID.toInt() } ?: item.everyWeekDay.minBy { it.toInt() }).toInt()
|
||||
c.set(Calendar.DAY_OF_WEEK, followingWeekDay)
|
||||
if (item.everyNr > 1){
|
||||
c.add(Calendar.DAY_OF_MONTH, 7 * item.everyNr)
|
||||
}
|
||||
}
|
||||
}
|
||||
ObserveSourcesRepository.EveryCategory.MONTH -> {
|
||||
c.add(Calendar.MONTH, item.everyNr)
|
||||
c.set(Calendar.DAY_OF_MONTH, item.everyMonthDay)
|
||||
}
|
||||
}
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC,
|
||||
c.timeInMillis,
|
||||
PendingIntent.getBroadcast(context, item.id.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateDownloadItem(
|
||||
downloadItem: DownloadItem,
|
||||
infoUtil: InfoUtil,
|
||||
repository: DownloadRepository,
|
||||
resultDao: ResultDao
|
||||
) : Boolean {
|
||||
var wasQuickDownloaded = false
|
||||
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
||||
runCatching {
|
||||
val info = infoUtil.getMissingInfo(downloadItem.url)
|
||||
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
||||
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
|
||||
downloadItem.duration = info?.duration.toString()
|
||||
downloadItem.website = info?.website.toString()
|
||||
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
|
||||
runBlocking {
|
||||
wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||
repository.updateWithoutUpsert(downloadItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return wasQuickDownloaded
|
||||
}
|
||||
|
||||
}
|
||||
5
app/src/main/res/drawable/baseline_archive_24.xml
Normal file
5
app/src/main/res/drawable/baseline_archive_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_calendar_month_24.xml
Normal file
5
app/src/main/res/drawable/baseline_calendar_month_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M19,4h-1V2h-2v2H8V2H6v2H5C3.89,4 3.01,4.9 3.01,6L3,20c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6C21,4.9 20.1,4 19,4zM19,20H5V10h14V20zM9,14H7v-2h2V14zM13,14h-2v-2h2V14zM17,14h-2v-2h2V14zM9,18H7v-2h2V18zM13,18h-2v-2h2V18zM17,18h-2v-2h2V18z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_import_export_24.xml
Normal file
5
app/src/main/res/drawable/baseline_import_export_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_loop_24.xml
Normal file
5
app/src/main/res/drawable/baseline_loop_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/circle.xml
Normal file
3
app/src/main/res/drawable/circle.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="50dp" />
|
||||
</shape>
|
||||
35
app/src/main/res/drawable/thumb.xml
Normal file
35
app/src/main/res/drawable/thumb.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 Google LLC
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
~ Any changes to the tint here needs to be mirrored in the call to
|
||||
~ Utils.getGradientDrawableWithTintAttr() as well.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle"
|
||||
android:tint="?colorControlActivated">
|
||||
|
||||
<corners android:radius="8dp" />
|
||||
|
||||
<padding android:top="4dp" android:bottom="4dp" />
|
||||
|
||||
<size android:width="8dp" android:height="52dp" />
|
||||
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
||||
|
|
@ -232,18 +232,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_gravity="bottom|end">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/copied_url_fab"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
android:src="@drawable/ic_clipboard"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -253,6 +254,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
|
|
|
|||
|
|
@ -243,18 +243,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_gravity="bottom|end">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/copied_url_fab"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
android:src="@drawable/ic_clipboard"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -264,6 +265,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
|
|
|
|||
|
|
@ -232,18 +232,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_gravity="bottom|end">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/copied_url_fab"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
android:src="@drawable/ic_clipboard"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -253,6 +254,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
|
|
|
|||
|
|
@ -262,18 +262,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_gravity="bottom|end">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/copied_url_fab"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
android:src="@drawable/ic_clipboard"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -283,6 +284,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
|
|
|
|||
|
|
@ -174,6 +174,14 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/save_auto_subtitles"
|
||||
style="@style/Widget.Material3.Chip.Filter.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/save_auto_subs"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/subtitle_languages"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:id="@+id/existing_list"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView android:id="@+id/download_recyclerview"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/playlist_card_constraintLayout"
|
||||
android:id="@+id/already_exists_constraint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:checkable="true"
|
||||
|
|
@ -23,22 +23,42 @@
|
|||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/already_exists_edit"
|
||||
app:layout_constraintEnd_toStartOf="@+id/already_exists_buttons"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_edit"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
<LinearLayout
|
||||
android:id="@+id/already_exists_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_edit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/already_exists_title" />
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_edit"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_edit"
|
||||
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/already_exists_title"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/already_exists_delete"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/baseline_delete_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/already_exists_title" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
|
|
@ -43,20 +43,24 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/download"
|
||||
android:textSize="25sp"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/configure_download"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,29 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorSecondary"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:maxLength="17"
|
||||
android:maxLines="1"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
|
|
|
|||
|
|
@ -298,6 +298,41 @@
|
|||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/bitrate_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:weightSum="100"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bitrate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
android:text="@string/bitrate"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bitrate_value"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bitrate_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:gravity="end"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@
|
|||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
android:maxLength="10"
|
||||
app:drawableStartCompat="@drawable/ic_music"
|
||||
android:textCursorDrawable="@string/audio_format"
|
||||
app:drawableTint="@color/white"
|
||||
|
|
@ -178,6 +179,7 @@
|
|||
android:clickable="false"
|
||||
android:gravity="bottom|end"
|
||||
android:maxLength="10"
|
||||
android:ellipsize="end"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@
|
|||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:icon="@drawable/baseline_align_horizontal_left_24"
|
||||
app:layout_constraintBottom_toBottomOf="@id/format_refresh"
|
||||
app:layout_constraintEnd_toStartOf="@+id/format_refresh"
|
||||
|
|
@ -77,6 +76,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:text="@string/update_formats"
|
||||
android:layout_marginStart="10dp"
|
||||
app:icon="@drawable/ic_refresh"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
@ -88,6 +88,7 @@
|
|||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:autoLink="all"
|
||||
android:text="@string/ok"
|
||||
app:icon="@drawable/ic_check"
|
||||
|
|
|
|||
|
|
@ -57,6 +57,16 @@
|
|||
|
||||
<View style="@style/Divider.Vertical"/>
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/not_deleted_chip"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/not_deleted"/>
|
||||
|
||||
<View android:id="@+id/notdeleted_divider" style="@style/Divider.Vertical"/>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="wrap_content"
|
||||
app:selectionRequired="false"
|
||||
|
|
|
|||
|
|
@ -161,18 +161,19 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="10dp"
|
||||
android:layout_gravity="bottom|end">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/copied_url_fab"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:text="@string/link_you_copied"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:contentDescription="@string/link_you_copied"
|
||||
android:src="@drawable/ic_clipboard"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_clipboard"
|
||||
app:useCompatPadding="true" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -181,8 +182,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/download_all"
|
||||
app:icon="@drawable/ic_down" />
|
||||
|
||||
|
|
@ -193,8 +194,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_margin="16dp"
|
||||
app:icon="@drawable/ic_down"
|
||||
android:text="@string/download"
|
||||
app:srcCompat="@drawable/ic_music"/>
|
||||
|
|
|
|||
|
|
@ -100,6 +100,21 @@
|
|||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/baseline_cookie_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/observe_sources"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="35dp"
|
||||
android:textSize="16sp"
|
||||
android:paddingVertical="15dp"
|
||||
android:text="@string/observe_sources"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingHorizontal="15dp"
|
||||
app:drawableStartCompat="@drawable/baseline_calendar_month_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/terminate"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
63
app/src/main/res/layout/fragment_observe_sources.xml
Normal file
63
app/src/main/res/layout/fragment_observe_sources.xml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:liftOnScroll="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:elevation="0dp"
|
||||
app:title="@string/observe_sources"
|
||||
android:layout_width="match_parent"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
app:menu="@menu/observe_sources_menu"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/newSource"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/new_source"
|
||||
app:chipIcon="@drawable/ic_plus" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_below="@+id/newSource"
|
||||
android:id="@+id/source_recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
>
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
<include layout="@layout/no_results"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
476
app/src/main/res/layout/observe_sources_bottom_sheet.xml
Normal file
476
app/src/main/res/layout/observe_sources_bottom_sheet.xml
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:scrollbars="none"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/observe_sources"
|
||||
android:textSize="25sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/configure_download"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/okButton"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:autoLink="all"
|
||||
android:text="@string/ok"
|
||||
app:icon="@drawable/ic_check"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/title_textinput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/title"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="text"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/url_textinput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/url"
|
||||
app:endIconMode="custom"
|
||||
app:endIconDrawable="@drawable/ic_clipboard"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="text"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/download_tablayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:tabGravity="start"
|
||||
app:tabMinWidth="0dp"
|
||||
app:tabMode="scrollable">
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/audio" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/video" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/command" />
|
||||
|
||||
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/download_viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:id="@+id/adjust"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingBottom="15dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/every" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/every_textinput"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:hintEnabled="false">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="number"
|
||||
android:textAlignment="center"
|
||||
android:text="1"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="45"
|
||||
android:paddingStart="10dp"
|
||||
android:hint="@string/every"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/everyCat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="none"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/everyMonthDay_textInput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
|
||||
android:layout_width="0dp"
|
||||
android:paddingStart="10dp"
|
||||
android:layout_weight="25"
|
||||
android:hint="@string/day"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/everyMonthDay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="none"
|
||||
app:simpleItems="@array/monthDays"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/weekdays"
|
||||
android:layout_width="match_parent"
|
||||
app:chipSpacingVertical="0dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/m"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/t"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"/>
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/w"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"/>
|
||||
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/th"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/f"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/sa"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/su"
|
||||
style="@style/Widget.Material3.Chip.Suggestion.Elevated"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"/>
|
||||
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/everyday_textInput"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="10dp"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="number"
|
||||
android:textAlignment="center"
|
||||
android:padding="15dp"
|
||||
android:text="1"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/every_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/set_time"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="text"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingBottom="15dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/starts" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/start_time"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/starts"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="text"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/startsMonth_textInput"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_weight="45"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:layout_height="0dp">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/startsMonth"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="15dp"
|
||||
android:inputType="none"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/ends" />
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/never"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:checked="true"
|
||||
android:text="@string/never" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/on"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/on"
|
||||
android:paddingHorizontal="10dp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/on_date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:hintEnabled="false"
|
||||
android:layout_marginStart="15dp"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="text"
|
||||
android:padding="15dp"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/after"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/after"
|
||||
android:paddingHorizontal="10dp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/after_nr"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="number"
|
||||
android:textAlignment="center"
|
||||
android:text="1"
|
||||
android:padding="15dp"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:text="@string/occurrences"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/retry_missing_downloads"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/retry_missing_downloads"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
150
app/src/main/res/layout/observe_sources_item.xml
Normal file
150
app/src/main/res/layout/observe_sources_item.xml
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="H,2:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/observe_sources_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="5dp"
|
||||
app:strokeColor="?attr/colorPrimary"
|
||||
app:checkedIconTint="?attr/colorPrimary"
|
||||
app:cardMaxElevation="12dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:checkable="true"
|
||||
app:strokeWidth="0dp"
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/result_image_view"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/download_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
android:alpha="0.7"
|
||||
android:scaleY="100" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:dividerPadding="5dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/search"
|
||||
app:layout_constraintEnd_toStartOf="@id/check_missing"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:scrollbars="none"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="bottom"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:scrollbars="none"
|
||||
android:shadowColor="@color/black"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="1.5"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/check_missing"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
android:padding="0dp"
|
||||
android:minHeight="0dp"
|
||||
app:icon="@drawable/ic_restore"
|
||||
app:layout_constraintBottom_toTopOf="@+id/search"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/info"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="10dp"
|
||||
app:chipMinTouchTargetSize="0dp"
|
||||
app:chipIcon="@drawable/baseline_loop_24"
|
||||
app:chipIconVisible="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/search"
|
||||
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||
android:layout_width="55dp"
|
||||
android:layout_height="55dp"
|
||||
android:layout_margin="10dp"
|
||||
app:elevation="0dp"
|
||||
android:elevation="0dp"
|
||||
app:borderWidth="0dp"
|
||||
android:src="@drawable/ic_search"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="20dp"
|
||||
android:maxLength="100"
|
||||
android:ellipsize="end"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
|
|
|
|||
|
|
@ -27,4 +27,10 @@
|
|||
android:icon="@drawable/ic_delete_all"
|
||||
android:title="@string/clear_saved"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/copy_urls"
|
||||
android:icon="@drawable/ic_delete_all"
|
||||
android:title="@string/copy_urls"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
@ -29,6 +29,10 @@
|
|||
android:icon="@drawable/baseline_cookie_24"
|
||||
android:checkable="true"
|
||||
android:id="@+id/cookiesFragment"/>
|
||||
<item android:title="@string/observe_sources"
|
||||
android:icon="@drawable/baseline_calendar_month_24"
|
||||
android:checkable="true"
|
||||
android:id="@+id/observeSourcesFragment"/>
|
||||
<item android:title="@string/kill_app"
|
||||
android:checkable="true"
|
||||
android:icon="@drawable/ic_power"
|
||||
|
|
|
|||
9
app/src/main/res/menu/observe_sources_menu.xml
Normal file
9
app/src/main/res/menu/observe_sources_menu.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/delete_sources"
|
||||
android:title="@string/delete_all_sources"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
|
|
@ -69,6 +69,9 @@
|
|||
<action
|
||||
android:id="@+id/action_moreFragment_to_terminalActivity"
|
||||
app:destination="@id/terminalActivity" />
|
||||
<action
|
||||
android:id="@+id/action_moreFragment_to_observeSourcesFragment"
|
||||
app:destination="@id/observeSourcesFragment" />
|
||||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/terminalActivity"
|
||||
|
|
@ -163,9 +166,29 @@
|
|||
<dialog
|
||||
android:id="@+id/downloadMultipleBottomSheetDialog2"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog"
|
||||
android:label="DownloadMultipleBottomSheetDialog" />
|
||||
android:label="DownloadMultipleBottomSheetDialog" >
|
||||
<action
|
||||
android:id="@+id/action_downloadMultipleBottomSheetDialog2_to_configureDownloadBottomSheetDialog"
|
||||
app:destination="@id/configureDownloadBottomSheetDialog" />
|
||||
</dialog>
|
||||
<dialog
|
||||
android:id="@+id/cutVideoBottomSheetDialog"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.CutVideoBottomSheetDialog"
|
||||
android:label="CutVideoBottomSheetDialog" />
|
||||
<dialog
|
||||
android:id="@+id/configureDownloadBottomSheetDialog"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog"
|
||||
android:label="ConfigureDownloadBottomSheetDialog" />
|
||||
<fragment
|
||||
android:id="@+id/observeSourcesFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.ObserveSourcesFragment"
|
||||
android:label="ObserveSourcesFragment" >
|
||||
<action
|
||||
android:id="@+id/action_observeSourcesFragment_to_observeSourcesBottomSheetDialog"
|
||||
app:destination="@id/observeSourcesBottomSheetDialog" />
|
||||
</fragment>
|
||||
<dialog
|
||||
android:id="@+id/observeSourcesBottomSheetDialog"
|
||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.ObserveSourcesBottomSheetDialog"
|
||||
android:label="ObserveSourcesBottomSheetDialog" />
|
||||
</navigation>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<string name="sponsorblock_intro">ئینترۆ</string>
|
||||
<string name="sponsorblock_fillers">فیلتەرەکان</string>
|
||||
<string name="sponsorblock_reminders">بیرخەرەوەکانی بەشداربوون</string>
|
||||
<string name="cancel_download">پاشگەزبوونەوە</string>
|
||||
<string name="cancel_task">پاشگەزبوونەوە</string>
|
||||
<string name="logs">تۆمارەکان</string>
|
||||
<string name="audio_quality">کوالێتی دەنگ</string>
|
||||
<string name="audio_format">فۆرماتی دەنگ</string>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue