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,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 14,
|
"version": 14,
|
||||||
"identityHash": "5cea521e1a26473b2ec5298791f10ac4",
|
"identityHash": "5be3b7ae26c7716f2192470a3a9249a8",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "results",
|
"tableName": "results",
|
||||||
|
|
@ -587,12 +587,131 @@
|
||||||
},
|
},
|
||||||
"indices": [],
|
"indices": [],
|
||||||
"foreignKeys": []
|
"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": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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.CancelDownloadNotificationReceiver" />
|
||||||
<receiver android:name=".receiver.CancelWorkReceiver" />
|
<receiver android:name=".receiver.CancelWorkReceiver" />
|
||||||
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
||||||
<receiver android:name=".receiver.AlarmStartReceiver" />
|
<receiver android:name=".receiver.downloadAlarmReceivers.AlarmStartReceiver" />
|
||||||
<receiver android:name=".receiver.AlarmStopReceiver" />
|
<receiver android:name=".receiver.downloadAlarmReceivers.AlarmStopReceiver" />
|
||||||
|
<receiver android:name=".receiver.ObserveAlarmReceiver" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".receiver.ResumeActivity"
|
android:name=".receiver.ResumeActivity"
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ class MainActivity : BaseActivity() {
|
||||||
if (navigationView is NavigationView){
|
if (navigationView is NavigationView){
|
||||||
(navigationView as NavigationView).setupWithNavController(navController)
|
(navigationView as NavigationView).setupWithNavController(navController)
|
||||||
//terminate button
|
//terminate button
|
||||||
(navigationView as NavigationView).menu.getItem(7).setOnMenuItemClickListener {
|
(navigationView as NavigationView).menu.getItem(8).setOnMenuItemClickListener {
|
||||||
if (preferences.getBoolean("ask_terminate_app", true)){
|
if (preferences.getBoolean("ask_terminate_app", true)){
|
||||||
var doNotShowAgain = false
|
var doNotShowAgain = false
|
||||||
val terminateDialog = MaterialAlertDialogBuilder(this)
|
val terminateDialog = MaterialAlertDialogBuilder(this)
|
||||||
|
|
@ -204,7 +204,7 @@ class MainActivity : BaseActivity() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
//settings button
|
//settings button
|
||||||
(navigationView as NavigationView).menu.getItem(8).setOnMenuItemClickListener {
|
(navigationView as NavigationView).menu.getItem(9).setOnMenuItemClickListener {
|
||||||
val intent = Intent(context, SettingsActivity::class.java)
|
val intent = Intent(context, SettingsActivity::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
true
|
true
|
||||||
|
|
@ -244,7 +244,7 @@ class MainActivity : BaseActivity() {
|
||||||
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
|
if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
|
||||||
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
|
if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
|
||||||
val version = YoutubeDL.getInstance().version(context)
|
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}]",
|
this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]",
|
||||||
Snackbar.LENGTH_LONG).show()
|
Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
@ -254,6 +254,8 @@ class MainActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
override fun onSaveInstanceState(savedInstanceState: Bundle) {
|
||||||
super.onSaveInstanceState(savedInstanceState)
|
super.onSaveInstanceState(savedInstanceState)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
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.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
|
@ -57,4 +58,19 @@ class Converters {
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
return gson.toJson(list)
|
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,
|
TemplateShortcut::class,
|
||||||
CookieItem::class,
|
CookieItem::class,
|
||||||
LogItem::class,
|
LogItem::class,
|
||||||
TerminalItem::class
|
TerminalItem::class,
|
||||||
|
ObserveSourcesItem::class
|
||||||
],
|
],
|
||||||
version = 14,
|
version = 14,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
|
|
@ -37,7 +38,7 @@ import com.deniscerri.ytdlnis.database.models.*
|
||||||
AutoMigration (from = 10, to = 11),
|
AutoMigration (from = 10, to = 11),
|
||||||
AutoMigration (from = 11, to = 12),
|
AutoMigration (from = 11, to = 12),
|
||||||
AutoMigration (from = 12, to = 13),
|
AutoMigration (from = 12, to = 13),
|
||||||
//AutoMigration (from = 13, to = 14), MANUALLY HANDLED
|
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
abstract class DBManager : RoomDatabase(){
|
abstract class DBManager : RoomDatabase(){
|
||||||
|
|
@ -49,6 +50,7 @@ abstract class DBManager : RoomDatabase(){
|
||||||
abstract val cookieDao: CookieDao
|
abstract val cookieDao: CookieDao
|
||||||
abstract val logDao: LogDao
|
abstract val logDao: LogDao
|
||||||
abstract val terminalDao: TerminalDao
|
abstract val terminalDao: TerminalDao
|
||||||
|
abstract val observeSourcesDao: ObserveSourcesDao
|
||||||
|
|
||||||
enum class SORTING{
|
enum class SORTING{
|
||||||
DESC, ASC
|
DESC, ASC
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,7 @@
|
||||||
package com.deniscerri.ytdlnis.database
|
package com.deniscerri.ytdlnis.database
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
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.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 {
|
object Migrations {
|
||||||
|
|
@ -48,7 +16,10 @@ object Migrations {
|
||||||
val path = cursor.getString(cursor.getColumnIndex("downloadPath"))
|
val path = cursor.getString(cursor.getColumnIndex("downloadPath"))
|
||||||
val newPath = "[\"${path.replace("\"", "\\\"").replace("'", "''")}\"]"
|
val newPath = "[\"${path.replace("\"", "\\\"").replace("'", "''")}\"]"
|
||||||
database.execSQL("UPDATE history SET downloadPath = '${newPath}' WHERE id = $id")
|
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")
|
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
||||||
fun getAllCookies() : List<CookieItem>
|
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")
|
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
||||||
fun getAllCookiesFlow() : Flow<List<CookieItem>>
|
fun getAllCookiesFlow() : Flow<List<CookieItem>>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
@Query("SELECT * FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
||||||
fun getActiveDownloads() : Flow<List<DownloadItem>>
|
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')")
|
@Query("SELECT COUNT(*) FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
||||||
fun getActiveDownloadsCountFlow() : Flow<Int>
|
fun getActiveDownloadsCountFlow() : Flow<Int>
|
||||||
|
|
||||||
|
|
@ -25,9 +28,29 @@ interface DownloadDao {
|
||||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||||
fun getDownloadsCountByStatus(status : List<String>) : Int
|
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')")
|
@Query("SELECT * FROM downloads WHERE status in('Active','ActivePaused','PausedReQueued')")
|
||||||
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||||
|
fun getProcessingDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||||
fun getActiveDownloadsList() : List<DownloadItem>
|
fun getActiveDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
|
|
@ -126,6 +149,9 @@ interface DownloadDao {
|
||||||
@Query("DELETE FROM downloads WHERE status='Saved'")
|
@Query("DELETE FROM downloads WHERE status='Saved'")
|
||||||
suspend fun deleteSaved()
|
suspend fun deleteSaved()
|
||||||
|
|
||||||
|
@Query("DELETE FROM downloads WHERE status='Processing'")
|
||||||
|
suspend fun deleteProcessing()
|
||||||
|
|
||||||
@Query("DELETE FROM downloads WHERE id in (:list)")
|
@Query("DELETE FROM downloads WHERE id in (:list)")
|
||||||
suspend fun deleteAllWithIDs(list: List<Long>)
|
suspend fun deleteAllWithIDs(list: List<Long>)
|
||||||
|
|
||||||
|
|
@ -174,12 +200,18 @@ interface DownloadDao {
|
||||||
@Query("Select id from downloads where id not in (:list) and status in (:status)")
|
@Query("Select id from downloads where id not in (:list) and status in (:status)")
|
||||||
fun getDownloadIDsNotPresentInList(list: List<Long>, status: List<String>) : List<Long>
|
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)")
|
@Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
|
||||||
fun resetScheduleTimeForItems(list: List<Long>)
|
fun resetScheduleTimeForItems(list: List<Long>)
|
||||||
|
|
||||||
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
||||||
fun reQueueDownloadItems(list: List<Long>)
|
fun reQueueDownloadItems(list: List<Long>)
|
||||||
|
|
||||||
|
@Query("Update downloads SET status='Saved' WHERE status='Processing'")
|
||||||
|
fun updateProcessingtoSavedStatus()
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
fun putAtTopOfTheQueue(id: Long){
|
fun putAtTopOfTheQueue(id: Long){
|
||||||
val downloads = getQueuedDownloadsList()
|
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")
|
@SerializedName(value = "url")
|
||||||
var url: String? = "",
|
var url: String? = "",
|
||||||
@SerializedName(value = "language", alternate = ["audioTrackLocale"])
|
@SerializedName(value = "language", alternate = ["audioTrackLocale"])
|
||||||
val lang: String? = ""
|
val lang: String? = "",
|
||||||
|
@SerializedName(value = "tbr", alternate = ["bitrate"])
|
||||||
|
var tbr: String? = ""
|
||||||
) : Parcelable
|
) : 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 splitByChapters: Boolean = false,
|
||||||
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
var sponsorBlockFilters: ArrayList<String> = arrayListOf(),
|
||||||
var writeSubs: Boolean = false,
|
var writeSubs: Boolean = false,
|
||||||
|
var writeAutoSubs: Boolean = false,
|
||||||
var subsLanguages: String = "en.*,.*-orig",
|
var subsLanguages: String = "en.*,.*-orig",
|
||||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||||
var removeAudio: Boolean = false
|
var removeAudio: Boolean = false
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ class CookieRepository(private val cookieDao: CookieDao) {
|
||||||
return cookieDao.getAllCookies()
|
return cookieDao.getAllCookies()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getByURL(url: String) : CookieItem {
|
||||||
|
return cookieDao.getByURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun insert(item: CookieItem) : Long{
|
suspend fun insert(item: CookieItem) : Long{
|
||||||
if (! cookieDao.checkIfExistsWithSameURL(item.url)){
|
if (! cookieDao.checkIfExistsWithSameURL(item.url)){
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
pagingSourceFactory = {downloadDao.getAllDownloads()}
|
pagingSourceFactory = {downloadDao.getAllDownloads()}
|
||||||
)
|
)
|
||||||
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads().distinctUntilChanged()
|
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads().distinctUntilChanged()
|
||||||
|
val processingDownloads : Flow<List<DownloadItem>> = downloadDao.getProcessingDownloads().distinctUntilChanged()
|
||||||
val queuedDownloads : Pager<Int, DownloadItemSimple> = Pager(
|
val queuedDownloads : Pager<Int, DownloadItemSimple> = Pager(
|
||||||
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
|
||||||
pagingSourceFactory = {downloadDao.getQueuedDownloads()}
|
pagingSourceFactory = {downloadDao.getQueuedDownloads()}
|
||||||
|
|
@ -38,7 +39,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCountFlow()
|
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCountFlow()
|
||||||
|
|
||||||
enum class Status {
|
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 {
|
suspend fun insert(item: DownloadItem) : Long {
|
||||||
|
|
@ -58,7 +59,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
private fun deleteCache(items: List<DownloadItem>) {
|
private fun deleteCache(items: List<DownloadItem>) {
|
||||||
val cacheDir = FileUtil.getCachePath(App.instance)
|
val cacheDir = FileUtil.getCachePath(App.instance)
|
||||||
items.forEach {
|
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()
|
return downloadDao.getActiveAndPausedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getProcessingDownloads() : List<DownloadItem> {
|
||||||
|
return downloadDao.getProcessingDownloadsList()
|
||||||
|
}
|
||||||
|
|
||||||
fun getActiveAndQueuedDownloads() : List<DownloadItem> {
|
fun getActiveAndQueuedDownloads() : List<DownloadItem> {
|
||||||
return downloadDao.getActiveAndQueuedDownloadsList()
|
return downloadDao.getActiveAndQueuedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +129,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.deleteSaved()
|
downloadDao.deleteSaved()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteProcessing(){
|
||||||
|
downloadDao.deleteProcessing()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun deleteAllWithIDs(ids: List<Long>){
|
suspend fun deleteAllWithIDs(ids: List<Long>){
|
||||||
downloadDao.deleteAllWithIDs(ids)
|
downloadDao.deleteAllWithIDs(ids)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ class HistoryRepository(private val historyDao: HistoryDao) {
|
||||||
return historyDao.getAllHistoryByURL(url)
|
return historyDao.getAllHistoryByURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) : List<HistoryItem> {
|
fun getFiltered(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, notDeleted: Boolean) : List<HistoryItem> {
|
||||||
return when(sortType){
|
var filtered = when(sortType){
|
||||||
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, format, site, sort.toString())
|
||||||
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
HistorySortType.TITLE -> historyDao.getHistorySortedByTitle(query, format, site, sort.toString())
|
||||||
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthor(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){
|
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()
|
val items : ArrayList<ResultItem?> = arrayListOf()
|
||||||
do {
|
do {
|
||||||
val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "")
|
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)
|
items.addAll(tmp.videos)
|
||||||
val tmpToken = tmp.nextPageToken
|
val tmpToken = tmp.nextPageToken
|
||||||
if (tmpToken.isEmpty()) break
|
if (tmpToken.isEmpty()) break
|
||||||
|
|
@ -72,10 +76,6 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
nextPageToken = tmpToken
|
nextPageToken = tmpToken
|
||||||
} while (true)
|
} while (true)
|
||||||
itemCount.value = items.size
|
itemCount.value = items.size
|
||||||
val ids = resultDao.insertMultiple(items.toList())
|
|
||||||
ids.forEachIndexed { index, id ->
|
|
||||||
items[index]?.id = id
|
|
||||||
}
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import com.deniscerri.ytdlnis.BuildConfig
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
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.ui.more.WebViewActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -43,6 +44,10 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
return repository.getAll()
|
return repository.getAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getByURL(url: String) : CookieItem {
|
||||||
|
return repository.getByURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun insert(item: CookieItem) : Long {
|
suspend fun insert(item: CookieItem) : Long {
|
||||||
return repository.insert(item)
|
return repository.insert(item)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import android.net.ConnectivityManager
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.animation.core.updateTransition
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
|
|
@ -17,6 +18,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
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.models.VideoPreferences
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
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.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
|
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -57,7 +62,7 @@ import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
|
class DownloadViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
private val dbManager: DBManager
|
private val dbManager: DBManager
|
||||||
val repository : DownloadRepository
|
val repository : DownloadRepository
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
@ -66,6 +71,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
val allDownloads : Flow<PagingData<DownloadItem>>
|
val allDownloads : Flow<PagingData<DownloadItem>>
|
||||||
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
val activeDownloads : Flow<List<DownloadItem>>
|
val activeDownloads : Flow<List<DownloadItem>>
|
||||||
|
val processingDownloads : Flow<List<DownloadItem>>
|
||||||
val activeDownloadsCount : Flow<Int>
|
val activeDownloadsCount : Flow<Int>
|
||||||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
|
|
@ -89,19 +95,14 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
private val dao: DownloadDao
|
private val dao: DownloadDao
|
||||||
private val historyRepository: HistoryRepository
|
private val historyRepository: HistoryRepository
|
||||||
|
|
||||||
data class DownloadsUiState(
|
data class AlreadyExistsUIState(
|
||||||
var errorMessage: Pair<Int, Int>?,
|
var historyItems: MutableList<Long>,
|
||||||
//action title, type, data
|
var downloadItems : MutableList<Long>
|
||||||
var actions: MutableList<Triple<Int, DownloadsAction, List<DownloadItem>?>>?
|
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class DownloadsAction {
|
val alreadyExistsUiState: MutableStateFlow<AlreadyExistsUIState> = MutableStateFlow(AlreadyExistsUIState(
|
||||||
DOWNLOAD_ANYWAY
|
historyItems = mutableListOf(),
|
||||||
}
|
downloadItems = mutableListOf()
|
||||||
|
|
||||||
val uiState: MutableStateFlow<DownloadsUiState> = MutableStateFlow(DownloadsUiState(
|
|
||||||
errorMessage = null,
|
|
||||||
actions = null
|
|
||||||
))
|
))
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
|
@ -126,6 +127,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
allDownloads = repository.allDownloads.flow
|
allDownloads = repository.allDownloads.flow
|
||||||
queuedDownloads = repository.queuedDownloads.flow
|
queuedDownloads = repository.queuedDownloads.flow
|
||||||
activeDownloads = repository.activeDownloads
|
activeDownloads = repository.activeDownloads
|
||||||
|
processingDownloads = repository.processingDownloads
|
||||||
activeDownloadsCount = repository.activeDownloadsCount
|
activeDownloadsCount = repository.activeDownloadsCount
|
||||||
savedDownloads = repository.savedDownloads.flow
|
savedDownloads = repository.savedDownloads.flow
|
||||||
cancelledDownloads = repository.cancelledDownloads.flow
|
cancelledDownloads = repository.cancelledDownloads.flow
|
||||||
|
|
@ -223,6 +225,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
||||||
val saveSubs = sharedPreferences.getBoolean("write_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 addChapters = sharedPreferences.getBoolean("add_chapters", false)
|
||||||
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
|
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||||
|
|
@ -261,6 +264,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
addChapters, false,
|
addChapters, false,
|
||||||
ArrayList(sponsorblock),
|
ArrayList(sponsorblock),
|
||||||
saveSubs,
|
saveSubs,
|
||||||
|
saveAutoSubs,
|
||||||
audioFormatIDs = preferredAudioFormats
|
audioFormatIDs = preferredAudioFormats
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -391,6 +395,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
fun createDownloadItemFromHistory(historyItem: HistoryItem) : DownloadItem {
|
fun createDownloadItemFromHistory(historyItem: HistoryItem) : DownloadItem {
|
||||||
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
||||||
val saveSubs = sharedPreferences.getBoolean("write_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 addChapters = sharedPreferences.getBoolean("add_chapters", false)
|
||||||
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
|
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
|
||||||
val embedThumb = sharedPreferences.getBoolean("embed_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 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
|
var path = defaultPath
|
||||||
historyItem.downloadPath.first().apply {
|
historyItem.downloadPath.first().apply {
|
||||||
File(this).parent?.apply {
|
File(this).parent?.apply {
|
||||||
|
|
@ -480,7 +485,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Type.audio -> {
|
Type.audio -> {
|
||||||
return cloneFormat (
|
return cloneFormat (
|
||||||
try {
|
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()
|
val requirements = getPreferredAudioRequirements()
|
||||||
theFormats.maxByOrNull { f -> requirements.count{req -> req(f)} } ?: throw Exception()
|
theFormats.maxByOrNull { f -> requirements.count{req -> req(f)} } ?: throw Exception()
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
|
|
@ -492,7 +497,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Type.video -> {
|
Type.video -> {
|
||||||
return cloneFormat(
|
return cloneFormat(
|
||||||
try {
|
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) {
|
when (videoQualityPreference) {
|
||||||
"worst" -> {
|
"worst" -> {
|
||||||
theFormats.last()
|
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) {
|
fun deleteCancelled() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
repository.deleteCancelled()
|
repository.deleteCancelled()
|
||||||
}
|
}
|
||||||
|
|
@ -599,6 +612,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
repository.deleteSaved()
|
repository.deleteSaved()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteProcessing() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
repository.deleteProcessing()
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||||
repository.deleteAllWithIDs(ids)
|
repository.deleteAllWithIDs(ids)
|
||||||
}
|
}
|
||||||
|
|
@ -646,27 +663,31 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||||
startDownloadWorker(emptyList())
|
DownloadUtil.startDownloadWorker(emptyList(), application)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{
|
suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{
|
||||||
dbManager.downloadDao.putAtTopOfTheQueue(id)
|
dbManager.downloadDao.putAtTopOfTheQueue(id)
|
||||||
startDownloadWorker(emptyList())
|
DownloadUtil.startDownloadWorker(emptyList(), application)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
dbManager.downloadDao.reQueueDownloadItems(items)
|
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 context = App.instance
|
||||||
val alarmScheduler = AlarmScheduler(context)
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val activeAndQueuedDownloads = withContext(Dispatchers.IO){
|
val activeAndQueuedDownloads = withContext(Dispatchers.IO){
|
||||||
repository.getActiveAndQueuedDownloads()
|
repository.getActiveAndQueuedDownloads()
|
||||||
}
|
}
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
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
|
//if scheduler is on
|
||||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
|
|
@ -680,122 +701,85 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
var alreadyExists = false
|
||||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
|
||||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
if(!ignoreDuplicate){
|
||||||
d.id = 0
|
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||||
d.logID = null
|
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||||
d.customFileNameTemplate = it.customFileNameTemplate
|
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||||
d.status = DownloadRepository.Status.Queued.toString()
|
d.id = 0
|
||||||
d.toString() == it.toString()
|
d.logID = null
|
||||||
}
|
d.customFileNameTemplate = it.customFileNameTemplate
|
||||||
if (existingDownload != null && !ignoreDuplicate) {
|
d.status = DownloadRepository.Status.Queued.toString()
|
||||||
it.id = existingDownload.id
|
d.toString() == it.toString()
|
||||||
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) } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val existingHistory = history.firstOrNull {
|
if (existingDownload != null){
|
||||||
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
it.status = DownloadRepository.Status.Saved.toString()
|
||||||
}
|
val id = runBlocking {
|
||||||
|
repository.insert(it)
|
||||||
if (existingHistory != null && !ignoreDuplicate){
|
}
|
||||||
it.id = existingHistory.id
|
alreadyExists = true
|
||||||
existing.add(it)
|
existingDownloads.add(id)
|
||||||
}else{
|
}else{
|
||||||
if (it.id == 0L){
|
//check if downloaded and file exists
|
||||||
val id = runBlocking {
|
val history = withContext(Dispatchers.IO){
|
||||||
repository.insert(it)
|
historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||||
}
|
|
||||||
it.id = id
|
|
||||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
repository.update(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()){
|
return Pair(existingDownloads, existingHistory)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getQueuedCollectedFileSize() : Long {
|
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() })
|
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(
|
private fun updateDownloadItem(
|
||||||
downloadItem: DownloadItem,
|
downloadItem: DownloadItem,
|
||||||
infoUtil: InfoUtil,
|
infoUtil: InfoUtil,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
||||||
val websiteFilter = MutableLiveData("")
|
val websiteFilter = MutableLiveData("")
|
||||||
private val queryFilter = MutableLiveData("")
|
private val queryFilter = MutableLiveData("")
|
||||||
private val formatFilter = MutableLiveData("")
|
private val formatFilter = MutableLiveData("")
|
||||||
|
private val notDeletedFilter = MutableLiveData(false)
|
||||||
|
|
||||||
val allItems : LiveData<List<HistoryItem>>
|
val allItems : LiveData<List<HistoryItem>>
|
||||||
private var _items = MediatorLiveData<List<HistoryItem>>()
|
private var _items = MediatorLiveData<List<HistoryItem>>()
|
||||||
|
|
@ -32,19 +33,23 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
|
||||||
allItems = repository.items.asLiveData()
|
allItems = repository.items.asLiveData()
|
||||||
|
|
||||||
_items.addSource(allItems){
|
_items.addSource(allItems){
|
||||||
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!)
|
filter(queryFilter.value!!, formatFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, notDeletedFilter.value!!)
|
||||||
}
|
}
|
||||||
_items.addSource(formatFilter){
|
_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){
|
_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){
|
_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){
|
_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
|
formatFilter.value = filter
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING) = viewModelScope.launch(Dispatchers.IO){
|
fun setNotDeleted(filter: Boolean){
|
||||||
_items.postValue(repository.getFiltered(query, format, site, sortType, sort))
|
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> {
|
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))
|
else -> res.addAll(repository.getDefault(inputQuery, resetResults))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e !is kotlinx.coroutines.CancellationException){
|
if (updateResultDataJob?.isCancelled == false){
|
||||||
uiState.update {it.copy(
|
uiState.update {it.copy(
|
||||||
processing = false,
|
processing = false,
|
||||||
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
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
|
package com.deniscerri.ytdlnis.receiver
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -97,6 +98,7 @@ class ShareActivity : BaseActivity() {
|
||||||
val navHostFragment = supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment
|
val navHostFragment = supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment
|
||||||
navController = navHostFragment.findNavController()
|
navController = navHostFragment.findNavController()
|
||||||
navController.addOnDestinationChangedListener(object: NavController.OnDestinationChangedListener{
|
navController.addOnDestinationChangedListener(object: NavController.OnDestinationChangedListener{
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
override fun onDestinationChanged(
|
override fun onDestinationChanged(
|
||||||
controller: NavController,
|
controller: NavController,
|
||||||
destination: NavDestination,
|
destination: NavDestination,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.deniscerri.ytdlnis.receiver
|
package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -13,7 +13,6 @@ import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class AlarmStartReceiver : BroadcastReceiver() {
|
class AlarmStartReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(p0: Context?, p1: Intent?) {
|
override fun onReceive(p0: Context?, p1: Intent?) {
|
||||||
Log.e("assa", "Start")
|
|
||||||
val workConstraints = Constraints.Builder()
|
val workConstraints = Constraints.Builder()
|
||||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
.addTag("download")
|
.addTag("download")
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.deniscerri.ytdlnis.receiver
|
package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.ui
|
package com.deniscerri.ytdlnis.ui
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
|
|
@ -14,6 +15,7 @@ import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.View.*
|
import android.view.View.*
|
||||||
|
import android.view.animation.Interpolator
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
@ -35,14 +37,15 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
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.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.models.SearchSuggestionItem
|
import com.deniscerri.ytdlnis.database.models.SearchSuggestionItem
|
||||||
import com.deniscerri.ytdlnis.database.models.SearchSuggestionType
|
import com.deniscerri.ytdlnis.database.models.SearchSuggestionType
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
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.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
|
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.SearchSuggestionsAdapter
|
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.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
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.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
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.SearchBar
|
||||||
import com.google.android.material.search.SearchView
|
import com.google.android.material.search.SearchView
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -76,13 +79,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
|
|
||||||
private var downloadSelectedFab: ExtendedFloatingActionButton? = null
|
private var downloadSelectedFab: ExtendedFloatingActionButton? = null
|
||||||
private var downloadAllFab: ExtendedFloatingActionButton? = null
|
private var downloadAllFab: ExtendedFloatingActionButton? = null
|
||||||
private var clipboardFab: FloatingActionButton? = null
|
private var clipboardFab: ExtendedFloatingActionButton? = null
|
||||||
private var homeFabs: CoordinatorLayout? = null
|
private var homeFabs: CoordinatorLayout? = null
|
||||||
private var infoUtil: InfoUtil? = null
|
private var infoUtil: InfoUtil? = null
|
||||||
private var downloadQueue: ArrayList<ResultItem>? = null
|
private var downloadQueue: ArrayList<ResultItem>? = null
|
||||||
|
|
||||||
private lateinit var resultViewModel : ResultViewModel
|
private lateinit var resultViewModel : ResultViewModel
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
|
private lateinit var historyViewModel : HistoryViewModel
|
||||||
|
|
||||||
private var fragmentView: View? = null
|
private var fragmentView: View? = null
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
|
|
@ -126,7 +130,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
uiHandler = Handler(Looper.getMainLooper())
|
uiHandler = Handler(Looper.getMainLooper())
|
||||||
selectedObjects = ArrayList()
|
selectedObjects = ArrayList()
|
||||||
|
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
|
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
|
||||||
|
|
||||||
downloadQueue = ArrayList()
|
downloadQueue = ArrayList()
|
||||||
resultsList = mutableListOf()
|
resultsList = mutableListOf()
|
||||||
|
|
@ -167,7 +172,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
searchSuggestionsRecyclerView = view.findViewById(R.id.search_suggestions_recycler)
|
searchSuggestionsRecyclerView = view.findViewById(R.id.search_suggestions_recycler)
|
||||||
searchSuggestionsRecyclerView?.layoutManager = LinearLayoutManager(context)
|
searchSuggestionsRecyclerView?.layoutManager = LinearLayoutManager(context)
|
||||||
searchSuggestionsRecyclerView?.adapter = searchSuggestionsAdapter
|
searchSuggestionsRecyclerView?.adapter = searchSuggestionsAdapter
|
||||||
searchSuggestionsRecyclerView?.enableFastScroll()
|
searchSuggestionsRecyclerView?.itemAnimator = null
|
||||||
|
|
||||||
|
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
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() {
|
override fun onResume() {
|
||||||
|
|
@ -278,15 +307,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
|
if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
|
||||||
arguments?.remove("showDownloadsWithUpdatedFormats")
|
arguments?.remove("showDownloadsWithUpdatedFormats")
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val ids = arguments?.getLongArray("downloadIds")
|
val ids = arguments?.getLongArray("downloadIds") ?: return@launch
|
||||||
val items = mutableListOf<DownloadItem>()
|
val type = downloadViewModel.updateItemsWithIdsToProcessingStatus(ids.toList())
|
||||||
ids?.forEach {
|
|
||||||
items.add(downloadViewModel.getItemByID(it))
|
|
||||||
downloadViewModel.deleteDownload(it)
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
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
|
clipboardFab?.isVisible = false
|
||||||
initSearch(searchView!!)
|
initSearch(searchView!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
clipboardFab?.extend()
|
||||||
|
delay(1000)
|
||||||
|
clipboardFab?.shrink()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -679,11 +710,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
downloadViewModel.turnResultItemsToDownloadItems(resultsList!!)
|
downloadViewModel.turnResultItemsToDownloadItems(resultsList!!)
|
||||||
}
|
}
|
||||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
Pair("downloads", downloadList)
|
downloadViewModel.insertToProcessing(downloadList)
|
||||||
))
|
}
|
||||||
|
|
||||||
|
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(Pair("type", downloadList[0].type)))
|
||||||
} else {
|
} 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)) {
|
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
downloadViewModel.insertToProcessing(downloadList)
|
||||||
|
}
|
||||||
|
|
||||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
||||||
Pair("downloads", downloadList)
|
Pair("type", downloadList[0].type)
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
downloadViewModel.queueDownloads(downloadList)
|
downloadViewModel.queueDownloads(downloadList)
|
||||||
|
|
@ -827,8 +866,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
chip.text = text
|
chip.text = text
|
||||||
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
|
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
|
||||||
chip.setOnClickListener {
|
chip.setOnClickListener {
|
||||||
|
if (queriesChipGroup!!.childCount == 1) queriesConstraint!!.visibility = View.GONE
|
||||||
queriesChipGroup!!.removeView(chip)
|
queriesChipGroup!!.removeView(chip)
|
||||||
if (queriesChipGroup!!.childCount == 0) queriesChipGroup!!.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
queriesChipGroup!!.addView(chip)
|
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.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
resultViewModel.removeSearchQueryFromHistory(text)
|
resultViewModel.removeSearchQueryFromHistory(text)
|
||||||
searchSuggestionsAdapter?.notifyItemRemoved(position)
|
updateSearchViewItems(searchView!!.editText.text)
|
||||||
}
|
}
|
||||||
deleteDialog.show()
|
deleteDialog.show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
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.dp
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
@ -61,6 +63,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
card.tag = "${item!!.id}##card"
|
card.tag = "${item!!.id}##card"
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
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.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
@ -56,6 +57,8 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
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.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
|
|
@ -54,13 +55,16 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
|
if (item == null) return
|
||||||
|
card.tag = item.id.toString()
|
||||||
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
|
|
||||||
// THUMBNAIL ----------------------------------
|
// THUMBNAIL ----------------------------------
|
||||||
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||||
val imageURL = item?.thumb
|
val imageURL = item.thumb
|
||||||
if (!imageURL.isNullOrBlank()) {
|
if (!imageURL.isNullOrBlank()) {
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -72,7 +76,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
duration.text = item!!.duration
|
duration.text = item.duration
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
|
|
@ -120,7 +124,7 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
|
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
|
||||||
|
|
||||||
btn.setOnClickListener {
|
btn.setOnClickListener {
|
||||||
onItemClickListener.onButtonClick(item.url)
|
onItemClickListener.onButtonClick(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
when(item.type) {
|
when(item.type) {
|
||||||
|
|
@ -136,18 +140,18 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
card.setOnClickListener {
|
card.setOnClickListener {
|
||||||
onItemClickListener.onCardClick(item.url)
|
onItemClickListener.onCardClick(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
card.setOnLongClickListener {
|
card.setOnLongClickListener {
|
||||||
onItemClickListener.onDelete(item.url); true
|
onItemClickListener.onDelete(item.id); true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnItemClickListener {
|
interface OnItemClickListener {
|
||||||
fun onButtonClick(itemURL: String)
|
fun onButtonClick(id: Long)
|
||||||
fun onCardClick(itemURL: String)
|
fun onCardClick(id: Long)
|
||||||
fun onDelete(itemURL: String)
|
fun onDelete(id: Long)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -157,7 +161,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
|
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 androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
||||||
class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CookieItem?, CookieAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
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) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.item
|
val card = holder.item
|
||||||
|
card.popup()
|
||||||
|
|
||||||
val title = card.findViewById<TextView>(R.id.title)
|
val title = card.findViewById<TextView>(R.id.title)
|
||||||
title.text = item?.url
|
title.text = item?.url
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
@ -50,6 +51,7 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.item
|
val card = holder.item
|
||||||
|
card.popup()
|
||||||
|
|
||||||
val title = card.findViewById<TextView>(R.id.title)
|
val title = card.findViewById<TextView>(R.id.title)
|
||||||
title.text = item?.title
|
title.text = item?.title
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
@ -55,6 +56,8 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
|
|
||||||
if (item == null) return
|
if (item == null) return
|
||||||
card.tag = item.id.toString()
|
card.tag = item.id.toString()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,8 @@ import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
|
@ -24,9 +22,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.google.android.material.badge.BadgeDrawable
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.google.android.material.badge.BadgeUtils
|
|
||||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
|
@ -86,6 +82,8 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
|
|
||||||
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
|
|
@ -116,7 +114,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
||||||
author.text = item.author
|
author.text = item.author
|
||||||
|
|
||||||
val length = card.findViewById<TextView>(R.id.length)
|
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 ----------------------------------
|
// TIME DOWNLOADED ----------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.ui.adapter
|
package com.deniscerri.ytdlnis.ui.adapter
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
|
@ -18,11 +19,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
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.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
|
|
||||||
|
|
||||||
class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, HomeAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, HomeAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||||
DIFF_CALLBACK
|
DIFF_CALLBACK
|
||||||
).build()) {
|
).build()) {
|
||||||
|
|
@ -55,6 +59,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val video = getItem(position)
|
val video = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
|
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 androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -56,6 +57,7 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ class SearchSuggestionsAdapter(onItemClickListener: OnItemClickListener, activit
|
||||||
}
|
}
|
||||||
textView.setOnLongClickListener { true }
|
textView.setOnLongClickListener { true }
|
||||||
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
|
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
|
||||||
|
mb.setImageResource(R.drawable.ic_arrow_outward)
|
||||||
mb.setOnClickListener {
|
mb.setOnClickListener {
|
||||||
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
|
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +80,7 @@ class SearchSuggestionsAdapter(onItemClickListener: OnItemClickListener, activit
|
||||||
}
|
}
|
||||||
|
|
||||||
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
|
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
|
||||||
|
mb.setImageResource(R.drawable.ic_arrow_outward)
|
||||||
mb.setOnClickListener {
|
mb.setOnClickListener {
|
||||||
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
|
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
||||||
class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CommandTemplate?, TemplatesAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
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) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.item
|
val card = holder.item
|
||||||
|
card.popup()
|
||||||
|
|
||||||
val title = card.findViewById<TextView>(R.id.title)
|
val title = card.findViewById<TextView>(R.id.title)
|
||||||
title.text = item?.title
|
title.text = item?.title
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
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.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
|
@ -47,6 +48,8 @@ class TerminalDownloadsAdapter(onItemClickListener: OnItemClickListener, activit
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
card.popup()
|
||||||
|
|
||||||
card.tag = "${item!!.id}##card"
|
card.tag = "${item!!.id}##card"
|
||||||
|
|
||||||
// PROGRESS BAR ----------------------------------------------------
|
// PROGRESS BAR ----------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.lifecycle.ViewModelProvider
|
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.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
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
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -40,7 +43,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
private lateinit var fragmentAdapter : DownloadFragmentAdapter
|
private lateinit var fragmentAdapter : DownloadFragmentAdapter
|
||||||
private lateinit var downloadViewModel: DownloadViewModel
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
private lateinit var resultViewModel: ResultViewModel
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
private lateinit var commandTemplateDao: CommandTemplateDao
|
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
||||||
private lateinit var sharedPreferences : SharedPreferences
|
private lateinit var sharedPreferences : SharedPreferences
|
||||||
|
|
@ -49,7 +52,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||||
onDownloadItemUpdateListener = listener
|
onDownloadItemUpdateListener = listener
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +87,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
var commandTemplateNr = 0
|
var commandTemplateNr = 0
|
||||||
lifecycleScope.launch{
|
lifecycleScope.launch{
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
commandTemplateNr = commandTemplateDao.getTotalNumber()
|
commandTemplateNr = commandTemplateViewModel.getTotalNumber()
|
||||||
if(commandTemplateNr <= 0){
|
if(commandTemplateNr <= 0){
|
||||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
||||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
|
(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?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
if (tab!!.position == 2 && commandTemplateNr == 0){
|
if (tab!!.position == 2 && commandTemplateNr == 0){
|
||||||
tabLayout.selectTab(tabLayout.getTabAt(1))
|
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){
|
}else if (tab.position == 1 && isAudioOnly){
|
||||||
tabLayout.selectTab(tabLayout.getTabAt(0))
|
tabLayout.selectTab(tabLayout.getTabAt(0))
|
||||||
Toast.makeText(context, getString(R.string.audio_only_item), Toast.LENGTH_SHORT).show()
|
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){
|
return when(selectedTabPosition){
|
||||||
0 -> {
|
0 -> {
|
||||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
f.downloadItem
|
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||||
f.downloadItem
|
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val f = fragmentManager?.findFragmentByTag("f2") as DownloadCommandFragment
|
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(
|
val prevDownloadItem = getDownloadItem(
|
||||||
if (viewPager2.currentItem == 1) 0 else 1
|
if (viewPager2.currentItem == 1) 0 else 1
|
||||||
)
|
)
|
||||||
|
fragmentAdapter.setTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||||
result.title = prevDownloadItem.title
|
|
||||||
result.author = prevDownloadItem.author
|
|
||||||
currentDownloadItem.title = prevDownloadItem.title
|
|
||||||
currentDownloadItem.author = prevDownloadItem.author
|
|
||||||
|
|
||||||
when(viewPager2.currentItem){
|
when(viewPager2.currentItem){
|
||||||
0 -> {
|
0 -> {
|
||||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
kotlin.runCatching {
|
||||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
|
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
kotlin.runCatching {
|
||||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||||
f.updateSelectedAudioFormat(getDownloadItem(0).format)
|
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 -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||||
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
|
@ -203,7 +204,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
//poll video progress
|
//poll video progress
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
videoProgress(player).collect { p ->
|
videoProgress(player).collect { p ->
|
||||||
val currentTime = infoUtil.formatIntegerDuration(p, Locale.US)
|
val currentTime = p.toStringDuration(Locale.US)
|
||||||
durationText.text = "$currentTime / ${item.duration}"
|
durationText.text = "$currentTime / ${item.duration}"
|
||||||
val startTimestamp = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
val startTimestamp = convertStringToTimestamp(fromTextInput.editText!!.text.toString())
|
||||||
if (toTextInput.editText!!.text.isNotBlank()){
|
if (toTextInput.editText!!.text.isNotBlank()){
|
||||||
|
|
@ -310,11 +311,11 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
val endValue = (endSeconds.toFloat() / endTimestamp) * 100
|
val endValue = (endSeconds.toFloat() / endTimestamp) * 100
|
||||||
|
|
||||||
if (seconds == 0) {
|
if (seconds == 0) {
|
||||||
fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
|
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
||||||
}else if (startValue > 100){
|
}else if (startValue > 100){
|
||||||
startTimestamp = 0
|
startTimestamp = 0
|
||||||
seconds = 0
|
seconds = 0
|
||||||
fromTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(startTimestamp, Locale.US))
|
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestamp.toStringDuration(Locale.US))
|
||||||
startValue = 0F
|
startValue = 0F
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,14 +351,14 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
var endValue = (seconds.toFloat() / endTimestamp) * 100
|
var endValue = (seconds.toFloat() / endTimestamp) * 100
|
||||||
if (endValue > 100F){
|
if (endValue > 100F){
|
||||||
endTimestamp = timeSeconds
|
endTimestamp = timeSeconds
|
||||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||||
endValue = 100F
|
endValue = 100F
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seconds == 0) {
|
if (seconds == 0) {
|
||||||
toTextInput.editText!!.setTextAndRecalculateWidth(infoUtil.formatIntegerDuration(endTimestamp, Locale.US))
|
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestamp.toStringDuration(Locale.US))
|
||||||
}else if (endValue <= rangeSlider.valueFrom.toInt()){
|
}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())
|
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 startTimestamp = (values[0].toInt() * timeSeconds) / 100
|
||||||
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
|
val endTimestamp = (values[1].toInt() * timeSeconds) / 100
|
||||||
|
|
||||||
val startTimestampString = infoUtil.formatIntegerDuration(startTimestamp, Locale.US)
|
val startTimestampString = startTimestamp.toStringDuration(Locale.US)
|
||||||
val endTimestampString = infoUtil.formatIntegerDuration(endTimestamp, Locale.US)
|
val endTimestampString = endTimestamp.toStringDuration(Locale.US)
|
||||||
|
|
||||||
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestampString)
|
fromTextInput.editText!!.setTextAndRecalculateWidth(startTimestampString)
|
||||||
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
|
toTextInput.editText!!.setTextAndRecalculateWidth(endTimestampString)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
|
@ -18,6 +19,7 @@ import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -48,7 +50,7 @@ import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
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 fragmentView: View? = null
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
|
|
@ -63,6 +65,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
lateinit var author : TextInputLayout
|
lateinit var author : TextInputLayout
|
||||||
lateinit var preferences: SharedPreferences
|
lateinit var preferences: SharedPreferences
|
||||||
lateinit var shownFields: List<String>
|
lateinit var shownFields: List<String>
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -95,7 +98,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
|
|
||||||
try {
|
try {
|
||||||
title = view.findViewById(R.id.title_textinput)
|
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){
|
if (title.editText?.text?.isEmpty() == true){
|
||||||
title.editText!!.setText(downloadItem.title)
|
title.editText!!.setText(downloadItem.title)
|
||||||
title.endIconMode = END_ICON_NONE
|
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 = 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){
|
if (author.editText?.text?.isEmpty() == true){
|
||||||
author.editText!!.setText(downloadItem.author)
|
author.editText!!.setText(downloadItem.author)
|
||||||
author.endIconMode = END_ICON_NONE
|
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)
|
val containers = requireContext().resources.getStringArray(R.array.audio_containers)
|
||||||
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
||||||
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
|
if (containerPreference == "Default") containerPreference = getString(R.string.defaultValue)
|
||||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||||
container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
|
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 =
|
val containerAutoCompleteTextView =
|
||||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
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)?.isClickable = false
|
||||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
|
(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
|
//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 {
|
lifecycleScope.launch {
|
||||||
resultViewModel.uiState.collectLatest { res ->
|
resultViewModel.uiState.collectLatest { res ->
|
||||||
|
|
@ -436,7 +416,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
shimmerLoadingSubtitle.visibility = View.VISIBLE
|
shimmerLoadingSubtitle.visibility = View.VISIBLE
|
||||||
shimmerLoading.startShimmer()
|
shimmerLoading.startShimmer()
|
||||||
shimmerLoadingSubtitle.startShimmer()
|
shimmerLoadingSubtitle.startShimmer()
|
||||||
updateItem.visibility = View.GONE
|
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||||
}else{
|
}else{
|
||||||
title.visibility = View.VISIBLE
|
title.visibility = View.VISIBLE
|
||||||
subtitle.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)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
downloadItem = withContext(Dispatchers.IO){
|
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)
|
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||||
Gson().fromJson(string, DownloadItem::class.java)
|
Gson().fromJson(string, DownloadItem::class.java)
|
||||||
}else{
|
}else{
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ class DownloadFragmentAdapter (
|
||||||
fragmentManager : FragmentManager,
|
fragmentManager : FragmentManager,
|
||||||
lifecycle : Lifecycle,
|
lifecycle : Lifecycle,
|
||||||
private var result: ResultItem,
|
private var result: ResultItem,
|
||||||
private var downloadItem: DownloadItem?
|
private var downloadItem: DownloadItem?,
|
||||||
|
private var nonSpecific: Boolean = false
|
||||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||||
|
|
||||||
fun setResultItem(res: ResultItem){
|
fun setResultItem(res: ResultItem){
|
||||||
|
|
@ -35,8 +36,8 @@ class DownloadFragmentAdapter (
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
return when(position){
|
return when(position){
|
||||||
0 -> DownloadAudioFragment(result, downloadItem)
|
0 -> DownloadAudioFragment(result, downloadItem,"", nonSpecific)
|
||||||
1 -> DownloadVideoFragment(result, downloadItem)
|
1 -> DownloadVideoFragment(result, downloadItem,"", nonSpecific)
|
||||||
else -> DownloadCommandFragment(result, downloadItem)
|
else -> DownloadCommandFragment(result, downloadItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -34,23 +35,21 @@ import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
|
||||||
import com.deniscerri.ytdlnis.R
|
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.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
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.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
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.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
|
||||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
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.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
|
@ -82,16 +81,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
private lateinit var bottomAppBar: BottomAppBar
|
private lateinit var bottomAppBar: BottomAppBar
|
||||||
private lateinit var filesize : TextView
|
private lateinit var filesize : TextView
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var type: DownloadViewModel.Type
|
||||||
private lateinit var items: MutableList<DownloadItem>
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
|
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
infoUtil = InfoUtil(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)
|
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
|
||||||
dialog.setContentView(view)
|
dialog.setContentView(view)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 33){
|
arguments?.getSerializable("type").apply {
|
||||||
arguments?.getParcelableArrayList("downloads", DownloadItem::class.java)
|
|
||||||
}else{
|
|
||||||
arguments?.getParcelableArrayList<DownloadItem>("downloads")
|
|
||||||
}.apply {
|
|
||||||
if (this == null){
|
if (this == null){
|
||||||
dismiss()
|
dismiss()
|
||||||
return
|
return
|
||||||
}else{
|
}else{
|
||||||
items = this
|
type = this as DownloadViewModel.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.isEmpty()) dismiss()
|
|
||||||
|
|
||||||
dialog.setOnShowListener {
|
dialog.setOnShowListener {
|
||||||
behavior = BottomSheetBehavior.from(view.parent as View)
|
behavior = BottomSheetBehavior.from(view.parent as View)
|
||||||
|
|
@ -138,42 +130,52 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
recyclerView.adapter = listAdapter
|
recyclerView.adapter = listAdapter
|
||||||
recyclerView.enableFastScroll()
|
recyclerView.enableFastScroll()
|
||||||
|
|
||||||
|
|
||||||
|
view.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
||||||
|
setOnClickListener {
|
||||||
|
recyclerView.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("multipledownloadcard")){
|
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("multipledownloadcard")){
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||||
}
|
}
|
||||||
listAdapter.submitList(items.toList())
|
|
||||||
|
|
||||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_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{
|
scheduleBtn.setOnClickListener{
|
||||||
UiUtil.showDatePicker(parentFragmentManager) {
|
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
||||||
scheduleBtn.isEnabled = false
|
scheduleBtn.isEnabled = false
|
||||||
download.isEnabled = false
|
download.isEnabled = false
|
||||||
items.forEach { item ->
|
|
||||||
item.downloadStartTime = it.timeInMillis
|
|
||||||
}
|
lifecycleScope.launch {
|
||||||
runBlocking {
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.queueDownloads(items)
|
downloadViewModel.downloadProcessingDownloads(cal.timeInMillis)
|
||||||
val first = items.first()
|
}
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
|
||||||
|
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()
|
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
download!!.setOnClickListener {
|
download!!.setOnClickListener {
|
||||||
scheduleBtn.isEnabled = false
|
scheduleBtn.isEnabled = false
|
||||||
download.isEnabled = false
|
download.isEnabled = false
|
||||||
runBlocking {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.queueDownloads(items)
|
withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.downloadProcessingDownloads()
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
download.setOnLongClickListener {
|
download.setOnLongClickListener {
|
||||||
|
|
@ -182,9 +184,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
lifecycleScope.launch{
|
lifecycleScope.launch{
|
||||||
items.map { it.status = DownloadRepository.Status.Saved.toString() }
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.insertAll(items)
|
downloadViewModel.moveProcessingToSavedCategory()
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +196,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||||
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
||||||
when(items.first().type){
|
when(type){
|
||||||
DownloadViewModel.Type.audio -> {
|
DownloadViewModel.Type.audio -> {
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||||
}
|
}
|
||||||
|
|
@ -212,72 +213,28 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
val formatListener = object : OnFormatClickListener {
|
val formatListener = object : OnFormatClickListener {
|
||||||
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
||||||
items.forEachIndexed { index, i ->
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
i.format = selectedFormats[index].format
|
downloadViewModel.updateProcessingFormat(selectedFormats)
|
||||||
if (i.type == DownloadViewModel.Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
|
||||||
}
|
}
|
||||||
listAdapter.submitList(items.toList())
|
|
||||||
listAdapter.notifyDataSetChanged()
|
|
||||||
updateFileSize(items.map { it.format.filesize })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val formatCollection = mutableListOf<List<Format>>()
|
downloadViewModel.updateProcessingAllFormats(allFormats)
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onContinueOnBackground() {
|
override fun onContinueOnBackground() {
|
||||||
requireActivity().lifecycleScope.launch {
|
requireActivity().lifecycleScope.launch {
|
||||||
val ids = mutableListOf<Long>()
|
withContext(Dispatchers.IO){
|
||||||
runBlocking {
|
downloadViewModel.continueUpdatingFormatsOnBackground()
|
||||||
items.map { it.status = DownloadRepository.Status.Saved.toString() }
|
|
||||||
ids.addAll(downloadViewModel.repository.insertAll(items))
|
|
||||||
}
|
}
|
||||||
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.menu[3].icon?.alpha = 30
|
||||||
}
|
}
|
||||||
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
|
|
@ -311,39 +268,38 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
|
|
||||||
audio!!.setOnClickListener {
|
audio!!.setOnClickListener {
|
||||||
items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.audio).toMutableList()
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
listAdapter.submitList(items.toList())
|
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
|
||||||
listAdapter.notifyDataSetChanged()
|
withContext(Dispatchers.Main){
|
||||||
updateFileSize(items.map { it.format.filesize })
|
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[3].icon?.alpha = 255
|
||||||
bottomAppBar.menu[3].icon?.alpha = 255
|
bottomSheet.cancel()
|
||||||
bottomSheet.cancel()
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
video!!.setOnClickListener {
|
video!!.setOnClickListener {
|
||||||
items = downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.video).toMutableList()
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
listAdapter.submitList(items.toList())
|
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
|
||||||
listAdapter.notifyDataSetChanged()
|
withContext(Dispatchers.Main){
|
||||||
updateFileSize(items.map { it.format.filesize })
|
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[3].icon?.alpha = 255
|
||||||
bottomAppBar.menu[3].icon?.alpha = 255
|
bottomSheet.cancel()
|
||||||
bottomSheet.cancel()
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command!!.setOnClickListener {
|
command!!.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
items = withContext(Dispatchers.IO){
|
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
|
||||||
downloadViewModel.switchDownloadType(items, DownloadViewModel.Type.command).toMutableList()
|
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 -> {
|
R.id.format -> {
|
||||||
if (! items.all { it.type == items[0].type }){
|
lifecycleScope.launch {
|
||||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
val res = withContext(Dispatchers.IO){
|
||||||
}else{
|
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||||
if (items.first().type == DownloadViewModel.Type.command){
|
}
|
||||||
lifecycleScope.launch {
|
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) {
|
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel) {
|
||||||
val format = Format(
|
val format = Format(
|
||||||
it.first().title,
|
it.first().title,
|
||||||
|
|
@ -375,15 +335,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
it.joinToString(" ") { c -> c.content }
|
it.joinToString(" ") { c -> c.content }
|
||||||
)
|
)
|
||||||
|
|
||||||
items.forEach { f ->
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
f.format = format
|
downloadViewModel.updateProcessingCommandFormat(format)
|
||||||
}
|
}
|
||||||
listAdapter.submitList(items.toList())
|
|
||||||
listAdapter.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}else{
|
||||||
}else{
|
val items = withContext(Dispatchers.IO){
|
||||||
lifecycleScope.launch {
|
downloadViewModel.getProcessingDownloads()
|
||||||
|
}
|
||||||
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
||||||
val commonFormats = withContext(Dispatchers.IO){
|
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 }
|
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 -> {
|
R.id.more -> {
|
||||||
if (! items.all { it.type == items[0].type }) {
|
lifecycleScope.launch {
|
||||||
Toast.makeText(
|
val res = withContext(Dispatchers.IO){
|
||||||
requireContext(),
|
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
|
||||||
getString(R.string.format_filtering_hint),
|
}
|
||||||
Toast.LENGTH_SHORT
|
if (!res.first) {
|
||||||
).show()
|
Toast.makeText(
|
||||||
}else{
|
requireContext(),
|
||||||
val scale = resources.displayMetrics.density
|
getString(R.string.format_filtering_hint),
|
||||||
val padding = (40*scale*0.5f).toInt()
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}else{
|
||||||
|
val scale = resources.displayMetrics.density
|
||||||
|
val padding = (40*scale*0.5f).toInt()
|
||||||
|
|
||||||
when(items[0].type){
|
when(res.second){
|
||||||
DownloadViewModel.Type.audio -> {
|
DownloadViewModel.Type.audio -> {
|
||||||
val bottomSheet = BottomSheetDialog(requireContext())
|
val bottomSheet = BottomSheetDialog(requireContext())
|
||||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
bottomSheet.setContentView(R.layout.adjust_audio)
|
bottomSheet.setContentView(R.layout.adjust_audio)
|
||||||
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
val sheetView = bottomSheet.findViewById<View>(android.R.id.content)!!
|
||||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||||
|
|
||||||
UiUtil.configureAudio(
|
val items = withContext(Dispatchers.IO){
|
||||||
sheetView,
|
downloadViewModel.getProcessingDownloads()
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
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(
|
UiUtil.configureAudio(
|
||||||
sheetView,
|
sheetView,
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
items,
|
items,
|
||||||
embedSubsClicked = {checked ->
|
embedThumbClicked = {enabled ->
|
||||||
items.forEach { it.videoPreferences.embedSubs = checked }
|
items.forEach {
|
||||||
},
|
it.audioPreferences.embedThumb = enabled
|
||||||
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]) }
|
|
||||||
}
|
}
|
||||||
}
|
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||||
bottomSheet.dismiss()
|
},
|
||||||
},
|
cropThumbClicked = {enabled ->
|
||||||
cutClicked = {},
|
items.forEach {
|
||||||
updateDataClicked = {},
|
it.audioPreferences.cropThumb = enabled
|
||||||
filenameTemplateSet = { checked ->
|
}
|
||||||
items.forEach { it.customFileNameTemplate = checked }
|
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||||
},
|
},
|
||||||
saveSubtitlesClicked = {checked ->
|
splitByChaptersClicked = {enabled ->
|
||||||
items.forEach { it.videoPreferences.writeSubs = checked }
|
items.forEach {
|
||||||
},
|
it.audioPreferences.splitByChapters = enabled
|
||||||
subtitleLanguagesSet = {value ->
|
}
|
||||||
items.forEach { it.videoPreferences.subsLanguages = value }
|
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||||
},
|
},
|
||||||
removeAudioClicked = {checked ->
|
filenameTemplateSet = {template ->
|
||||||
items.forEach { it.videoPreferences.removeAudio = checked }
|
items.forEach {
|
||||||
},
|
it.customFileNameTemplate = template
|
||||||
extraCommandsClicked = {
|
}
|
||||||
val callback = object : ExtraCommandsListener {
|
requireActivity().lifecycleScope.launch {
|
||||||
override fun onChangeExtraCommand(c: String) {
|
items.forEach { downloadViewModel.updateDownload(it) }
|
||||||
items.forEach { it.extraCommands = c }
|
}
|
||||||
bottomSheet.dismiss()
|
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)
|
val bottomSheetDialog = AddExtraCommandsDialog(null, callback)
|
||||||
bottomSheetDialog.show(parentFragmentManager, "extraCommands")
|
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()
|
UiUtil.configureVideo(
|
||||||
val displayMetrics = DisplayMetrics()
|
sheetView,
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
requireActivity(),
|
||||||
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
items,
|
||||||
bottomSheet.window!!.setLayout(
|
embedSubsClicked = {checked ->
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
items.forEach { it.videoPreferences.embedSubs = checked }
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||||
)
|
},
|
||||||
}
|
addChaptersClicked = {checked ->
|
||||||
DownloadViewModel.Type.command -> {
|
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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch{
|
lifecycleScope.launch {
|
||||||
downloadViewModel.uiState.collectLatest { res ->
|
downloadViewModel.processingDownloads.collectLatest {
|
||||||
if (res.errorMessage != null) {
|
listAdapter.submitList(it)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
kotlin.runCatching {
|
updateFileSize(it.map { it2 -> it2.format.filesize })
|
||||||
UiUtil.handleDownloadsResponse(
|
|
||||||
requireActivity(),
|
|
||||||
requireActivity().lifecycleScope,
|
|
||||||
requireActivity().supportFragmentManager,
|
|
||||||
res,
|
|
||||||
downloadViewModel,
|
|
||||||
historyViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
|
||||||
errorMessage = null,
|
|
||||||
actions = null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>){
|
private fun updateFileSize(items: List<Long>){
|
||||||
if (items.all { it > 0L }){
|
if (items.all { it > 0L }){
|
||||||
filesize.visibility = View.VISIBLE
|
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{
|
}else{
|
||||||
filesize.visibility = View.GONE
|
filesize.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
@ -603,18 +607,21 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
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())
|
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 {
|
lifecycleScope.launch {
|
||||||
val item = items.find { it.url == itemURL } ?: return@launch
|
var item = withContext(Dispatchers.IO){
|
||||||
val position = items.indexOf(item)
|
downloadViewModel.getItemByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
val bottomSheet = BottomSheetDialog(requireContext())
|
val bottomSheet = BottomSheetDialog(requireContext())
|
||||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
|
@ -635,26 +642,31 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
|
|
||||||
audio!!.setOnClickListener {
|
audio!!.setOnClickListener {
|
||||||
items[position] = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.audio).first()
|
lifecycleScope.launch {
|
||||||
listAdapter.submitList(items.toList())
|
item = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.audio).first()
|
||||||
listAdapter.notifyItemChanged(position)
|
withContext(Dispatchers.IO){
|
||||||
bottomSheet.cancel()
|
downloadViewModel.updateDownload(item)
|
||||||
|
}
|
||||||
|
bottomSheet.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
video!!.setOnClickListener {
|
video!!.setOnClickListener {
|
||||||
items[position] = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.video).first()
|
lifecycleScope.launch {
|
||||||
listAdapter.submitList(items.toList())
|
item = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.video).first()
|
||||||
listAdapter.notifyItemChanged(position)
|
withContext(Dispatchers.IO){
|
||||||
bottomSheet.cancel()
|
downloadViewModel.updateDownload(item)
|
||||||
|
}
|
||||||
|
bottomSheet.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
command!!.setOnClickListener {
|
command!!.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
items[position] = withContext(Dispatchers.IO){
|
item = downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.command).first()
|
||||||
downloadViewModel.switchDownloadType(listOf(item), DownloadViewModel.Type.command).first()
|
withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.updateDownload(item)
|
||||||
}
|
}
|
||||||
listAdapter.submitList(items.toList())
|
|
||||||
listAdapter.notifyItemChanged(position)
|
|
||||||
bottomSheet.cancel()
|
bottomSheet.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -670,60 +682,71 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCardClick(itemURL: String) {
|
override fun onCardClick(id: Long) {
|
||||||
lifecycleScope.launch{
|
lifecycleScope.launch{
|
||||||
val downloadItem = items.find { it.url == itemURL }
|
|
||||||
|
val downloadItem = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.getItemByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
val resultItem = withContext(Dispatchers.IO){
|
val resultItem = withContext(Dispatchers.IO){
|
||||||
resultViewModel.getItemByURL(downloadItem!!.url)!!
|
resultViewModel.getItemByURL(downloadItem.url)!!
|
||||||
}
|
}
|
||||||
if (parentFragmentManager.findFragmentByTag("configureDownloadSingleSheet") == null){
|
if (parentFragmentManager.findFragmentByTag("configureDownloadSingleSheet") == null){
|
||||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem!!, this@DownloadMultipleBottomSheetDialog)
|
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem, this@DownloadMultipleBottomSheetDialog)
|
||||||
bottomSheet.show(parentFragmentManager, "configureDownloadSingleSheet")
|
bottomSheet.show(parentFragmentManager, "configureDownloadSingleSheet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDelete(itemURL: String) {
|
override fun onDelete(id: Long) {
|
||||||
val deletedItem = items.find { it.url == itemURL } ?: return
|
lifecycleScope.launch {
|
||||||
val position = items.indexOf(deletedItem)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
UiUtil.showGenericDeleteDialog(requireContext(), deletedItem.title){
|
downloadViewModel.getItemByID(id)
|
||||||
items.remove(deletedItem)
|
} ?: return@launch
|
||||||
listAdapter.submitList(items.toList())
|
|
||||||
if (items.isNotEmpty()){
|
UiUtil.showGenericDeleteDialog(requireContext(), deletedItem.title){
|
||||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
lifecycleScope.launch {
|
||||||
.setAction(getString(R.string.undo)) {
|
val count = withContext(Dispatchers.IO){
|
||||||
items.add(position, deletedItem)
|
downloadViewModel.getProcessingDownloadsCount()
|
||||||
listAdapter.submitList(items.toList())
|
}
|
||||||
}.show()
|
downloadViewModel.deleteDownload(id)
|
||||||
}else{
|
|
||||||
dismiss()
|
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 onClick(p0: View?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) {
|
override fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) {
|
||||||
val i = items.indexOf(items.find { it.url == item.url })
|
lifecycleScope.launch {
|
||||||
items[i] = item
|
withContext(Dispatchers.IO){
|
||||||
if (! items.all { it.type == items[0].type }){
|
downloadViewModel.updateDownload(item)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
listAdapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (items.count { it.type == DownloadViewModel.Type.command } == 0){
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
bottomAppBar.menu[2].icon?.alpha = 255
|
requireActivity().lifecycleScope.launch {
|
||||||
}else{
|
downloadViewModel.deleteProcessing()
|
||||||
bottomAppBar.menu[2].icon?.alpha = 30
|
|
||||||
}
|
}
|
||||||
|
super.onDismiss(dialog)
|
||||||
listAdapter.submitList(items.toList())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
|
|
@ -734,22 +757,29 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
val position = viewHolder.bindingAdapterPosition
|
val itemID = viewHolder.itemView.tag.toString().toLong()
|
||||||
when (direction) {
|
when (direction) {
|
||||||
ItemTouchHelper.LEFT -> {
|
ItemTouchHelper.LEFT -> {
|
||||||
val deletedItem = items[position]
|
lifecycleScope.launch {
|
||||||
items.remove(deletedItem)
|
val deletedItem = withContext(Dispatchers.IO){
|
||||||
listAdapter.submitList(items.toList())
|
downloadViewModel.getItemByID(itemID)
|
||||||
if (items.isNotEmpty()){
|
}
|
||||||
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
val count = withContext(Dispatchers.IO){
|
||||||
.setAction(getString(R.string.undo)) {
|
downloadViewModel.getProcessingDownloadsCount()
|
||||||
items.add(position, deletedItem)
|
}
|
||||||
listAdapter.submitList(items.toList())
|
withContext(Dispatchers.IO){
|
||||||
}.show()
|
downloadViewModel.deleteDownload(deletedItem.id)
|
||||||
}else{
|
}
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -34,23 +35,19 @@ import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.card.MaterialCardView
|
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
|
||||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
|
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.END_ICON_NONE
|
||||||
import com.google.android.material.textfield.TextInputLayout.EndIconMode
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
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 fragmentView: View? = null
|
||||||
private var activity: Activity? = null
|
private var activity: Activity? = null
|
||||||
private lateinit var downloadViewModel : DownloadViewModel
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
|
|
@ -67,6 +64,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
lateinit var downloadItem: DownloadItem
|
lateinit var downloadItem: DownloadItem
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
@ -100,7 +98,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
try {
|
try {
|
||||||
title = view.findViewById(R.id.title_textinput)
|
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){
|
if (title.editText?.text?.isEmpty() == true){
|
||||||
title.editText!!.setText(downloadItem.title)
|
title.editText!!.setText(downloadItem.title)
|
||||||
title.endIconMode = END_ICON_NONE
|
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 = 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){
|
if (author.editText?.text?.isEmpty() == true){
|
||||||
author.editText!!.setText(downloadItem.author)
|
author.editText!!.setText(downloadItem.author)
|
||||||
author.endIconMode = END_ICON_NONE
|
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 containers = requireContext().resources.getStringArray(R.array.video_containers)
|
||||||
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
|
||||||
container.visibility = if (shownFields.contains("container")) View.VISIBLE else View.GONE
|
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 =
|
val containerAutoCompleteTextView =
|
||||||
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
|
||||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
||||||
|
|
@ -329,6 +336,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
saveSubtitlesClicked = {
|
saveSubtitlesClicked = {
|
||||||
downloadItem.videoPreferences.writeSubs = it
|
downloadItem.videoPreferences.writeSubs = it
|
||||||
},
|
},
|
||||||
|
saveAutoSubtitlesClicked = {
|
||||||
|
downloadItem.videoPreferences.writeAutoSubs = it
|
||||||
|
},
|
||||||
subtitleLanguagesSet = {
|
subtitleLanguagesSet = {
|
||||||
downloadItem.videoPreferences.subsLanguages = it
|
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)
|
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 {
|
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.ui.adapter.PlaylistAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
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.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
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.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -197,11 +199,13 @@ class SelectPlaylistItemsDialog : DialogFragment(), PlaylistAdapter.OnItemClickL
|
||||||
downloadItems.add(i)
|
downloadItems.add(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main){
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
findNavController().navigate(R.id.action_selectPlaylistItemsDialog_to_downloadMultipleBottomSheetDialog, bundleOf(
|
downloadViewModel.insertToProcessing(downloadItems)
|
||||||
Pair("downloads", downloadItems)
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundleOf(
|
||||||
|
Pair("type", downloadItems[0].type)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.get
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
|
@ -17,8 +18,10 @@ import androidx.viewpager2.widget.ViewPager2
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
|
@ -136,6 +139,24 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
downloadViewModel.deleteSaved()
|
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){
|
}catch (e: Exception){
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
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.chip.ChipGroup
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
@ -80,7 +81,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
private var fragmentContext: Context? = null
|
private var fragmentContext: Context? = null
|
||||||
private var layoutinflater: LayoutInflater? = null
|
private var layoutinflater: LayoutInflater? = null
|
||||||
private var topAppBar: MaterialToolbar? = null
|
private var topAppBar: MaterialToolbar? = null
|
||||||
private var recyclerView: RecyclerView? = null
|
private lateinit var recyclerView: RecyclerView
|
||||||
private var historyAdapter: HistoryAdapter? = null
|
private var historyAdapter: HistoryAdapter? = null
|
||||||
private var sortSheet: BottomSheetDialog? = null
|
private var sortSheet: BottomSheetDialog? = null
|
||||||
private var uiHandler: Handler? = null
|
private var uiHandler: Handler? = null
|
||||||
|
|
@ -128,8 +129,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
requireActivity()
|
requireActivity()
|
||||||
)
|
)
|
||||||
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
|
||||||
recyclerView?.adapter = historyAdapter
|
recyclerView.adapter = historyAdapter
|
||||||
recyclerView?.enableFastScroll()
|
recyclerView.enableFastScroll()
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("history")){
|
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)
|
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
|
noResults?.visibility = GONE
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -185,11 +186,11 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
|
|
||||||
|
|
||||||
lifecycleScope.launch{
|
lifecycleScope.launch{
|
||||||
downloadViewModel.uiState.collectLatest { res ->
|
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
|
||||||
if (res.errorMessage != null) {
|
if (res.downloadItems.isNotEmpty() || res.historyItems.isNotEmpty()) {
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
UiUtil.handleDownloadsResponse(
|
UiUtil.handleExistingDownloadsResponse(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
requireActivity().lifecycleScope,
|
requireActivity().lifecycleScope,
|
||||||
requireActivity().supportFragmentManager,
|
requireActivity().supportFragmentManager,
|
||||||
|
|
@ -198,9 +199,9 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
historyViewModel)
|
historyViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
downloadViewModel.uiState.value = DownloadViewModel.DownloadsUiState(
|
downloadViewModel.alreadyExistsUiState.value = DownloadViewModel.AlreadyExistsUIState(
|
||||||
errorMessage = null,
|
mutableListOf(),
|
||||||
actions = null
|
mutableListOf()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,6 +397,17 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
command.isChecked = false
|
command.isChecked = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val notDeleted = fragmentView!!.findViewById<Chip>(R.id.not_deleted_chip)
|
||||||
|
notDeleted.setOnClickListener {
|
||||||
|
if (notDeleted.isChecked) {
|
||||||
|
historyViewModel.setNotDeleted(true)
|
||||||
|
notDeleted.isChecked = true
|
||||||
|
} else {
|
||||||
|
historyViewModel.setNotDeleted(false)
|
||||||
|
notDeleted.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWebsiteChips(list : List<HistoryItem>) {
|
private fun updateWebsiteChips(list : List<HistoryItem>) {
|
||||||
|
|
@ -577,7 +589,18 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
historyAdapter!!.notifyItemChanged(position)
|
historyAdapter!!.notifyItemChanged(position)
|
||||||
UiUtil.showRemoveHistoryItemDialog(deletedItem!!, requireActivity(),
|
UiUtil.showRemoveHistoryItemDialog(deletedItem!!, requireActivity(),
|
||||||
delete = { item, deleteFile ->
|
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 -> {
|
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.repository.CommandTemplateRepository.CommandTemplateSortType
|
||||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.chip.Chip
|
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 it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -162,38 +163,51 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDialog(url: String){
|
private fun showDialog(url: String){
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
lifecycleScope.launch {
|
||||||
builder.setTitle(getString(R.string.cookies))
|
var item = withContext(Dispatchers.IO){
|
||||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
cookiesViewModel.getByURL(url)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle the negative button of the alert dialog
|
|
||||||
builder.setNegativeButton(
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
getString(R.string.cancel)
|
builder.setTitle(getString(R.string.cookies))
|
||||||
) { dialog: DialogInterface?, which: Int -> }
|
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
||||||
val dialog = builder.create()
|
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||||
editText.doOnTextChanged { text, start, before, count ->
|
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.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 {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ class MoreFragment : Fragment() {
|
||||||
private lateinit var commandTemplates: TextView
|
private lateinit var commandTemplates: TextView
|
||||||
private lateinit var downloadQueue: TextView
|
private lateinit var downloadQueue: TextView
|
||||||
private lateinit var cookies: TextView
|
private lateinit var cookies: TextView
|
||||||
|
private lateinit var observeSources: TextView
|
||||||
private lateinit var terminateApp: TextView
|
private lateinit var terminateApp: TextView
|
||||||
private lateinit var settings: TextView
|
private lateinit var settings: TextView
|
||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
|
|
@ -58,6 +59,7 @@ class MoreFragment : Fragment() {
|
||||||
commandTemplates = view.findViewById(R.id.command_templates)
|
commandTemplates = view.findViewById(R.id.command_templates)
|
||||||
downloadQueue = view.findViewById(R.id.download_queue)
|
downloadQueue = view.findViewById(R.id.download_queue)
|
||||||
cookies = view.findViewById(R.id.cookies)
|
cookies = view.findViewById(R.id.cookies)
|
||||||
|
observeSources = view.findViewById(R.id.observe_sources)
|
||||||
terminateApp = view.findViewById(R.id.terminate)
|
terminateApp = view.findViewById(R.id.terminate)
|
||||||
settings = view.findViewById(R.id.settings)
|
settings = view.findViewById(R.id.settings)
|
||||||
|
|
||||||
|
|
@ -82,6 +84,10 @@ class MoreFragment : Fragment() {
|
||||||
findNavController().navigate(R.id.cookiesFragment)
|
findNavController().navigate(R.id.cookiesFragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observeSources.setOnClickListener {
|
||||||
|
findNavController().navigate(R.id.observeSourcesFragment)
|
||||||
|
}
|
||||||
|
|
||||||
terminateApp.setOnClickListener {
|
terminateApp.setOnClickListener {
|
||||||
if (mainSharedPreferences.getBoolean("ask_terminate_app", true)){
|
if (mainSharedPreferences.getBoolean("ask_terminate_app", true)){
|
||||||
var doNotShowAgain = false
|
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.app.ActionBar
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context.CLIPBOARD_SERVICE
|
import android.content.Context.CLIPBOARD_SERVICE
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
|
@ -13,10 +14,12 @@ import android.widget.HorizontalScrollView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
||||||
|
|
@ -39,6 +42,7 @@ class DownloadLogFragment : Fragment() {
|
||||||
private lateinit var copyLog : ExtendedFloatingActionButton
|
private lateinit var copyLog : ExtendedFloatingActionButton
|
||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
private lateinit var logViewModel: LogViewModel
|
private lateinit var logViewModel: LogViewModel
|
||||||
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
@ -47,6 +51,7 @@ class DownloadLogFragment : Fragment() {
|
||||||
): View? {
|
): View? {
|
||||||
mainActivity = activity as MainActivity
|
mainActivity = activity as MainActivity
|
||||||
mainActivity.hideBottomNavigation()
|
mainActivity.hideBottomNavigation()
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
return inflater.inflate(R.layout.fragment_download_log, container, false)
|
return inflater.inflate(R.layout.fragment_download_log, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,9 +146,13 @@ class DownloadLogFragment : Fragment() {
|
||||||
slider?.apply {
|
slider?.apply {
|
||||||
this.valueFrom = 0f
|
this.valueFrom = 0f
|
||||||
this.valueTo = 10f
|
this.valueTo = 10f
|
||||||
this.value = 2f
|
this.value = sharedPreferences.getFloat("log_zoom", 2f)
|
||||||
|
content.setCustomTextSize(this.value + 13f)
|
||||||
this.addOnChangeListener { slider, value, fromUser ->
|
this.addOnChangeListener { slider, value, fromUser ->
|
||||||
content.setCustomTextSize(value + 13f)
|
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.PreferenceManager
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.deniscerri.ytdlnis.R
|
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.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
|
@ -278,10 +279,15 @@ class TerminalFragment : Fragment() {
|
||||||
slider?.apply {
|
slider?.apply {
|
||||||
this.valueFrom = 0f
|
this.valueFrom = 0f
|
||||||
this.valueTo = 10f
|
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 ->
|
this.addOnChangeListener { slider, value, fromUser ->
|
||||||
output?.setCustomTextSize(value + 13f)
|
output?.setCustomTextSize(value + 13f)
|
||||||
input?.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
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -11,25 +12,26 @@ import android.graphics.drawable.shapes.OvalShape
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
|
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewOutlineProvider
|
import android.view.ViewOutlineProvider
|
||||||
|
import android.view.animation.Interpolator
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ScrollView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Px
|
import androidx.annotation.Px
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.whenStarted
|
|
||||||
import androidx.lifecycle.withStarted
|
import androidx.lifecycle.withStarted
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.neo.highlight.core.Highlight
|
import com.neo.highlight.core.Highlight
|
||||||
import com.neo.highlight.util.listener.HighlightTextWatcher
|
import com.neo.highlight.util.listener.HighlightTextWatcher
|
||||||
import com.neo.highlight.util.scheme.ColorScheme
|
import com.neo.highlight.util.scheme.ColorScheme
|
||||||
|
|
@ -37,7 +39,9 @@ import kotlinx.coroutines.launch
|
||||||
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Locale
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
object Extensions {
|
object Extensions {
|
||||||
|
|
@ -110,6 +114,7 @@ object Extensions {
|
||||||
drawable.paint.color = context.getColor(android.R.color.transparent)
|
drawable.paint.color = context.getColor(android.R.color.transparent)
|
||||||
|
|
||||||
FastScrollerBuilder(this)
|
FastScrollerBuilder(this)
|
||||||
|
.useMd2Style()
|
||||||
.setTrackDrawable(drawable)
|
.setTrackDrawable(drawable)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
@ -187,4 +192,41 @@ object Extensions {
|
||||||
this.setTextSize(TypedValue.COMPLEX_UNIT_SP, newSize)
|
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"
|
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 {
|
fun getDefaultTerminalPath() : String {
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + File.separator + "YTDLnis/TERMINAL_CACHE"
|
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.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.getIntByAny
|
import com.deniscerri.ytdlnis.util.Extensions.getIntByAny
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.getStringByAny
|
import com.deniscerri.ytdlnis.util.Extensions.getStringByAny
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
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"))
|
val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))
|
||||||
itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||||
itm?.playlistURL = "https://www.youtube.com/playlist?list=$id"
|
itm?.playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||||
itm?.playlistIndex = (i+1)
|
|
||||||
items.add(itm)
|
items.add(itm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,7 @@ class InfoUtil(private val context: Context) {
|
||||||
Html.fromHtml(obj.getString("uploaderName").toString()).toString()
|
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 thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg"
|
||||||
val formats : ArrayList<Format> = ArrayList()
|
val formats : ArrayList<Format> = ArrayList()
|
||||||
|
|
||||||
|
|
@ -206,6 +206,9 @@ class InfoUtil(private val context: Context) {
|
||||||
}else{
|
}else{
|
||||||
formatObj.format_note = formatObj.format_note + " Audio"
|
formatObj.format_note = formatObj.format_note + " Audio"
|
||||||
}
|
}
|
||||||
|
if (!formatObj.tbr.isNullOrBlank()){
|
||||||
|
formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k"
|
||||||
|
}
|
||||||
|
|
||||||
}catch (e: Exception) {
|
}catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -223,6 +226,9 @@ class InfoUtil(private val context: Context) {
|
||||||
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
val formatObj = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
try{
|
try{
|
||||||
formatObj.vcodec = format.getString("codec")
|
formatObj.vcodec = format.getString("codec")
|
||||||
|
if (!formatObj.tbr.isNullOrBlank()){
|
||||||
|
formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k"
|
||||||
|
}
|
||||||
}catch (e: Exception) {
|
}catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
@ -453,6 +459,10 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--compat-options", "manifest-filesize-approx")
|
request.addOption("--compat-options", "manifest-filesize-approx")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||||
|
request.addOption("-4")
|
||||||
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
|
|
@ -505,6 +515,10 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("-R", "1")
|
request.addOption("-R", "1")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||||
|
request.addOption("-4")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
|
|
@ -588,6 +602,10 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--compat-options", "manifest-filesize-approx")
|
request.addOption("--compat-options", "manifest-filesize-approx")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||||
|
request.addOption("-4")
|
||||||
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
|
|
@ -625,7 +643,7 @@ class InfoUtil(private val context: Context) {
|
||||||
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
||||||
var duration = jsonObject.getIntByAny("duration").toString()
|
var duration = jsonObject.getIntByAny("duration").toString()
|
||||||
if (duration != "-1"){
|
if (duration != "-1"){
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
||||||
}
|
}
|
||||||
|
|
||||||
var thumb: String? = ""
|
var thumb: String? = ""
|
||||||
|
|
@ -724,6 +742,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}catch (ignored: Exception){}
|
}catch (ignored: Exception){}
|
||||||
}
|
}
|
||||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||||
|
|
||||||
if (formatProper.format_note == null) continue
|
if (formatProper.format_note == null) continue
|
||||||
if (format.has("format_note")){
|
if (format.has("format_note")){
|
||||||
if (!formatProper!!.format_note.contains("audio only", true)) {
|
if (!formatProper!!.format_note.contains("audio only", true)) {
|
||||||
|
|
@ -736,10 +755,20 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
if (formatProper.format_note == "storyboard") continue
|
if (formatProper.format_note == "storyboard") continue
|
||||||
formatProper.container = format.getString("ext")
|
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)
|
formats.add(formatProper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -751,6 +780,10 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("-R", "1")
|
request.addOption("-R", "1")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||||
|
request.addOption("-4")
|
||||||
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
|
|
@ -780,7 +813,7 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
var duration = jsonObject.getIntByAny("duration").toString()
|
var duration = jsonObject.getIntByAny("duration").toString()
|
||||||
if (duration != "-1"){
|
if (duration != "-1"){
|
||||||
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
|
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
||||||
}
|
}
|
||||||
|
|
||||||
var thumb: String? = ""
|
var thumb: String? = ""
|
||||||
|
|
@ -871,19 +904,7 @@ class InfoUtil(private val context: Context) {
|
||||||
return duration
|
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?> {
|
fun getStreamingUrlAndChapters(url: String) : MutableList<String?> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -904,6 +925,10 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("-R", "1")
|
request.addOption("-R", "1")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
||||||
|
request.addOption("-4")
|
||||||
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
|
|
@ -955,11 +980,16 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
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)
|
YoutubeDLRequest(downloadItem.url)
|
||||||
}else{
|
}else{
|
||||||
YoutubeDLRequest(downloadItem.playlistURL!!).apply {
|
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 (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) request.addOption("--restrict-filenames")
|
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)){
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
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 preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!!
|
||||||
val aCodecPrefIndex = context.getStringArray(R.array.audio_codec_values).indexOf(preferredAudioCodec)
|
val aCodecPrefIndex = context.getStringArray(R.array.audio_codec_values).indexOf(preferredAudioCodec)
|
||||||
val aCodecPref = runCatching { context.getStringArray(R.array.audio_codec_values_ytdlp)[aCodecPrefIndex] }.getOrElse { "" }
|
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("--sub-format", "${subFormat}/best")
|
||||||
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
request.addOption("--embed-subs")
|
request.addOption("--embed-subs")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs){
|
if(downloadItem.videoPreferences.writeAutoSubs){
|
||||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
|
||||||
request.addOption("--write-auto-subs")
|
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){
|
if (downloadItem.videoPreferences.removeAudio){
|
||||||
request.addOption("--ppa", "ffmpeg:-an")
|
request.addOption("--ppa", "ffmpeg:-an")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,27 @@ class NotificationUtil(var context: Context) {
|
||||||
.build()
|
.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(
|
fun createDownloadServiceNotification(
|
||||||
pendingIntent: PendingIntent?,
|
pendingIntent: PendingIntent?,
|
||||||
title: String?,
|
title: String?,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import android.widget.Button
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
|
|
@ -33,14 +34,18 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.compose.ui.text.toUpperCase
|
import androidx.compose.ui.text.toUpperCase
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.setPadding
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
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.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
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.ConfigureDownloadBottomSheetDialog
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
|
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.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.BadgeDrawable
|
||||||
import com.google.android.material.badge.BadgeUtils
|
import com.google.android.material.badge.BadgeUtils
|
||||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
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.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
@ -396,6 +407,14 @@ object UiUtil {
|
||||||
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
|
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
|
||||||
item.apply {
|
item.apply {
|
||||||
findViewById<TextView>(R.id.file_name).text = file.nameWithoutExtension
|
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.filesize).text = FileUtil.convertFileSize(file.length())
|
||||||
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
|
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
|
||||||
if (!file.exists()){
|
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 ){
|
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
||||||
val currentDate = Calendar.getInstance()
|
val currentDate = Calendar.getInstance()
|
||||||
val date = Calendar.getInstance()
|
val date = Calendar.getInstance()
|
||||||
|
|
@ -869,6 +907,7 @@ object UiUtil {
|
||||||
val formatnoteParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_note_parent)
|
val formatnoteParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_note_parent)
|
||||||
val fpsParent = bottomSheet.findViewById<ConstraintLayout>(R.id.fps_parent)
|
val fpsParent = bottomSheet.findViewById<ConstraintLayout>(R.id.fps_parent)
|
||||||
val asrParent = bottomSheet.findViewById<ConstraintLayout>(R.id.asr_parent)
|
val asrParent = bottomSheet.findViewById<ConstraintLayout>(R.id.asr_parent)
|
||||||
|
val bitrateParent = bottomSheet.findViewById<ConstraintLayout>(R.id.bitrate_parent)
|
||||||
|
|
||||||
|
|
||||||
val clicker = View.OnClickListener {
|
val clicker = View.OnClickListener {
|
||||||
|
|
@ -954,6 +993,13 @@ object UiUtil {
|
||||||
asrParent?.setOnLongClickListener(longClicker)
|
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()
|
bottomSheet.show()
|
||||||
|
|
@ -1139,6 +1185,7 @@ object UiUtil {
|
||||||
cutClicked: (VideoCutListener) -> Unit,
|
cutClicked: (VideoCutListener) -> Unit,
|
||||||
filenameTemplateSet: (String) -> Unit,
|
filenameTemplateSet: (String) -> Unit,
|
||||||
saveSubtitlesClicked: (Boolean) -> Unit,
|
saveSubtitlesClicked: (Boolean) -> Unit,
|
||||||
|
saveAutoSubtitlesClicked: (Boolean) -> Unit,
|
||||||
subtitleLanguagesSet: (String) -> Unit,
|
subtitleLanguagesSet: (String) -> Unit,
|
||||||
removeAudioClicked: (Boolean) -> Unit,
|
removeAudioClicked: (Boolean) -> Unit,
|
||||||
extraCommandsClicked: () -> Unit,
|
extraCommandsClicked: () -> Unit,
|
||||||
|
|
@ -1148,6 +1195,7 @@ object UiUtil {
|
||||||
|
|
||||||
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
|
||||||
val saveSubtitles = view.findViewById<Chip>(R.id.save_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)
|
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
|
||||||
|
|
||||||
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
|
embedSubs!!.isChecked = items.all { it.videoPreferences.embedSubs }
|
||||||
|
|
@ -1173,6 +1221,8 @@ object UiUtil {
|
||||||
if (splitByChapters.isChecked){
|
if (splitByChapters.isChecked){
|
||||||
items.forEach { it.videoPreferences.addChapters = false }
|
items.forEach { it.videoPreferences.addChapters = false }
|
||||||
addChapters.isChecked = false
|
addChapters.isChecked = false
|
||||||
|
addChapters.isEnabled = false
|
||||||
|
addChaptersClicked(false)
|
||||||
}
|
}
|
||||||
splitByChapters.setOnClickListener {
|
splitByChapters.setOnClickListener {
|
||||||
if (splitByChapters.isChecked){
|
if (splitByChapters.isChecked){
|
||||||
|
|
@ -1230,7 +1280,7 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
val cut = view.findViewById<Chip>(R.id.cut)
|
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{
|
else{
|
||||||
if(items[0].duration.isNotEmpty()){
|
if(items[0].duration.isNotEmpty()){
|
||||||
val downloadItem = items[0]
|
val downloadItem = items[0]
|
||||||
|
|
@ -1306,6 +1356,10 @@ object UiUtil {
|
||||||
saveSubtitlesClicked(saveSubtitles.isChecked)
|
saveSubtitlesClicked(saveSubtitles.isChecked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveAutoSubtitles.setOnCheckedChangeListener { _, _ ->
|
||||||
|
saveAutoSubtitlesClicked(saveAutoSubtitles.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
subtitleLanguages.isEnabled = embedSubs.isChecked || saveSubtitles.isChecked
|
||||||
subtitleLanguages.setOnClickListener {
|
subtitleLanguages.setOnClickListener {
|
||||||
val currentSubtitleLang = if (items.size == 1 || items.all { it.videoPreferences.subsLanguages == items[0].videoPreferences.subsLanguages }){
|
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)
|
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{
|
else{
|
||||||
val downloadItem = items[0]
|
val downloadItem = items[0]
|
||||||
if (downloadItem.duration.isNotEmpty()){
|
if (downloadItem.duration.isNotEmpty()){
|
||||||
|
|
@ -1555,136 +1609,163 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun handleDownloadsResponse(context: Activity, lifecycleScope: CoroutineScope, supportFragmentManager: FragmentManager, it: DownloadViewModel.DownloadsUiState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){
|
fun handleExistingDownloadsResponse(context: Activity, lifecycleScope: CoroutineScope, supportFragmentManager: FragmentManager, it: DownloadViewModel.AlreadyExistsUIState, downloadViewModel: DownloadViewModel, historyViewModel: HistoryViewModel){
|
||||||
val downloadAnywayAction = it.actions?.first { it.second == DownloadViewModel.DownloadsAction.DOWNLOAD_ANYWAY}
|
if (it.downloadItems.isEmpty() && it.historyItems.isEmpty()) return
|
||||||
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 }
|
|
||||||
|
|
||||||
|
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 sheet = BottomSheetDialog(context)
|
||||||
val message = context.getString(it.errorMessage!!.second)
|
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)
|
var adapter: AlreadyExistsAdapter? = null
|
||||||
.setTitle(title)
|
val list = mutableListOf<Pair<DownloadItem, Long?>>()
|
||||||
.setMessage(message)
|
val alreadyExistsClickListener = object: AlreadyExistsAdapter.OnItemClickListener {
|
||||||
|
|
||||||
var dialog: Dialog? = null
|
override fun onEditItem(downloadItem: DownloadItem, position: Int) {
|
||||||
|
val resultItem = downloadViewModel.createResultItemFromDownload(downloadItem)
|
||||||
val linearLayout = context.layoutInflater.inflate(R.layout.already_exists_card, null) as LinearLayout
|
val onItemUpdated = object: ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
|
||||||
ids.forEachIndexed { index, id ->
|
override fun onDownloadItemUpdate(
|
||||||
val alreadyExistsItem = context.layoutInflater.inflate(R.layout.already_exists_item, null)
|
resultItemID: Long,
|
||||||
alreadyExistsItem.tag = id.toString()
|
item: DownloadItem
|
||||||
val ttle = alreadyExistsItem.findViewById<Button>(R.id.already_exists_title)
|
) {
|
||||||
ttle.text = "${index + 1}. ${titles[index]}"
|
val currentIndex = list.indexOfFirst { it.first.id == item.id }
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
val current = list[currentIndex]
|
||||||
val editBtn = alreadyExistsItem.findViewById<Button>(R.id.already_exists_edit)
|
list[currentIndex] = Pair(item, current.second)
|
||||||
var downloadItem: DownloadItem = downloadViewModel.getItemByID(id)
|
adapter?.submitList(list)
|
||||||
val historyItem: HistoryItem? = downloadViewModel.getHistoryItemById(id)
|
adapter?.notifyItemChanged(position)
|
||||||
|
|
||||||
if (historyItem != null){
|
|
||||||
val idx = downloads.indexOfFirst { it.id == historyItem.id }
|
|
||||||
downloadItem = downloadViewModel.createDownloadItemFromHistory(historyItem)
|
|
||||||
downloadItem.id = historyItem.id
|
|
||||||
downloads[idx] = downloadItem
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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){
|
withContext(Dispatchers.Main){
|
||||||
editBtn.visibility = View.VISIBLE
|
sheet.dismiss()
|
||||||
}
|
|
||||||
|
|
||||||
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 = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ttle.setOnLongClickListener {
|
|
||||||
showGenericDeleteDialog(context, ttle.text.toString(), accepted = {
|
|
||||||
linearLayout.removeView(alreadyExistsItem)
|
|
||||||
if (linearLayout.childCount == 0){
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
linearLayout.addView(alreadyExistsItem)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
errDialog.setView(linearLayout)
|
sheet.findViewById<Button>(R.id.bottomsheet_schedule_button)?.apply {
|
||||||
|
setOnClickListener {
|
||||||
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 ->
|
|
||||||
showDatePicker(supportFragmentManager) { calendar ->
|
showDatePicker(supportFragmentManager) { calendar ->
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val items = mutableListOf<DownloadItem>()
|
downloadViewModel.deleteProcessing()
|
||||||
linearLayout.children.forEach {view ->
|
val items = list.map { it.first }
|
||||||
val downloadItem = downloads.first { it.id == (view.tag as String).toLong()}
|
items.forEach {
|
||||||
downloadItem.downloadStartTime = calendar.timeInMillis
|
it.id = 0
|
||||||
downloadItem.id = 0
|
it.downloadStartTime = calendar.timeInMillis
|
||||||
items.add(downloadItem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val chunks = items.chunked(10)
|
downloadViewModel.queueDownloads(items, true)
|
||||||
for (c in chunks) {
|
|
||||||
downloadViewModel.queueDownloads(c, true)
|
|
||||||
}
|
|
||||||
val first = items.first()
|
val first = items.first()
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
val date = SimpleDateFormat(
|
||||||
withContext(Dispatchers.Main){
|
DateFormat.getBestDateTimePattern(
|
||||||
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
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.setView(view)
|
||||||
builder.setPositiveButton(
|
builder.setPositiveButton(
|
||||||
context.getString(R.string.dismiss)
|
context.getString(android.R.string.copy)
|
||||||
) { _: DialogInterface?, _: Int ->
|
) { _: DialogInterface?, _: Int ->
|
||||||
copyToClipboard(txt, context)
|
copyToClipboard(txt, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the negative button of the alert dialog
|
// handle the negative button of the alert dialog
|
||||||
builder.setNegativeButton(
|
builder.setNegativeButton(
|
||||||
context.getString(android.R.string.cancel)
|
context.getString(R.string.dismiss)
|
||||||
) { _: DialogInterface?, _: Int -> }
|
) { _: DialogInterface?, _: Int -> }
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
import com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers.AlarmStartReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.AlarmStopReceiver
|
import com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers.AlarmStopReceiver
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
class AlarmScheduler(private val context: Context) {
|
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.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
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.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
|
@ -228,7 +229,7 @@ class DownloadWorker(
|
||||||
val file = File(this)
|
val file = File(this)
|
||||||
var duration = downloadItem.duration
|
var duration = downloadItem.duration
|
||||||
val d = file.getMediaDuration(context)
|
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.filesize = file.length()
|
||||||
downloadItem.format.container = file.extension
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
android:layout_gravity="bottom|end">
|
android:layout_gravity="bottom|end">
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/copied_url_fab"
|
android:id="@+id/copied_url_fab"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="5dp"
|
android:text="@string/link_you_copied"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
android:contentDescription="@string/link_you_copied"
|
android:contentDescription="@string/link_you_copied"
|
||||||
android:src="@drawable/ic_clipboard"
|
app:icon="@drawable/ic_clipboard"
|
||||||
android:text="@string/download_all"
|
|
||||||
app:useCompatPadding="true" />
|
app:useCompatPadding="true" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -253,6 +254,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:text="@string/download_all"
|
android:text="@string/download_all"
|
||||||
app:icon="@drawable/ic_down" />
|
app:icon="@drawable/ic_down" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -243,18 +243,19 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
android:layout_gravity="bottom|end">
|
android:layout_gravity="bottom|end">
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/copied_url_fab"
|
android:id="@+id/copied_url_fab"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="5dp"
|
android:text="@string/link_you_copied"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
android:contentDescription="@string/link_you_copied"
|
android:contentDescription="@string/link_you_copied"
|
||||||
android:src="@drawable/ic_clipboard"
|
app:icon="@drawable/ic_clipboard"
|
||||||
android:text="@string/download_all"
|
|
||||||
app:useCompatPadding="true" />
|
app:useCompatPadding="true" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -264,6 +265,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:text="@string/download_all"
|
android:text="@string/download_all"
|
||||||
app:icon="@drawable/ic_down" />
|
app:icon="@drawable/ic_down" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,18 +232,19 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
android:layout_gravity="bottom|end">
|
android:layout_gravity="bottom|end">
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/copied_url_fab"
|
android:id="@+id/copied_url_fab"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="5dp"
|
android:text="@string/link_you_copied"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
android:contentDescription="@string/link_you_copied"
|
android:contentDescription="@string/link_you_copied"
|
||||||
android:src="@drawable/ic_clipboard"
|
app:icon="@drawable/ic_clipboard"
|
||||||
android:text="@string/download_all"
|
|
||||||
app:useCompatPadding="true" />
|
app:useCompatPadding="true" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -253,6 +254,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:text="@string/download_all"
|
android:text="@string/download_all"
|
||||||
app:icon="@drawable/ic_down" />
|
app:icon="@drawable/ic_down" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -262,18 +262,19 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
android:layout_gravity="bottom|end">
|
android:layout_gravity="bottom|end">
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/copied_url_fab"
|
android:id="@+id/copied_url_fab"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="5dp"
|
android:text="@string/link_you_copied"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
android:contentDescription="@string/link_you_copied"
|
android:contentDescription="@string/link_you_copied"
|
||||||
android:src="@drawable/ic_clipboard"
|
app:icon="@drawable/ic_clipboard"
|
||||||
android:text="@string/download_all"
|
|
||||||
app:useCompatPadding="true" />
|
app:useCompatPadding="true" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -283,6 +284,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:text="@string/download_all"
|
android:text="@string/download_all"
|
||||||
app:icon="@drawable/ic_down" />
|
app:icon="@drawable/ic_down" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,14 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="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
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/subtitle_languages"
|
android:id="@+id/subtitle_languages"
|
||||||
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout android:id="@+id/existing_list"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical">
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
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_width="match_parent"
|
||||||
android:layout_marginBottom="10dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
|
|
@ -23,22 +23,42 @@
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_marginEnd="10dp"
|
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_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<Button
|
<LinearLayout
|
||||||
android:id="@+id/already_exists_edit"
|
android:id="@+id/already_exists_buttons"
|
||||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:icon="@drawable/ic_edit"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/already_exists_title"
|
|
||||||
app:layout_constraintEnd_toEndOf="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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
||||||
|
|
@ -43,20 +43,24 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/bottom_sheet_title"
|
android:id="@+id/bottom_sheet_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/download"
|
android:text="@string/download"
|
||||||
android:textSize="25sp"
|
android:textSize="25sp"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:singleLine="false"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/bottom_sheet_subtitle"
|
android:id="@+id/bottom_sheet_subtitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/configure_download"
|
android:text="@string/configure_download"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,29 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent">
|
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
|
<TextView
|
||||||
android:id="@+id/filesize"
|
android:id="@+id/filesize"
|
||||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,41 @@
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:cornerRadius="10dp"
|
app:cornerRadius="10dp"
|
||||||
|
android:maxLength="10"
|
||||||
app:drawableStartCompat="@drawable/ic_music"
|
app:drawableStartCompat="@drawable/ic_music"
|
||||||
android:textCursorDrawable="@string/audio_format"
|
android:textCursorDrawable="@string/audio_format"
|
||||||
app:drawableTint="@color/white"
|
app:drawableTint="@color/white"
|
||||||
|
|
@ -178,6 +179,7 @@
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:gravity="bottom|end"
|
android:gravity="bottom|end"
|
||||||
android:maxLength="10"
|
android:maxLength="10"
|
||||||
|
android:ellipsize="end"
|
||||||
app:cornerRadius="10dp"
|
app:cornerRadius="10dp"
|
||||||
app:layout_constraintVertical_bias="1.0"
|
app:layout_constraintVertical_bias="1.0"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@
|
||||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
app:icon="@drawable/baseline_align_horizontal_left_24"
|
app:icon="@drawable/baseline_align_horizontal_left_24"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/format_refresh"
|
app:layout_constraintBottom_toBottomOf="@id/format_refresh"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/format_refresh"
|
app:layout_constraintEnd_toStartOf="@+id/format_refresh"
|
||||||
|
|
@ -77,6 +76,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:autoLink="all"
|
android:autoLink="all"
|
||||||
android:text="@string/update_formats"
|
android:text="@string/update_formats"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
app:icon="@drawable/ic_refresh"
|
app:icon="@drawable/ic_refresh"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
@ -88,6 +88,7 @@
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
android:autoLink="all"
|
android:autoLink="all"
|
||||||
android:text="@string/ok"
|
android:text="@string/ok"
|
||||||
app:icon="@drawable/ic_check"
|
app:icon="@drawable/ic_check"
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,16 @@
|
||||||
|
|
||||||
<View style="@style/Divider.Vertical"/>
|
<View style="@style/Divider.Vertical"/>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/not_deleted_chip"
|
||||||
|
style="@style/Widget.Material3.Chip.Filter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/not_deleted"/>
|
||||||
|
|
||||||
|
<View android:id="@+id/notdeleted_divider" style="@style/Divider.Vertical"/>
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
app:selectionRequired="false"
|
app:selectionRequired="false"
|
||||||
|
|
|
||||||
|
|
@ -161,18 +161,19 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
android:layout_gravity="bottom|end">
|
android:layout_gravity="bottom|end">
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/copied_url_fab"
|
android:id="@+id/copied_url_fab"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="5dp"
|
android:text="@string/link_you_copied"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
android:contentDescription="@string/link_you_copied"
|
android:contentDescription="@string/link_you_copied"
|
||||||
android:src="@drawable/ic_clipboard"
|
app:icon="@drawable/ic_clipboard"
|
||||||
android:text="@string/download_all"
|
|
||||||
app:useCompatPadding="true" />
|
app:useCompatPadding="true" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -181,8 +182,8 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:text="@string/download_all"
|
android:text="@string/download_all"
|
||||||
app:icon="@drawable/ic_down" />
|
app:icon="@drawable/ic_down" />
|
||||||
|
|
||||||
|
|
@ -193,8 +194,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_margin="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
app:icon="@drawable/ic_down"
|
app:icon="@drawable/ic_down"
|
||||||
android:text="@string/download"
|
android:text="@string/download"
|
||||||
app:srcCompat="@drawable/ic_music"/>
|
app:srcCompat="@drawable/ic_music"/>
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,21 @@
|
||||||
android:paddingHorizontal="15dp"
|
android:paddingHorizontal="15dp"
|
||||||
app:drawableStartCompat="@drawable/baseline_cookie_24" />
|
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
|
<TextView
|
||||||
android:id="@+id/terminate"
|
android:id="@+id/terminate"
|
||||||
android:layout_width="match_parent"
|
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:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:drawablePadding="20dp"
|
android:drawablePadding="20dp"
|
||||||
|
android:maxLength="100"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,10 @@
|
||||||
android:icon="@drawable/ic_delete_all"
|
android:icon="@drawable/ic_delete_all"
|
||||||
android:title="@string/clear_saved"
|
android:title="@string/clear_saved"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/copy_urls"
|
||||||
|
android:icon="@drawable/ic_delete_all"
|
||||||
|
android:title="@string/copy_urls"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|
@ -29,6 +29,10 @@
|
||||||
android:icon="@drawable/baseline_cookie_24"
|
android:icon="@drawable/baseline_cookie_24"
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
android:id="@+id/cookiesFragment"/>
|
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"
|
<item android:title="@string/kill_app"
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
android:icon="@drawable/ic_power"
|
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
|
<action
|
||||||
android:id="@+id/action_moreFragment_to_terminalActivity"
|
android:id="@+id/action_moreFragment_to_terminalActivity"
|
||||||
app:destination="@id/terminalActivity" />
|
app:destination="@id/terminalActivity" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_moreFragment_to_observeSourcesFragment"
|
||||||
|
app:destination="@id/observeSourcesFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<activity
|
<activity
|
||||||
android:id="@+id/terminalActivity"
|
android:id="@+id/terminalActivity"
|
||||||
|
|
@ -163,9 +166,29 @@
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/downloadMultipleBottomSheetDialog2"
|
android:id="@+id/downloadMultipleBottomSheetDialog2"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog"
|
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
|
<dialog
|
||||||
android:id="@+id/cutVideoBottomSheetDialog"
|
android:id="@+id/cutVideoBottomSheetDialog"
|
||||||
android:name="com.deniscerri.ytdlnis.ui.downloadcard.CutVideoBottomSheetDialog"
|
android:name="com.deniscerri.ytdlnis.ui.downloadcard.CutVideoBottomSheetDialog"
|
||||||
android:label="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>
|
</navigation>
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
<string name="sponsorblock_intro">ئینترۆ</string>
|
<string name="sponsorblock_intro">ئینترۆ</string>
|
||||||
<string name="sponsorblock_fillers">فیلتەرەکان</string>
|
<string name="sponsorblock_fillers">فیلتەرەکان</string>
|
||||||
<string name="sponsorblock_reminders">بیرخەرەوەکانی بەشداربوون</string>
|
<string name="sponsorblock_reminders">بیرخەرەوەکانی بەشداربوون</string>
|
||||||
<string name="cancel_download">پاشگەزبوونەوە</string>
|
<string name="cancel_task">پاشگەزبوونەوە</string>
|
||||||
<string name="logs">تۆمارەکان</string>
|
<string name="logs">تۆمارەکان</string>
|
||||||
<string name="audio_quality">کوالێتی دەنگ</string>
|
<string name="audio_quality">کوالێتی دەنگ</string>
|
||||||
<string name="audio_format">فۆرماتی دەنگ</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