diff --git a/app/build.gradle b/app/build.gradle
index f9888e51..0360c55b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -19,7 +19,7 @@ if (versionBuild > 0){
android {
namespace 'com.deniscerri.ytdlnis'
- compileSdk 33
+ compileSdk 34
try {
def propertiesFile = rootProject.file("local.properties")
@@ -128,7 +128,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appCompatVer"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
- implementation 'com.google.android.material:material:1.9.0-rc01'
+ implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.preference:preference-ktx:1.2.1'
diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/10.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/10.json
new file mode 100644
index 00000000..385a6ccb
--- /dev/null
+++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/10.json
@@ -0,0 +1,544 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 10,
+ "identityHash": "6b5efd73749a7138035e1f00349715c8",
+ "entities": [
+ {
+ "tableName": "results",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `creationTime` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumb",
+ "columnName": "thumb",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "playlistTitle",
+ "columnName": "playlistTitle",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "formats",
+ "columnName": "formats",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "urls",
+ "columnName": "urls",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "chapters",
+ "columnName": "chapters",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "creationTime",
+ "columnName": "creationTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "history",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadId` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumb",
+ "columnName": "thumb",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadPath",
+ "columnName": "downloadPath",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadId",
+ "columnName": "downloadId",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "downloads",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumb",
+ "columnName": "thumb",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "container",
+ "columnName": "container",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'Default'"
+ },
+ {
+ "fieldPath": "downloadSections",
+ "columnName": "downloadSections",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "allFormats",
+ "columnName": "allFormats",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadPath",
+ "columnName": "downloadPath",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadSize",
+ "columnName": "downloadSize",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "playlistTitle",
+ "columnName": "playlistTitle",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "audioPreferences",
+ "columnName": "audioPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "videoPreferences",
+ "columnName": "videoPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "extraCommands",
+ "columnName": "extraCommands",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "customFileNameTemplate",
+ "columnName": "customFileNameTemplate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "SaveThumb",
+ "columnName": "SaveThumb",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'Queued'"
+ },
+ {
+ "fieldPath": "downloadStartTime",
+ "columnName": "downloadStartTime",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "logID",
+ "columnName": "logID",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "commandTemplates",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "useAsExtraCommand",
+ "columnName": "useAsExtraCommand",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "searchHistory",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "query",
+ "columnName": "query",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "templateShortcuts",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "cookies",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "logs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadType",
+ "columnName": "downloadType",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadTime",
+ "columnName": "downloadTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "terminalDownloads",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "command",
+ "columnName": "command",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6b5efd73749a7138035e1f00349715c8')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a4b577a0..8c04e1a9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -93,7 +93,7 @@
+
+
+
+
(1, TimeUnit.DAYS)
- .setConstraints(constraints)
- .build()
-
- WorkManager.getInstance(this@App).enqueueUniquePeriodicWork(
- "ytdlp-Updater",
- ExistingPeriodicWorkPolicy.KEEP,
- periodicWorkRequest
- )
- }else{
- WorkManager.getInstance(this@App).cancelUniqueWork("ytdlp-Updater")
- }
-
-
-
}catch (e: Exception){
Looper.prepare().runCatching {
Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show()
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt
index 35d934ab..8d263cd6 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt
@@ -34,6 +34,7 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
+import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@@ -51,9 +52,12 @@ import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.navigation.NavigationView
import com.google.android.material.navigationrail.NavigationRailView
+import com.google.android.material.snackbar.Snackbar
+import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.BufferedReader
@@ -233,6 +237,19 @@ class MainActivity : BaseActivity() {
"Queue" -> navController.navigate(R.id.downloadQueueMainFragment)
}
+ if (sharedPreferences.getBoolean("auto_update_ytdlp", false)){
+ CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
+ if(DBManager.getInstance(this@MainActivity).downloadDao.getDownloadsCountByStatus(listOf("Active", "Queued")) == 0){
+ if (UpdateUtil(this@MainActivity).updateYoutubeDL() == YoutubeDL.UpdateStatus.DONE) {
+ val version = YoutubeDL.getInstance().version(context)
+ Snackbar.make(findViewById(android.R.id.content),
+ this@MainActivity.getString(R.string.ytld_update_success) + " [${version}]",
+ Snackbar.LENGTH_LONG).show()
+ }
+ }
+
+ }
+ }
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
@@ -350,8 +367,7 @@ class MainActivity : BaseActivity() {
Toast.makeText(context, "Couldn't read file", Toast.LENGTH_LONG).show()
return
}
- val item = downloadViewModel.createEmptyResultItem(f.absolutePath)
- val bottomSheet = DownloadBottomSheetDialog(item, DownloadViewModel.Type.command, null, true)
+ val bottomSheet = DownloadBottomSheetDialog(type = DownloadViewModel.Type.command, result = downloadViewModel.createEmptyResultItem(f.absolutePath))
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
}else{
val `is` = contentResolver.openInputStream(uri!!)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/TerminalDownloadsAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/TerminalDownloadsAdapter.kt
new file mode 100644
index 00000000..46ce9e83
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/TerminalDownloadsAdapter.kt
@@ -0,0 +1,89 @@
+package com.deniscerri.ytdlnis.adapter
+
+import android.app.Activity
+import android.content.SharedPreferences
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import com.deniscerri.ytdlnis.R
+import com.deniscerri.ytdlnis.database.models.TerminalItem
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.card.MaterialCardView
+import com.google.android.material.progressindicator.LinearProgressIndicator
+
+class TerminalDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
+ private val onItemClickListener: OnItemClickListener
+ private val activity: Activity
+ private val sharedPreferences: SharedPreferences
+
+ init {
+ this.onItemClickListener = onItemClickListener
+ this.activity = activity
+ sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
+ }
+
+ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val cardView: MaterialCardView
+
+ init {
+ cardView = itemView.findViewById(R.id.active_download_card_view)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val cardView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.active_terminal_card, parent, false)
+ return ViewHolder(cardView)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = getItem(position)
+ val card = holder.cardView
+ card.tag = "${item!!.id}##card"
+
+ // PROGRESS BAR ----------------------------------------------------
+ val progressBar = card.findViewById(R.id.progress)
+ progressBar.tag = "${item.id}##progress"
+ progressBar.progress = 0
+ progressBar.isIndeterminate = true
+
+ // COMMAND ----------------------------------
+ val itemTitle = card.findViewById(R.id.title)
+ itemTitle.text = item.command.trim()
+
+ //OUTPUT
+ val output = card.findViewById(R.id.output)
+ output.tag = "${item.id}##output"
+
+ // STOP BUTTON ----------------------------------
+ val stopButton = card.findViewById(R.id.active_download_stop)
+ if (stopButton.hasOnClickListeners()) stopButton.setOnClickListener(null)
+ stopButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)}
+
+ card.setOnClickListener {
+ onItemClickListener.onCardClick(item)
+ }
+ }
+ interface OnItemClickListener {
+ fun onCancelClick(itemID: Long)
+ fun onCardClick(item: TerminalItem)
+ }
+ companion object {
+ private val DIFF_CALLBACK: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: TerminalItem, newItem: TerminalItem): Boolean {
+ val ranged = arrayListOf(oldItem.id, newItem.id)
+ return ranged[0] == ranged[1]
+ }
+
+ override fun areContentsTheSame(oldItem: TerminalItem, newItem: TerminalItem): Boolean {
+ return oldItem.command == newItem.command
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
index 70357a75..7b936abc 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
@@ -12,8 +12,18 @@ import kotlinx.coroutines.flow.Flow
@TypeConverters(Converters::class)
@Database(
- entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class, CookieItem::class, LogItem::class],
- version = 9,
+ entities = [
+ ResultItem::class,
+ HistoryItem::class,
+ DownloadItem::class,
+ CommandTemplate::class,
+ SearchHistoryItem::class,
+ TemplateShortcut::class,
+ CookieItem::class,
+ LogItem::class,
+ TerminalItem::class
+ ],
+ version = 10,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@@ -22,7 +32,8 @@ import kotlinx.coroutines.flow.Flow
AutoMigration (from = 5, to = 6),
AutoMigration (from = 6, to = 7),
AutoMigration (from = 7, to = 8),
- AutoMigration (from = 8, to = 9)
+ AutoMigration (from = 8, to = 9),
+ AutoMigration (from = 9, to = 10)
]
)
abstract class DBManager : RoomDatabase(){
@@ -33,6 +44,7 @@ abstract class DBManager : RoomDatabase(){
abstract val searchHistoryDao: SearchHistoryDao
abstract val cookieDao: CookieDao
abstract val logDao: LogDao
+ abstract val terminalDao: TerminalDao
companion object {
//prevents multiple instances of db getting created at the same time
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt
index eabfa80b..3fedfefe 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt
@@ -20,7 +20,10 @@ interface DownloadDao {
fun getActiveDownloadsCountFlow() : Flow
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
- fun getDownloadsCountByStatus(status : List) : Flow
+ fun getDownloadsCountByStatusFlow(status : List) : Flow
+
+ @Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
+ fun getDownloadsCountByStatus(status : List) : Int
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
fun getActiveAndPausedDownloadsList() : List
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt
new file mode 100644
index 00000000..f9ccf873
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/TerminalDao.kt
@@ -0,0 +1,28 @@
+package com.deniscerri.ytdlnis.database.dao
+
+import androidx.paging.PagingSource
+import androidx.room.*
+import com.deniscerri.ytdlnis.database.models.DownloadItem
+import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
+import com.deniscerri.ytdlnis.database.models.Format
+import com.deniscerri.ytdlnis.database.models.TerminalItem
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface TerminalDao {
+
+ @Query("SELECT * FROM terminalDownloads ORDER BY id")
+ fun getActiveTerminalDownloads() : List
+
+ @Query("SELECT * FROM terminalDownloads ORDER BY id")
+ fun getActiveTerminalDownloadsFlow() : Flow>
+
+ @Query("SELECT COUNT(*) FROM terminalDownloads")
+ fun getActiveTerminalsCount() : Int
+
+ @Insert
+ suspend fun insert(item: TerminalItem) : Long
+
+ @Query("DELETE from terminalDownloads WHERE id=:id")
+ suspend fun delete(id: Long)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/TerminalItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/TerminalItem.kt
new file mode 100644
index 00000000..fc5433a4
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/TerminalItem.kt
@@ -0,0 +1,13 @@
+package com.deniscerri.ytdlnis.database.models
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
+
+@Entity(tableName = "terminalDownloads")
+data class TerminalItem(
+ @PrimaryKey(autoGenerate = true)
+ var id: Long = 0,
+ var command: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
index 128d2763..4d8d8589 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/ResultRepository.kt
@@ -33,36 +33,28 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
suspend fun search(inputQuery: String, resetResults: Boolean) : ArrayList{
val infoUtil = InfoUtil(context)
- try{
- if (resetResults) deleteAll()
- val res = infoUtil.search(inputQuery)
- itemCount.value = res.size
- res.forEach {
- resultDao.insert(it!!)
- }
- return res
- }catch (ignored: Exception){}
- return arrayListOf()
+ if (resetResults) deleteAll()
+ val res = infoUtil.search(inputQuery)
+ itemCount.value = res.size
+ res.forEach {
+ resultDao.insert(it!!)
+ }
+ return res
}
suspend fun getOne(inputQuery: String, resetResults: Boolean) : ArrayList{
val infoUtil = InfoUtil(context)
- try {
- val v = infoUtil.getYoutubeVideo(inputQuery)
- if (resetResults) {
- deleteAll()
- itemCount.value = v.size
- }else{
- v.forEach { it?.playlistTitle = "ytdlnis-Search" }
- }
- v.forEach {
- resultDao.insert(it!!)
- }
- return ArrayList(v)
- } catch (e: Exception) {
- Log.e(tag, e.toString())
+ val v = infoUtil.getYoutubeVideo(inputQuery)
+ if (resetResults) {
+ deleteAll()
+ itemCount.value = v.size
+ }else{
+ v.forEach { it?.playlistTitle = "ytdlnis-Search" }
}
- return arrayListOf()
+ v.forEach {
+ resultDao.insert(it!!)
+ }
+ return ArrayList(v)
}
suspend fun getPlaylist(inputQuery: String, resetResults: Boolean) : ArrayList{
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
index e82b5f2a..694fa1af 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt
@@ -46,13 +46,9 @@ import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
-import java.text.SimpleDateFormat
-import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit
@@ -84,6 +80,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private var audioContainer: String?
private var videoContainer: String?
private var videoCodec: String?
+ private var audioCodec: String?
private val dao: DownloadDao
enum class Type {
@@ -155,6 +152,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
videoCodec = sharedPreferences.getString("video_codec", "")
+ audioCodec = sharedPreferences.getString("audio_codec", "")
}
fun deleteDownload(id: Long) = viewModelScope.launch(Dispatchers.IO) {
@@ -188,7 +186,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
}
- fun createDownloadItemFromResult(resultItem: ResultItem, givenType: Type) : DownloadItem {
+ fun createDownloadItemFromResult(result: ResultItem?, url: String = "", givenType: Type) : DownloadItem {
+ val resultItem = result ?: createEmptyResultItem(url)
+
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
val saveSubs = sharedPreferences.getBoolean("write_subtitles", false)
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
@@ -386,12 +386,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
Type.audio -> {
return cloneFormat (
try {
- try{
- formats.filter { it.format_note.contains("audio", ignoreCase = true)}
- .firstOrNull { audioFormatIDPreference.indexOfFirst { a -> a.contains(it.format_id) } >= 0 || it.container == audioContainer} ?: throw Exception()
- }catch (e: Exception){
- formats.first { it.format_note.contains("audio", ignoreCase = true) }
- }
+ val theFormats = formats.filter { it.format_note.contains("audio", ignoreCase = true) }
+ val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
+ requirements.add {it: Format -> audioFormatIDPreference.contains(it.format_id)}
+ requirements.add {it: Format -> it.container == audioContainer }
+ requirements.add {it: Format -> "^(${audioCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.acodec)}
+ theFormats.maxByOrNull { f -> requirements.count{req -> req(f)} } ?: throw Exception()
}catch (e: Exception){
bestAudioFormat
}
@@ -408,7 +408,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
else /*best*/ -> {
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
- requirements.add { it: Format -> formatIDPreference.sorted().contains(it.format_id) }
+ requirements.add { it: Format -> formatIDPreference.contains(it.format_id) }
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.split("_")[0].dropLast(1)) }
requirements.add { it: Format -> "^(${videoCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)}
@@ -451,7 +451,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
var preferredType = sharedPreferences.getString("preferred_download_type", "video")
items.forEach {
preferredType = getDownloadType(Type.valueOf(preferredType!!), it!!.url).toString()
- list.add(createDownloadItemFromResult(it, Type.valueOf(preferredType!!)))
+ list.add(createDownloadItemFromResult(result = it, givenType = Type.valueOf(preferredType!!)))
}
return list
}
@@ -656,7 +656,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
fun getTotalSize(status: List) : LiveData {
- return dbManager.downloadDao.getDownloadsCountByStatus(status.map { it.toString() }).asLiveData()
+ return dbManager.downloadDao.getDownloadsCountByStatusFlow(status.map { it.toString() }).asLiveData()
}
fun checkAllQueuedItemsAreScheduledAfterNow(items: List, inverted: Boolean, currentStartTime: Long) : Boolean {
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
index e5f234f1..32862b43 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/ResultViewModel.kt
@@ -64,18 +64,20 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
deleteAll()
}
}
- suspend fun parseQueries(inputQueries: List){
+ suspend fun parseQueries(inputQueries: List) : MutableList{
state = ResultsState.PROCESSING
- if (inputQueries.size == 1){
+ return if (inputQueries.size == 1){
parseQuery(inputQueries[0], true)
}else {
repository.itemCount.value = inputQueries.size
loadingItems.postValue(true)
+ val items = mutableListOf()
inputQueries.forEach {
- parseQuery(it, false)
+ items.addAll(parseQuery(it, false))
}
state = ResultsState.IDLE
loadingItems.postValue(false)
+ items
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/TerminalViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/TerminalViewModel.kt
new file mode 100644
index 00000000..736ca623
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/TerminalViewModel.kt
@@ -0,0 +1,110 @@
+package com.deniscerri.ytdlnis.database.viewmodel
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.net.ConnectivityManager
+import android.os.Looper
+import android.util.DisplayMetrics
+import android.util.Log
+import android.widget.Toast
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.viewModelScope
+import androidx.paging.PagingData
+import androidx.preference.PreferenceManager
+import androidx.work.Constraints
+import androidx.work.Data
+import androidx.work.ExistingWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.await
+import com.deniscerri.ytdlnis.App
+import com.deniscerri.ytdlnis.R
+import com.deniscerri.ytdlnis.database.DBManager
+import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
+import com.deniscerri.ytdlnis.database.dao.DownloadDao
+import com.deniscerri.ytdlnis.database.dao.ResultDao
+import com.deniscerri.ytdlnis.database.dao.TerminalDao
+import com.deniscerri.ytdlnis.database.models.AudioPreferences
+import com.deniscerri.ytdlnis.database.models.CommandTemplate
+import com.deniscerri.ytdlnis.database.models.DownloadItem
+import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
+import com.deniscerri.ytdlnis.database.models.Format
+import com.deniscerri.ytdlnis.database.models.HistoryItem
+import com.deniscerri.ytdlnis.database.models.ResultItem
+import com.deniscerri.ytdlnis.database.models.TerminalItem
+import com.deniscerri.ytdlnis.database.models.VideoPreferences
+import com.deniscerri.ytdlnis.database.repository.DownloadRepository
+import com.deniscerri.ytdlnis.util.FileUtil
+import com.deniscerri.ytdlnis.util.InfoUtil
+import com.deniscerri.ytdlnis.util.NotificationUtil
+import com.deniscerri.ytdlnis.work.AlarmScheduler
+import com.deniscerri.ytdlnis.work.DownloadWorker
+import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
+import com.google.gson.Gson
+import com.yausername.youtubedl_android.YoutubeDL
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import java.io.File
+import java.util.Locale
+import java.util.concurrent.TimeUnit
+
+
+class TerminalViewModel(private val application: Application) : AndroidViewModel(application) {
+ private val dbManager: DBManager = DBManager.getInstance(application)
+ private val dao: TerminalDao = dbManager.terminalDao
+ private val notificationUtil = NotificationUtil(application)
+ fun getCount() : Int{
+ return dao.getActiveTerminalsCount()
+ }
+
+ fun getTerminals() : Flow> {
+ return dao.getActiveTerminalDownloadsFlow()
+ }
+
+ suspend fun insert(item: TerminalItem) : Long {
+ return dao.insert(item)
+ }
+
+ suspend fun delete(id: Long) = CoroutineScope(Dispatchers.IO).launch{
+ dao.delete(id)
+ }
+
+ fun startTerminalDownloadWorker(item: TerminalItem) = CoroutineScope(Dispatchers.IO).launch {
+ val workRequest = OneTimeWorkRequestBuilder()
+ .setInputData(
+ Data.Builder()
+ .putInt("id", item.id.toInt())
+ .putString("command", item.command)
+ .build()
+ )
+ .addTag("terminal")
+ .addTag(item.id.toString())
+ .build()
+
+ WorkManager.getInstance(application).beginUniqueWork(
+ item.id.toString(),
+ ExistingWorkPolicy.KEEP,
+ workRequest
+ ).enqueue()
+ }
+
+ fun cancelTerminalDownload(id: Long) = CoroutineScope(Dispatchers.IO).launch{
+ YoutubeDL.getInstance().destroyProcessById(id.toString())
+ WorkManager.getInstance(application).cancelUniqueWork(id.toString())
+ Thread.sleep(200)
+ notificationUtil.cancelDownloadNotification(id.toInt())
+ delete(id)
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
index aae9bdf3..a51e66d8 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
@@ -108,17 +108,6 @@ class ShareActivity : BaseActivity() {
runCatching { supportFragmentManager.popBackStack() }
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
-
- val loadingBottomSheet = BottomSheetDialog(this)
- loadingBottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
- loadingBottomSheet.setContentView(R.layout.please_wait_bottom_sheet)
- loadingBottomSheet.show()
- val cancel = loadingBottomSheet.findViewById(R.id.cancel)
- cancel!!.setOnClickListener {
- resultViewModel.deleteAll()
- this.finish()
- }
-
val intentData = when(action){
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
else -> intent.dataString!!
@@ -132,15 +121,16 @@ class ShareActivity : BaseActivity() {
lifecycleScope.launch {
try {
- val result = resultViewModel.getItemByURL(inputQuery)
- loadingBottomSheet.dismiss()
+ val result = withContext(Dispatchers.IO){
+ resultViewModel.getItemByURL(inputQuery)
+ }
+ if (result == null) throw Exception()
showDownloadSheet(result)
}catch (e: Exception){
resultViewModel.deleteAll()
- if (quickDownload && matcher.matches()){
- val result = downloadViewModel.createEmptyResultItem(inputQuery)
- loadingBottomSheet.dismiss()
- showDownloadSheet(result)
+ showDownloadSheet(downloadViewModel.createEmptyResultItem(inputQuery))
+ if (matcher.matches()){
+
}else{
val res = withContext(Dispatchers.IO){
resultViewModel.parseQuery(inputQuery, true)
@@ -149,7 +139,6 @@ class ShareActivity : BaseActivity() {
Toast.makeText(this@ShareActivity, "No Results Found!", Toast.LENGTH_SHORT).show()
exit()
}else{
- loadingBottomSheet.dismiss()
if (res.size == 1){
showDownloadSheet(res[0]!!)
}else{
@@ -163,12 +152,17 @@ class ShareActivity : BaseActivity() {
}
private fun showDownloadSheet(it: ResultItem){
+ val downloadType = downloadViewModel.getDownloadType(DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!), it.url)
if (sharedPreferences.getBoolean("download_card", true)){
- val bottomSheet = DownloadBottomSheetDialog(it,downloadViewModel.getDownloadType(DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!), it.url), null, quickDownload)
+ val bottomSheet = DownloadBottomSheetDialog(
+ result = it,
+ type = downloadType)
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
}else{
lifecycleScope.launch(Dispatchers.IO){
- val downloadItem = downloadViewModel.createDownloadItemFromResult(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
+ val downloadItem = downloadViewModel.createDownloadItemFromResult(
+ result = it,
+ givenType = downloadType)
downloadViewModel.queueDownloads(listOf(downloadItem))
}
this.finish()
@@ -184,7 +178,9 @@ class ShareActivity : BaseActivity() {
val downloadItems = mutableListOf()
lifecycleScope.launch(Dispatchers.IO){
it.forEach { res ->
- val i = downloadViewModel.createDownloadItemFromResult(res!!, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
+ val i = downloadViewModel.createDownloadItemFromResult(
+ result = res!!,
+ givenType = DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
i.format = downloadViewModel.getLatestCommandTemplateAsFormat()
downloadItems.add(i)
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt
new file mode 100644
index 00000000..baaaa980
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/ErrorDialogActivity.kt
@@ -0,0 +1,57 @@
+package com.deniscerri.ytdlnis.ui
+
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.DialogInterface
+import android.graphics.drawable.ColorDrawable
+import android.os.Build
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import com.deniscerri.ytdlnis.R
+import com.deniscerri.ytdlnis.util.ThemeUtil
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.elevation.SurfaceColors
+
+open class ErrorDialogActivity : BaseActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ val title = intent.extras?.getString("title") ?: ""
+ val message = intent.extras?.getString("message") ?: ""
+
+ ThemeUtil.updateTheme(this)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets ->
+ v.setPadding(0, 0, 0, 0)
+ insets
+ }
+ window.run {
+ setBackgroundDrawable(ColorDrawable(0))
+ setLayout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT
+ )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
+ } else {
+ setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
+ }
+ }
+
+ val errDialog = MaterialAlertDialogBuilder(this)
+ .setTitle(title)
+ .setMessage(message)
+ .setIcon(R.drawable.ic_cancel)
+ .setPositiveButton(getString(R.string.copy_log)) { _: DialogInterface?, _: Int ->
+ val clipboard: ClipboardManager =
+ getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ clipboard.setText(message)
+ this.finish()
+ }.setOnDismissListener {
+ this.finish()
+ }
+ errDialog.show()
+ super.onCreate(savedInstanceState)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
index 1c9483ea..c71affa0 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt
@@ -6,8 +6,6 @@ import android.content.*
import android.content.Context.CLIPBOARD_SERVICE
import android.content.res.ColorStateList
import android.graphics.Color
-import android.graphics.drawable.ShapeDrawable
-import android.graphics.drawable.shapes.OvalShape
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -23,8 +21,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.children
import androidx.core.view.forEach
-import androidx.core.view.forEachIndexed
-import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@@ -45,7 +41,6 @@ import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
-import com.devbrackets.android.exomedia.core.video.mp.NativeVideoDelegate
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
@@ -61,7 +56,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.util.*
@@ -168,7 +162,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}else if (resultViewModel.repository.itemCount.value == 1){
if (sharedPreferences!!.getBoolean("download_card", true)){
if(it.size == 1 && quickLaunchSheet && parentFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
- showSingleDownloadSheet(it[0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
+ showSingleDownloadSheet(
+ it[0],
+ DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
+ )
}
}
}else{
@@ -212,7 +209,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
if (arguments?.getString("url") != null){
val url = requireArguments().getString("url")
if (inputQueries == null) inputQueries = mutableListOf()
- searchBar?.text = url
+ searchBar?.setText(url)
val argList = url!!.split("\n").toMutableList()
argList.removeAll(listOf("", null))
inputQueries!!.addAll(argList)
@@ -242,18 +239,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
}
- if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
- CoroutineScope(Dispatchers.IO).launch {
- val ids = arguments?.getLongArray("downloadIds")
- val items = mutableListOf()
- ids?.forEach {
- items.add(downloadViewModel.getItemByID(it))
- }
- val bottomSheet = DownloadMultipleBottomSheetDialog(items.toMutableList())
- bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
- }
- }
-
}
override fun onResume() {
@@ -267,7 +252,20 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}else{
arguments?.remove("url")
}
- arguments?.remove("showDownloadsWithUpdatedFormats")
+
+ if (arguments?.getBoolean("showDownloadsWithUpdatedFormats") == true){
+ arguments?.remove("showDownloadsWithUpdatedFormats")
+ CoroutineScope(Dispatchers.IO).launch {
+ val ids = arguments?.getLongArray("downloadIds")
+ val items = mutableListOf()
+ ids?.forEach {
+ items.add(downloadViewModel.getItemByID(it))
+ }
+ val bottomSheet = DownloadMultipleBottomSheetDialog(items.toMutableList())
+ bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
+ }
+ }
+
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
lifecycleScope.launch {
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
@@ -412,7 +410,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
R.id.delete_results -> {
resultViewModel.getTrending()
selectedObjects = ArrayList()
- searchBar!!.text = ""
+ searchBar!!.setText("")
downloadAllFabCoordinator!!.visibility = GONE
downloadFabs!!.visibility = GONE
}
@@ -538,7 +536,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
if (queryList.isEmpty()) return
if (queryList.size == 1){
- searchBar!!.text = searchView.text
+ searchBar!!.setText(searchView.text)
}
searchView.hide()
@@ -552,13 +550,18 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
Thread.sleep(300)
if(sharedPreferences!!.getBoolean("quick_download", false) || sharedPreferences!!.getString("preferred_download_type", "video") == "command"){
if (queryList.size == 1 && queryList.first().matches("(https?://(?:www\\.|(?!www))[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|www\\.[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|https?://(?:www\\.|(?!www))[a-zA-Z\\d]+\\.\\S{2,}|www\\.[a-zA-Z\\d]+\\.\\S{2,})".toRegex())){
- val resultItem = downloadViewModel.createEmptyResultItem(queryList.first())
if (sharedPreferences!!.getBoolean("download_card", true)) {
- showSingleDownloadSheet(resultItem, DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), true)
+ showSingleDownloadSheet(
+ resultItem = downloadViewModel.createEmptyResultItem(queryList.first()),
+ type = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
+ )
} else {
lifecycleScope.launch{
val downloadItem = withContext(Dispatchers.IO){
- downloadViewModel.createDownloadItemFromResult(resultItem, DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!))
+ downloadViewModel.createDownloadItemFromResult(
+ result = downloadViewModel.createEmptyResultItem(queryList.first()),
+ givenType = DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
+ )
}
downloadViewModel.queueDownloads(listOf(downloadItem))
}
@@ -585,11 +588,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
Log.e(TAG, resultsList!![0].toString() + " " + videoURL)
recyclerView!!.findViewWithTag("""${item?.url}##$type""")
if (sharedPreferences!!.getBoolean("download_card", true)) {
- showSingleDownloadSheet(item!!, type!!, false)
+ showSingleDownloadSheet(item!!, type!!)
} else {
lifecycleScope.launch{
val downloadItem = withContext(Dispatchers.IO){
- downloadViewModel.createDownloadItemFromResult(item!!, type!!)
+ downloadViewModel.createDownloadItemFromResult(
+ result = item!!,
+ givenType = type!!)
}
downloadViewModel.queueDownloads(listOf(downloadItem))
}
@@ -599,11 +604,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
override fun onLongButtonClick(videoURL: String, type: DownloadViewModel.Type?) {
Log.e(TAG, type.toString() + " " + videoURL)
val item = resultsList!!.find { it?.url == videoURL }
- showSingleDownloadSheet(item!!, type!!, false)
+ showSingleDownloadSheet(item!!, type!!)
}
- private fun showSingleDownloadSheet(resultItem : ResultItem, type: DownloadViewModel.Type, quickDownload: Boolean){
- val bottomSheet = DownloadBottomSheetDialog(resultItem, downloadViewModel.getDownloadType(type, resultItem.url), null, quickDownload)
+ private fun showSingleDownloadSheet(
+ resultItem: ResultItem,
+ type: DownloadViewModel.Type
+ ){
+ val bottomSheet = DownloadBottomSheetDialog(resultItem, downloadViewModel.getDownloadType(type, resultItem.url))
bottomSheet.show(parentFragmentManager, "downloadSingleSheet")
}
@@ -696,7 +704,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
R.id.download -> {
lifecycleScope.launch {
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
- showSingleDownloadSheet(selectedObjects!![0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
+ showSingleDownloadSheet(
+ selectedObjects!![0],
+ DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
+ )
}else{
val downloadList = withContext(Dispatchers.IO){
downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
@@ -704,7 +715,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
if (sharedPreferences!!.getBoolean("download_card", true)) {
if (selectedObjects!!.size == 1){
- showSingleDownloadSheet(selectedObjects!![0], DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!), false)
+ showSingleDownloadSheet(
+ selectedObjects!![0],
+ DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
+ )
}else{
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt
index fbcce7b7..060a69ab 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/AddExtraCommandsDialog.kt
@@ -77,6 +77,7 @@ class AddExtraCommandsDialog(private val item: DownloadItem?, private val callba
}
+ @SuppressLint("SetTextI18n")
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -143,9 +144,14 @@ class AddExtraCommandsDialog(private val item: DownloadItem?, private val callba
shortcuts.setOnClickListener {
lifecycleScope.launch {
if (shortcutCount > 0){
- UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel) {sh ->
- text.text.insert(text.selectionStart, " $sh ")
- }
+ UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel,
+ itemSelected = {sh ->
+ text.setText("${text.text} $sh")
+ },
+ itemRemoved = {removed ->
+ text.setText(text.text.replace("(${Regex.escape(removed)})(?!.*\\1)".toRegex(), ""))
+ text.setSelection(text.text.length)
+ })
}
}
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt
index f13fe9b1..baef007a 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ConfigureDownloadBottomSheetDialog.kt
@@ -5,7 +5,6 @@ import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.util.DisplayMetrics
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -24,19 +23,16 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
-import com.deniscerri.ytdlnis.util.FileUtil
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.tabs.TabLayout
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-
-class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, private val downloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
+//TODO MAKE CONFIGURE LIKE NORMAL SHEET
+class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private val currentDownloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
private lateinit var tabLayout: TabLayout
private lateinit var viewPager2: ViewPager2
private lateinit var fragmentAdapter : DownloadFragmentAdapter
@@ -80,17 +76,12 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
overScrollMode = View.OVER_SCROLL_NEVER
}
- val fragments = mutableListOf(
- DownloadAudioFragment(resultItem, downloadItem),
- DownloadVideoFragment(resultItem, downloadItem)
- )
+
var commandTemplateNr = 0
lifecycleScope.launch{
withContext(Dispatchers.IO){
commandTemplateNr = commandTemplateDao.getTotalNumber()
- if(commandTemplateNr > 0){
- fragments.add(DownloadCommandFragment(resultItem, downloadItem))
- }else{
+ if(commandTemplateNr <= 0){
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
}
@@ -98,7 +89,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
}
//check if the item has formats and its audio-only
- val isAudioOnly = resultItem.formats.isNotEmpty() && resultItem.formats.none { !it.format_note.contains("audio") }
+ val isAudioOnly = result.formats.isNotEmpty() && result.formats.none { !it.format_note.contains("audio") }
if (isAudioOnly){
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.isClickable = true
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
@@ -107,15 +98,15 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter(
- resultItem,
fragmentManager,
lifecycle,
- fragments
+ result,
+ currentDownloadItem
)
viewPager2.adapter = fragmentAdapter
viewPager2.isSaveFromParentEnabled = false
- when(downloadItem.type) {
+ when(currentDownloadItem.type) {
Type.audio -> {
tabLayout.selectTab(tabLayout.getTabAt(0))
viewPager2.setCurrentItem(0, false)
@@ -172,17 +163,17 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
val ok = view.findViewById