alot of stuff
- include search history when searching - removed scroll bug from command tab - added spacing between command template title and ok button in selection card - made download progress not dublicate in terminal - made ability to store terminal state - added ability to create multiple terminal instances and show them as a list similar to download queue - fixed thumbnail download not working - fixed app crashing when clicking on format updated notification - fixed app crashing when double clicking format on multiple download card - added custom sponsorblock api preference - removed contextual app bar when you its enabled and the user taps the log button in the erorred tab - made app always show quick download card and asynchoronously load data. Quick Download now if its on, it wont load data at all - added shimmer when loading data in the download card - fixed app showing no formats if there were no common formats. Now it will give you generic formats - made open command template list be half the screen, shortcuts third of the screen so the user can see what its being added - fixed sometimes app slipping queued downloads even though its told to pause all - fixed trim filenames cutting files too short - made mediastore scanning of files one by one - fixed filename template not working in multiple download card - fixed -F in terminal not being inline - added preferred audio codec - made auto update on boot if there are no active downloads - fixed format text overlapping - added a new error activity dialog in cases yt-dlp data fetching in the home screen fails. You can copy the log
This commit is contained in:
parent
a31f3f0804
commit
25a09e7604
68 changed files with 2332 additions and 763 deletions
|
|
@ -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'
|
||||
|
|
|
|||
544
app/schemas/com.deniscerri.ytdlnis.database.DBManager/10.json
Normal file
544
app/schemas/com.deniscerri.ytdlnis.database.DBManager/10.json
Normal file
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.more.TerminalActivity"
|
||||
android:name=".ui.more.terminal.TerminalActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:exported="true"
|
||||
android:label="@string/terminal"
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
|
||||
<activity-alias
|
||||
android:name=".terminalShareAlias"
|
||||
android:targetActivity=".ui.more.TerminalActivity"
|
||||
android:targetActivity=".ui.more.terminal.TerminalActivity"
|
||||
android:enabled="false"
|
||||
android:icon="@mipmap/terminal_icon"
|
||||
android:roundIcon="@mipmap/terminal_icon_round"
|
||||
|
|
@ -211,6 +211,16 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".ui.ErrorDialogActivity"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:theme="@style/Theme.BottomSheet">
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@ import androidx.work.ExistingPeriodicWorkPolicy
|
|||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.yausername.aria2c.Aria2c
|
||||
import com.yausername.ffmpeg.FFmpeg
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
@ -44,34 +48,10 @@ class App : Application() {
|
|||
initLibraries()
|
||||
val appVer = sharedPreferences.getString("version", "")!!
|
||||
if(appVer.isEmpty() || appVer != BuildConfig.VERSION_NAME){
|
||||
UpdateUtil(this@App).updateYoutubeDL()
|
||||
sharedPreferences.edit(commit = true){
|
||||
putString("version", BuildConfig.VERSION_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("auto_update_ytdlp", true)){
|
||||
//init yt-dlp auto update with work request
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
|
||||
val periodicWorkRequest = PeriodicWorkRequestBuilder<UpdateYTDLWorker>(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()
|
||||
|
|
|
|||
|
|
@ -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!!)
|
||||
|
|
|
|||
|
|
@ -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<TerminalItem?, TerminalDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
private val activity: Activity
|
||||
private val sharedPreferences: SharedPreferences
|
||||
|
||||
init {
|
||||
this.onItemClickListener = onItemClickListener
|
||||
this.activity = activity
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val cardView: 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<LinearProgressIndicator>(R.id.progress)
|
||||
progressBar.tag = "${item.id}##progress"
|
||||
progressBar.progress = 0
|
||||
progressBar.isIndeterminate = true
|
||||
|
||||
// COMMAND ----------------------------------
|
||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||
itemTitle.text = item.command.trim()
|
||||
|
||||
//OUTPUT
|
||||
val output = card.findViewById<TextView>(R.id.output)
|
||||
output.tag = "${item.id}##output"
|
||||
|
||||
// STOP BUTTON ----------------------------------
|
||||
val stopButton = card.findViewById<MaterialButton>(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<TerminalItem> = object : DiffUtil.ItemCallback<TerminalItem>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ interface DownloadDao {
|
|||
fun getActiveDownloadsCountFlow() : Flow<Int>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||
fun getDownloadsCountByStatus(status : List<String>) : Flow<Int>
|
||||
fun getDownloadsCountByStatusFlow(status : List<String>) : Flow<Int>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||
fun getDownloadsCountByStatus(status : List<String>) : Int
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
|
||||
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
||||
|
|
|
|||
|
|
@ -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<TerminalItem>
|
||||
|
||||
@Query("SELECT * FROM terminalDownloads ORDER BY id")
|
||||
fun getActiveTerminalDownloadsFlow() : Flow<List<TerminalItem>>
|
||||
|
||||
@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)
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -33,36 +33,28 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
|
||||
suspend fun search(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
|
||||
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<ResultItem?>{
|
||||
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<ResultItem?>{
|
||||
|
|
|
|||
|
|
@ -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<DownloadRepository.Status>) : LiveData<Int> {
|
||||
return dbManager.downloadDao.getDownloadsCountByStatus(status.map { it.toString() }).asLiveData()
|
||||
return dbManager.downloadDao.getDownloadsCountByStatusFlow(status.map { it.toString() }).asLiveData()
|
||||
}
|
||||
|
||||
fun checkAllQueuedItemsAreScheduledAfterNow(items: List<Long>, inverted: Boolean, currentStartTime: Long) : Boolean {
|
||||
|
|
|
|||
|
|
@ -64,18 +64,20 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
deleteAll()
|
||||
}
|
||||
}
|
||||
suspend fun parseQueries(inputQueries: List<String>){
|
||||
suspend fun parseQueries(inputQueries: List<String>) : MutableList<ResultItem?>{
|
||||
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<ResultItem?>()
|
||||
inputQueries.forEach {
|
||||
parseQuery(it, false)
|
||||
items.addAll(parseQuery(it, false))
|
||||
}
|
||||
state = ResultsState.IDLE
|
||||
loadingItems.postValue(false)
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<List<TerminalItem>> {
|
||||
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<TerminalDownloadWorker>()
|
||||
.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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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<TextView>(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<DownloadItem>()
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DownloadItem>()
|
||||
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<DownloadItem>()
|
||||
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<MaterialButton>("""${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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Fragment>(
|
||||
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<Button>(R.id.bottom_sheet_ok)
|
||||
ok!!.setOnClickListener {
|
||||
val item = getDownloadItem()
|
||||
onDownloadItemUpdateListener.onDownloadItemUpdate(resultItem.id, item)
|
||||
onDownloadItemUpdateListener.onDownloadItemUpdate(result.id, item)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
link.text = resultItem.url
|
||||
link.text = result.url
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), resultItem.url, null)
|
||||
UiUtil.openLinkIntent(requireContext(), result.url, null)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), result.url, null)
|
||||
true
|
||||
}
|
||||
|
||||
|
|
@ -211,10 +202,10 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
|
|||
if (viewPager2.currentItem == 1) 0 else 1
|
||||
)
|
||||
|
||||
resultItem.title = prevDownloadItem.title
|
||||
resultItem.author = prevDownloadItem.author
|
||||
downloadItem.title = prevDownloadItem.title
|
||||
downloadItem.author = prevDownloadItem.author
|
||||
result.title = prevDownloadItem.title
|
||||
result.author = prevDownloadItem.author
|
||||
currentDownloadItem.title = prevDownloadItem.title
|
||||
currentDownloadItem.author = prevDownloadItem.author
|
||||
|
||||
when(viewPager2.currentItem){
|
||||
0 -> {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import android.widget.AdapterView
|
|||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.isEmpty
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -24,7 +25,6 @@ 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.databinding.FragmentHomeBinding
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
|
|
@ -37,7 +37,7 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
|
||||
|
||||
class DownloadAudioFragment(private var resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment(), TitleAuthorSync {
|
||||
class DownloadAudioFragment(private var resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "") : Fragment(), GUISync {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -69,18 +69,20 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO) {
|
||||
if (currentDownloadItem != null){
|
||||
//object cloning
|
||||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
downloadViewModel.createDownloadItemFromResult(resultItem, Type.audio)
|
||||
downloadViewModel.createDownloadItemFromResult(resultItem, url, Type.audio)
|
||||
}
|
||||
}
|
||||
val sharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
try {
|
||||
title = view.findViewById(R.id.title_textinput)
|
||||
title.editText!!.setText(downloadItem.title)
|
||||
if (title.editText?.text?.isEmpty() == true){
|
||||
title.editText!!.setText(downloadItem.title)
|
||||
}
|
||||
title.editText!!.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
|
|
@ -88,9 +90,17 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
downloadItem.title = p0.toString()
|
||||
}
|
||||
})
|
||||
title.setEndIconOnClickListener {
|
||||
if (resultItem != null){
|
||||
title.editText?.setText(resultItem?.title)
|
||||
}
|
||||
title.endIconDrawable = null
|
||||
}
|
||||
|
||||
author = view.findViewById(R.id.author_textinput)
|
||||
author.editText!!.setText(downloadItem.author)
|
||||
if (author.editText?.text?.isEmpty() == true){
|
||||
author.editText!!.setText(downloadItem.author)
|
||||
}
|
||||
author.editText!!.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
|
|
@ -98,6 +108,26 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
downloadItem.author = p0.toString()
|
||||
}
|
||||
})
|
||||
author.setEndIconOnClickListener {
|
||||
if (resultItem != null){
|
||||
author.editText?.setText(resultItem?.author)
|
||||
}
|
||||
author.endIconDrawable = null
|
||||
}
|
||||
|
||||
if (savedInstanceState?.containsKey("updated") == true){
|
||||
if (!listOf(resultItem?.title, downloadItem.title).contains(title.editText?.text.toString())){
|
||||
title.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
|
||||
downloadItem.title = title.editText?.text.toString()
|
||||
}
|
||||
|
||||
if (!listOf(resultItem?.author, downloadItem.author).contains(author.editText?.text.toString())){
|
||||
author.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
|
||||
downloadItem.author = author.editText?.text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
saveDir = view.findViewById(R.id.outputPath)
|
||||
saveDir.editText!!.setText(
|
||||
FileUtil.formatPath(downloadItem.downloadPath)
|
||||
|
|
@ -109,7 +139,18 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
audioPathResultLauncher.launch(intent)
|
||||
|
||||
val callback = FileUtil.getURIFromResult(this@DownloadAudioFragment, requireActivity()){result ->
|
||||
downloadItem.downloadPath = result
|
||||
//downloadViewModel.updateDownload(downloadItem)
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
}
|
||||
callback.launch(intent)
|
||||
}
|
||||
freeSpace = view.findViewById(R.id.freespace)
|
||||
val free = FileUtil.convertFileSize(
|
||||
|
|
@ -119,7 +160,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
|
||||
var formats = mutableListOf<Format>()
|
||||
if (currentDownloadItem == null) {
|
||||
formats.addAll(resultItem.formats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
||||
formats.addAll(resultItem?.formats?.filter { it.format_note.contains("audio", ignoreCase = true) } ?: listOf())
|
||||
}else{
|
||||
//if its updating a present downloaditem and its the wrong category
|
||||
if (currentDownloadItem!!.type != Type.audio){
|
||||
|
|
@ -153,9 +194,11 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format, null)
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultItem.formats.removeAll(formats.toSet())
|
||||
resultItem.formats.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) })
|
||||
resultViewModel.update(resultItem)
|
||||
resultItem?.formats?.removeAll(formats.toSet())
|
||||
resultItem?.formats?.addAll(allFormats.first().filter { !genericAudioFormats.contains(it) })
|
||||
if (resultItem != null){
|
||||
resultViewModel.update(resultItem!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
formats = allFormats.first().filter { !genericAudioFormats.contains(it) }.toMutableList()
|
||||
|
|
@ -209,7 +252,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
},
|
||||
sponsorBlockItemsSet = { values, checkedItems ->
|
||||
downloadItem.audioPreferences.sponsorBlockFilters.clear()
|
||||
for (i in 0 until checkedItems.size) {
|
||||
for (i in checkedItems.indices) {
|
||||
if (checkedItems[i]) {
|
||||
downloadItem.audioPreferences.sponsorBlockFilters.add(values[i])
|
||||
}
|
||||
|
|
@ -217,7 +260,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
},
|
||||
cutClicked = {cutVideoListener ->
|
||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
|
||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||
}
|
||||
},
|
||||
|
|
@ -242,29 +285,15 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
|||
downloadItem.title = t
|
||||
downloadItem.author = a
|
||||
title.editText?.setText(t)
|
||||
title.endIconDrawable = null
|
||||
author.editText?.setText(a)
|
||||
title.endIconDrawable = null
|
||||
}
|
||||
|
||||
private var audioPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
downloadItem.downloadPath = result.data?.data.toString()
|
||||
//downloadViewModel.updateDownload(downloadItem)
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result.data?.data.toString()), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
}
|
||||
override fun updateUI(res: ResultItem?){
|
||||
resultItem = res
|
||||
val state = Bundle()
|
||||
state.putBoolean("updated", true)
|
||||
onViewCreated(requireView(),savedInstanceState = state)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateFormat
|
||||
|
|
@ -15,17 +16,18 @@ import android.view.ViewGroup
|
|||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
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.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
|
|
@ -34,17 +36,19 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
|||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
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.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import kotlinx.coroutines.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val type: Type, private val downloadItem: DownloadItem?,private val quickDownload: Boolean) : BottomSheetDialogFragment() {
|
||||
class DownloadBottomSheetDialog(private var result: ResultItem, private val type: Type, private var currentDownloadItem: DownloadItem? = null) : BottomSheetDialogFragment() {
|
||||
private lateinit var tabLayout: TabLayout
|
||||
private lateinit var viewPager2: ViewPager2
|
||||
private lateinit var fragmentAdapter : DownloadFragmentAdapter
|
||||
|
|
@ -53,22 +57,22 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var commandTemplateDao : CommandTemplateDao
|
||||
private lateinit var infoUtil: InfoUtil
|
||||
private lateinit var sharedPreferences : SharedPreferences
|
||||
private lateinit var view: View
|
||||
|
||||
private lateinit var downloadAudioFragment: DownloadAudioFragment
|
||||
private lateinit var downloadVideoFragment: DownloadVideoFragment
|
||||
private lateinit var downloadCommandFragment: DownloadCommandFragment
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
||||
infoUtil = InfoUtil(requireContext())
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@SuppressLint("RestrictedApi", "InflateParams")
|
||||
override fun setupDialog(dialog: Dialog, style: Int) {
|
||||
super.setupDialog(dialog, style)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
|
||||
view = LayoutInflater.from(context).inflate(R.layout.download_bottom_sheet, null)
|
||||
dialog.setContentView(view)
|
||||
|
||||
dialog.setOnShowListener {
|
||||
|
|
@ -89,18 +93,12 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
}
|
||||
|
||||
downloadAudioFragment = DownloadAudioFragment(resultItem, downloadItem)
|
||||
downloadVideoFragment = DownloadVideoFragment(resultItem, downloadItem)
|
||||
val fragments = mutableListOf<Fragment>(downloadAudioFragment, downloadVideoFragment)
|
||||
var commandTemplateNr = 0
|
||||
lifecycleScope.launch{
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateNr = commandTemplateDao.getTotalNumber()
|
||||
if (!Patterns.WEB_URL.matcher(resultItem.url).matches()) commandTemplateNr++
|
||||
if(commandTemplateNr > 0){
|
||||
downloadCommandFragment = DownloadCommandFragment(resultItem, downloadItem)
|
||||
fragments.add(downloadCommandFragment)
|
||||
}else{
|
||||
if (!Patterns.WEB_URL.matcher(result.url).matches()) commandTemplateNr++
|
||||
if(commandTemplateNr <= 0){
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isClickable = true
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f
|
||||
}
|
||||
|
|
@ -108,14 +106,15 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
}
|
||||
|
||||
//check if the item has formats and its audio-only
|
||||
val isAudioOnly = resultItem.formats.isNotEmpty() && resultItem.formats.none { !it.format_note.contains("audio") }
|
||||
val formats = result.formats
|
||||
val isAudioOnly = formats.isNotEmpty() && 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
|
||||
}
|
||||
|
||||
//check if the item is coming from a text file
|
||||
val isCommandOnly = (type == Type.command && !Patterns.WEB_URL.matcher(resultItem.url).matches())
|
||||
val isCommandOnly = (type == Type.command && !Patterns.WEB_URL.matcher(result.url).matches())
|
||||
if (isCommandOnly){
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(0)?.isClickable = false
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(0)?.alpha = 0.3f
|
||||
|
|
@ -127,10 +126,10 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
|
||||
val fragmentManager = parentFragmentManager
|
||||
fragmentAdapter = DownloadFragmentAdapter(
|
||||
resultItem,
|
||||
fragmentManager,
|
||||
lifecycle,
|
||||
fragments
|
||||
result,
|
||||
currentDownloadItem
|
||||
)
|
||||
|
||||
viewPager2.adapter = fragmentAdapter
|
||||
|
|
@ -238,36 +237,23 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
|
||||
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
val updateItem = view.findViewById<Button>(R.id.update_item)
|
||||
if (Patterns.WEB_URL.matcher(resultItem.url).matches()){
|
||||
link.text = resultItem.url
|
||||
if (Patterns.WEB_URL.matcher(result.url).matches()){
|
||||
link.text = result.url
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), resultItem.url, null)
|
||||
UiUtil.openLinkIntent(requireContext(), result.url, null)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), resultItem.url, null)
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), result.url, null)
|
||||
true
|
||||
}
|
||||
|
||||
if (quickDownload) {
|
||||
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
|
||||
updateItem.setOnClickListener {
|
||||
if (activity is ShareActivity) {
|
||||
dismiss()
|
||||
val intent = Intent(context, ShareActivity::class.java)
|
||||
intent.action = Intent.ACTION_SEND
|
||||
intent.type = "text/plain"
|
||||
intent.putExtra(Intent.EXTRA_TEXT, resultItem.url)
|
||||
intent.putExtra("quick_download", false)
|
||||
startActivity(intent)
|
||||
}else{
|
||||
dismiss()
|
||||
val bundle = Bundle()
|
||||
bundle.putString("url", resultItem.url)
|
||||
findNavController().popBackStack(R.id.homeFragment, true)
|
||||
findNavController().navigate(
|
||||
R.id.homeFragment,
|
||||
bundle
|
||||
)
|
||||
//if auto-update after the card is open is off
|
||||
if (result.title.isEmpty() && currentDownloadItem == null) {
|
||||
if(sharedPreferences.getBoolean("quick_download", false)) {
|
||||
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
|
||||
updateItem.setOnClickListener {
|
||||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
initUpdateData(view)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
|
@ -278,6 +264,12 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
link.visibility = View.GONE
|
||||
updateItem.visibility = View.GONE
|
||||
}
|
||||
|
||||
|
||||
//update in the background if there is no data
|
||||
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false)){
|
||||
initUpdateData(view)
|
||||
}
|
||||
}
|
||||
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
|
||||
return when(selectedTabPosition){
|
||||
|
|
@ -300,11 +292,7 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
val prevDownloadItem = getDownloadItem(
|
||||
if (viewPager2.currentItem == 1) 0 else 1
|
||||
)
|
||||
|
||||
resultItem.title = prevDownloadItem.title
|
||||
resultItem.author = prevDownloadItem.author
|
||||
downloadItem?.title = prevDownloadItem.title
|
||||
downloadItem?.author = prevDownloadItem.author
|
||||
fragmentAdapter.setTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
|
||||
when(viewPager2.currentItem){
|
||||
0 -> {
|
||||
|
|
@ -319,6 +307,75 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
}
|
||||
}
|
||||
|
||||
private fun initUpdateData(v: View){
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
|
||||
val shimmerLoading = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_title)
|
||||
val title = v.findViewById<View>(R.id.bottom_sheet_title)
|
||||
|
||||
val shimmerLoadingSubtitle = v.findViewById<ShimmerFrameLayout>(R.id.shimmer_loading_subtitle)
|
||||
val subtitle = v.findViewById<View>(R.id.bottom_sheet_subtitle)
|
||||
withContext(Dispatchers.Main){
|
||||
title.visibility = View.GONE
|
||||
subtitle.visibility = View.GONE
|
||||
shimmerLoading.visibility = View.VISIBLE
|
||||
shimmerLoadingSubtitle.visibility = View.VISIBLE
|
||||
shimmerLoading.startShimmer()
|
||||
shimmerLoadingSubtitle.startShimmer()
|
||||
}
|
||||
|
||||
|
||||
if (result.url.isBlank()){
|
||||
withContext(Dispatchers.Main){dismiss()}
|
||||
}
|
||||
|
||||
val result = resultViewModel.parseQueries(listOf(result.url))
|
||||
if (result.isEmpty()){
|
||||
withContext(Dispatchers.Main){
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
if (result.size == 1 && result[0] != null){
|
||||
fragmentAdapter.setResultItem(result[0]!!)
|
||||
withContext(Dispatchers.Main){
|
||||
runCatching {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.updateUI(result[0])
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
|
||||
f1.updateUI(result[0])
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
title.visibility = View.VISIBLE
|
||||
subtitle.visibility = View.VISIBLE
|
||||
shimmerLoading.visibility = View.GONE
|
||||
shimmerLoadingSubtitle.visibility = View.GONE
|
||||
shimmerLoading.stopShimmer()
|
||||
shimmerLoadingSubtitle.stopShimmer()
|
||||
}
|
||||
|
||||
}else{
|
||||
//open multi download card instead
|
||||
if (activity is ShareActivity){
|
||||
val preferredType = DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!)
|
||||
withContext(Dispatchers.Main){
|
||||
val playlistSelect = SelectPlaylistItemsDialog(items = result, type = preferredType)
|
||||
parentFragmentManager.addFragmentOnAttachListener { fragmentManager, fragment ->
|
||||
dismiss()
|
||||
}
|
||||
playlistSelect.show(parentFragmentManager, "downloadMultipleSheet")
|
||||
}
|
||||
}else{
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
super.onCancel(dialog)
|
||||
cleanUp()
|
||||
|
|
@ -332,13 +389,11 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
|||
|
||||
private fun cleanUp(){
|
||||
kotlin.runCatching {
|
||||
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("downloadSingleSheet")!!).commit()
|
||||
parentFragmentManager.beginTransaction().remove(downloadVideoFragment).commit()
|
||||
parentFragmentManager.beginTransaction().remove(downloadVideoFragment).commit()
|
||||
if (this::downloadCommandFragment.isInitialized){
|
||||
parentFragmentManager.beginTransaction().remove(downloadCommandFragment).commit()
|
||||
repeat(parentFragmentManager.findFragmentByTag("downloadSingleSheet")?.fragmentManager?.backStackEntryCount ?: 0){
|
||||
parentFragmentManager.findFragmentByTag("downloadSingleSheet")?.fragmentManager?.popBackStack()
|
||||
}
|
||||
if (activity is ShareActivity){
|
||||
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("downloadSingleSheet")!!).commit()
|
||||
if (activity is ShareActivity && !parentFragmentManager.fragments.map { it.tag }.contains("downloadMultipleSheet")){
|
||||
(activity as ShareActivity).finish()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class DownloadCommandFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
|
||||
class DownloadCommandFragment(private val resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "") : Fragment() {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -73,7 +73,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
downloadViewModel.createDownloadItemFromResult(resultItem, DownloadViewModel.Type.command)
|
||||
downloadViewModel.createDownloadItemFromResult(resultItem, url, DownloadViewModel.Type.command)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,6 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
chosenCommandView.editText!!.setText("")
|
||||
}
|
||||
}
|
||||
chosenCommandView.editText!!.enableScrollText()
|
||||
|
||||
val commandTemplateCard = view.findViewById<MaterialCardView>(R.id.command_card)
|
||||
runCatching {
|
||||
|
|
@ -230,10 +229,19 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
}
|
||||
},
|
||||
shortcutClicked = {
|
||||
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel) { sh ->
|
||||
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $sh")
|
||||
downloadItem.format.format_note += " $sh"
|
||||
}
|
||||
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel,
|
||||
itemSelected = {
|
||||
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it")
|
||||
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString()
|
||||
},
|
||||
itemRemoved = {removed ->
|
||||
chosenCommandView.editText!!.setText(chosenCommandView.editText!!.text.replace("(${
|
||||
Regex.escape(
|
||||
removed
|
||||
)
|
||||
})(?!.*\\1)".toRegex(), "").trimEnd())
|
||||
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString().trimEnd()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -264,23 +272,4 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun EditText.enableScrollText()
|
||||
{
|
||||
overScrollMode = View.OVER_SCROLL_ALWAYS
|
||||
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||
isVerticalScrollBarEnabled = true
|
||||
setOnTouchListener { view, event ->
|
||||
if (view is EditText) {
|
||||
if(!view.text.isNullOrEmpty()) {
|
||||
view.parent.requestDisallowInterceptTouchEvent(true)
|
||||
when (event.action and MotionEvent.ACTION_MASK) {
|
||||
MotionEvent.ACTION_UP -> view.parent.requestDisallowInterceptTouchEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,21 +4,41 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
|
||||
class DownloadFragmentAdapter (
|
||||
private val resultItem: ResultItem,
|
||||
fragmentManager : FragmentManager,
|
||||
lifecycle : Lifecycle,
|
||||
private val fragments: List<Fragment>
|
||||
private var result: ResultItem,
|
||||
private var downloadItem: DownloadItem?
|
||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
|
||||
fun setResultItem(res: ResultItem){
|
||||
result = res
|
||||
}
|
||||
|
||||
fun setDownloadItem(dd: DownloadItem){
|
||||
downloadItem = dd
|
||||
}
|
||||
|
||||
fun setTitleAuthor(t: String, a: String){
|
||||
result.title = t
|
||||
result.author = a
|
||||
downloadItem?.title = t
|
||||
downloadItem?.author = a
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return fragments.size
|
||||
return 3
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return fragments[position]
|
||||
return when(position){
|
||||
0 -> DownloadAudioFragment(result, downloadItem)
|
||||
1 -> DownloadVideoFragment(result, downloadItem)
|
||||
else -> DownloadCommandFragment(result, downloadItem)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,23 +12,16 @@ import android.graphics.Color
|
|||
import android.os.Bundle
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.get
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
|
|
@ -41,7 +34,6 @@ import androidx.work.OneTimeWorkRequestBuilder
|
|||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
|
|
@ -62,13 +54,11 @@ import com.google.android.material.button.MaterialButton
|
|||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.reflect.Type
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -628,7 +618,6 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
|||
}
|
||||
|
||||
override fun onClick(p0: View?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -13,7 +14,7 @@ import android.widget.AdapterView
|
|||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -37,7 +38,7 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
|
||||
|
||||
class DownloadVideoFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment(), TitleAuthorSync {
|
||||
class DownloadVideoFragment(private var resultItem: ResultItem? = null, private var currentDownloadItem: DownloadItem? = null, private var url: String = "") : Fragment(), GUISync {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
|
@ -76,14 +77,16 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
|
||||
Gson().fromJson(string, DownloadItem::class.java)
|
||||
}else{
|
||||
downloadViewModel.createDownloadItemFromResult(resultItem, Type.video)
|
||||
downloadViewModel.createDownloadItemFromResult(resultItem, url, Type.video)
|
||||
}
|
||||
}
|
||||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
try {
|
||||
title = view.findViewById(R.id.title_textinput)
|
||||
title.editText!!.setText(downloadItem.title)
|
||||
if (title.editText?.text?.isEmpty() == true){
|
||||
title.editText!!.setText(downloadItem.title)
|
||||
}
|
||||
title.editText!!.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
|
|
@ -93,7 +96,9 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
})
|
||||
|
||||
author = view.findViewById(R.id.author_textinput)
|
||||
author.editText!!.setText(downloadItem.author)
|
||||
if (author.editText?.text?.isEmpty() == true){
|
||||
author.editText!!.setText(downloadItem.author)
|
||||
}
|
||||
author.editText!!.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
|
||||
|
|
@ -102,6 +107,18 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
}
|
||||
})
|
||||
|
||||
if (savedInstanceState?.containsKey("updated") == true){
|
||||
if (!listOf(resultItem?.title, downloadItem.title).contains(title.editText?.text.toString())){
|
||||
title.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
|
||||
downloadItem.title = title.editText?.text.toString()
|
||||
}
|
||||
|
||||
if (!listOf(resultItem?.author, downloadItem.author).contains(author.editText?.text.toString())){
|
||||
author.endIconDrawable = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_refresh)
|
||||
downloadItem.author = author.editText?.text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
saveDir = view.findViewById(R.id.outputPath)
|
||||
saveDir.editText!!.setText(
|
||||
FileUtil.formatPath(downloadItem.downloadPath)
|
||||
|
|
@ -113,7 +130,16 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
videoPathResultLauncher.launch(intent)
|
||||
val callback = FileUtil.getURIFromResult(this@DownloadVideoFragment, requireActivity()){ result ->
|
||||
downloadItem.downloadPath = result
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
}
|
||||
callback.launch(intent)
|
||||
}
|
||||
|
||||
freeSpace = view.findViewById(R.id.freespace)
|
||||
|
|
@ -125,7 +151,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
|
||||
var formats = mutableListOf<Format>()
|
||||
if (currentDownloadItem == null) {
|
||||
formats.addAll(resultItem.formats)
|
||||
formats.addAll(resultItem?.formats ?: listOf())
|
||||
}else{
|
||||
//if its updating a present downloaditem and its the wrong category
|
||||
if (currentDownloadItem!!.type != Type.video){
|
||||
|
|
@ -163,9 +189,11 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
}
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
resultItem.formats.removeAll(formats)
|
||||
resultItem.formats.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) })
|
||||
resultViewModel.update(resultItem)
|
||||
resultItem?.formats?.removeAll(formats)
|
||||
resultItem?.formats?.addAll(allFormats.first().filter { !genericVideoFormats.contains(it) })
|
||||
if (resultItem != null){
|
||||
resultViewModel.update(resultItem!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
formats = allFormats.first().filter { !genericVideoFormats.contains(it) }.toMutableList()
|
||||
|
|
@ -232,7 +260,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
},
|
||||
cutClicked = { cutVideoListener ->
|
||||
if (parentFragmentManager.findFragmentByTag("cutVideoSheet") == null){
|
||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem.urls, resultItem.chapters, cutVideoListener)
|
||||
val bottomSheet = CutVideoBottomSheetDialog(downloadItem, resultItem?.urls ?: "", resultItem?.chapters ?: listOf(), cutVideoListener)
|
||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||
}
|
||||
},
|
||||
|
|
@ -271,28 +299,17 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
|||
downloadItem.title = t
|
||||
downloadItem.author = a
|
||||
title.editText?.setText(t)
|
||||
title.endIconDrawable = null
|
||||
author.editText?.setText(a)
|
||||
title.endIconDrawable = null
|
||||
}
|
||||
|
||||
private var videoPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
downloadItem.downloadPath = result.data?.data.toString()
|
||||
saveDir.editText?.setText(FileUtil.formatPath(result.data?.data.toString()), TextView.BufferType.EDITABLE)
|
||||
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
}
|
||||
override fun updateUI(res: ResultItem?) {
|
||||
resultItem = res
|
||||
val state = Bundle()
|
||||
state.putBoolean("updated", true)
|
||||
onViewCreated(requireView(),savedInstanceState = state)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import android.util.DisplayMetrics
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.compose.runtime.invalidateGroupsWithKey
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -212,24 +213,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
selectedVideo =
|
||||
chosenFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.maxByOrNull { it.filesize }!!
|
||||
}
|
||||
|
||||
//simple video format selection
|
||||
if (items.size == 1){
|
||||
listener.onFormatClick(formats, listOf(FormatTuple(selectedVideo, selectedAudios)))
|
||||
}else{
|
||||
//playlist format selection
|
||||
val selectedFormats = mutableListOf<Format>()
|
||||
formatCollection.forEach {
|
||||
selectedFormats.add(it.first{ f -> f.format_id == selectedVideo.format_id})
|
||||
}
|
||||
if (selectedFormats.isEmpty()) {
|
||||
items.forEach { _ ->
|
||||
selectedFormats.add(selectedVideo)
|
||||
}
|
||||
}
|
||||
listener.onFormatClick(formats, selectedFormats.map { FormatTuple(it, selectedAudios) })
|
||||
}
|
||||
|
||||
returnFormats()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
|
|
@ -237,9 +221,30 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
refreshBtn.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun returnFormats(){
|
||||
//simple video format selection
|
||||
if (items.size == 1){
|
||||
listener.onFormatClick(formats, listOf(FormatTuple(selectedVideo, selectedAudios)))
|
||||
}else{
|
||||
//playlist format selection
|
||||
val selectedFormats = mutableListOf<Format>()
|
||||
formatCollection.forEach {
|
||||
selectedFormats.add(it.first{ f -> f.format_id == selectedVideo.format_id})
|
||||
}
|
||||
if (selectedFormats.isEmpty()) {
|
||||
items.forEach { _ ->
|
||||
selectedFormats.add(selectedVideo)
|
||||
}
|
||||
}
|
||||
listener.onFormatClick(formats, selectedFormats.map { FormatTuple(it, selectedAudios) })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun addFormatsToView(){
|
||||
//sort
|
||||
val finalFormats: List<Format> = when(sortBy){
|
||||
var finalFormats: List<Format> = when(sortBy){
|
||||
FormatSorting.container -> chosenFormats.groupBy { it.container }.flatMap { it.value }
|
||||
FormatSorting.id -> chosenFormats.sortedBy { it.format_id }
|
||||
FormatSorting.codec -> {
|
||||
|
|
@ -276,6 +281,14 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
}
|
||||
|
||||
if (finalFormats.isEmpty()){
|
||||
finalFormats = if (items.first()?.type == Type.audio){
|
||||
infoUtil.getGenericAudioFormats(requireContext().resources)
|
||||
}else{
|
||||
infoUtil.getGenericVideoFormats(requireContext().resources)
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 0.. finalFormats.lastIndex){
|
||||
val format = finalFormats[i]
|
||||
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
||||
|
|
@ -287,7 +300,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
val clickedCard = (clickedformat as MaterialCardView)
|
||||
if (format.vcodec.isNotBlank() && format.vcodec != "none") {
|
||||
if (clickedCard.isChecked) {
|
||||
listener.onFormatClick(formats, listOf(FormatTuple(format, null)))
|
||||
returnFormats()
|
||||
dismiss()
|
||||
}
|
||||
videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
|
||||
interface GUISync {
|
||||
fun updateTitleAuthor(t: String, a: String)
|
||||
fun updateUI(res: ResultItem?)
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import android.view.ViewGroup
|
|||
import android.view.Window
|
||||
import android.widget.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.map
|
||||
|
|
@ -87,7 +88,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
infoUtil = InfoUtil(requireActivity().applicationContext)
|
||||
infoUtil = InfoUtil(requireActivity())
|
||||
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
|
|
@ -229,15 +230,14 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
|
||||
downloadThumb.setOnClickListener {
|
||||
downloadManager.enqueue(
|
||||
DownloadManager.Request(Uri.parse(item.thumb))
|
||||
DownloadManager.Request(item.thumb.toUri())
|
||||
.setAllowedNetworkTypes(
|
||||
DownloadManager.Request.NETWORK_WIFI or
|
||||
DownloadManager.Request.NETWORK_MOBILE
|
||||
)
|
||||
.setAllowedOverRoaming(true)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setTitle(requireContext().getString(R.string.app_name))
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "YTDLnis"))
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "YTDLnis/" + item.title + ".jpg"))
|
||||
}
|
||||
|
||||
title.text = item.title
|
||||
|
|
@ -293,12 +293,14 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
|||
private fun onButtonClick(type: DownloadViewModel.Type){
|
||||
this.dismiss()
|
||||
if (sharedPreferences.getBoolean("download_card", true)) {
|
||||
val bottomSheet = DownloadBottomSheetDialog(item, type, null, false)
|
||||
val bottomSheet = DownloadBottomSheetDialog(item, type)
|
||||
bottomSheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
} else {
|
||||
lifecycleScope.launch{
|
||||
val downloadItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.createDownloadItemFromResult(item, type)
|
||||
downloadViewModel.createDownloadItemFromResult(
|
||||
result = item,
|
||||
givenType = type)
|
||||
}
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -164,13 +164,14 @@ class SelectPlaylistItemsDialog(private val items: List<ResultItem?>, private va
|
|||
val checkedResultItems = items.filter { item -> checkedItems.contains(item!!.url) }
|
||||
if (checkedResultItems.size == 1){
|
||||
val resultItem = resultViewModel.getItemByURL(checkedResultItems[0]!!.url)
|
||||
val bottomSheet = DownloadBottomSheetDialog(resultItem, type, null, false)
|
||||
val bottomSheet = DownloadBottomSheetDialog(resultItem, type)
|
||||
bottomSheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
}else{
|
||||
val downloadItems = mutableListOf<DownloadItem>()
|
||||
checkedResultItems.forEach { c ->
|
||||
c!!.id = 0
|
||||
val i = downloadViewModel.createDownloadItemFromResult(c,type)
|
||||
val i = downloadViewModel.createDownloadItemFromResult(
|
||||
result = c, givenType = type)
|
||||
if (type == DownloadViewModel.Type.command){
|
||||
i.format = downloadViewModel.getLatestCommandTemplateAsFormat()
|
||||
}
|
||||
|
|
@ -207,7 +208,7 @@ class SelectPlaylistItemsDialog(private val items: List<ResultItem?>, private va
|
|||
}
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
requireActivity().finishAffinity()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,9 +225,9 @@ class SelectPlaylistItemsDialog(private val items: List<ResultItem?>, private va
|
|||
private fun cleanup(){
|
||||
kotlin.runCatching {
|
||||
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("downloadPlaylistSheet")!!).commit()
|
||||
if (parentFragmentManager.fragments.size == 1){
|
||||
(activity as ShareActivity).finish()
|
||||
}
|
||||
}
|
||||
if (activity is ShareActivity){
|
||||
(activity as ShareActivity).finishAffinity()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||
|
||||
interface TitleAuthorSync {
|
||||
fun updateTitleAuthor(t: String, a: String)
|
||||
}
|
||||
|
|
@ -80,6 +80,14 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
workManager.cancelAllWorkByTag("download")
|
||||
pauseResume.isEnabled = false
|
||||
|
||||
// pause queued
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}.forEach {
|
||||
it.status = DownloadRepository.Status.QueuedPaused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
// pause active
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
|
|
@ -89,13 +97,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
// pause queued
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getQueued()
|
||||
}.forEach {
|
||||
it.status = DownloadRepository.Status.QueuedPaused.toString()
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
|
||||
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
pauseResume.isEnabled = true
|
||||
|
|
|
|||
|
|
@ -127,7 +127,11 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
|||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
val sheet = DownloadBottomSheetDialog(
|
||||
currentDownloadItem = it,
|
||||
result = downloadViewModel.createResultItemFromDownload(it),
|
||||
type = it.type
|
||||
)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {}
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
if (item.logID != null) {
|
||||
actionMode?.finish()
|
||||
val bundle = Bundle()
|
||||
bundle.putLong("logID", item.logID!!)
|
||||
findNavController().navigate(
|
||||
|
|
@ -129,7 +130,11 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
|||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
val sheet = DownloadBottomSheetDialog(
|
||||
currentDownloadItem = it,
|
||||
result = downloadViewModel.createResultItemFromDownload(it),
|
||||
type = it.type
|
||||
)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {}
|
||||
|
|
|
|||
|
|
@ -527,7 +527,9 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
|
||||
redownload.setOnLongClickListener {
|
||||
bottomSheet!!.cancel()
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromHistory(item), item.type, downloadViewModel.createDownloadItemFromHistory(item), false)
|
||||
val sheet = DownloadBottomSheetDialog(
|
||||
result = downloadViewModel.createResultItemFromHistory(item),
|
||||
type = item.type)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
true
|
||||
}
|
||||
|
|
@ -669,7 +671,9 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
ItemTouchHelper.RIGHT -> {
|
||||
val item = historyList!![position]!!
|
||||
historyAdapter!!.notifyItemChanged(position)
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromHistory(item), item.type, downloadViewModel.createDownloadItemFromHistory(item), false)
|
||||
val sheet = DownloadBottomSheetDialog(type = item.type,
|
||||
result = downloadViewModel.createResultItemFromHistory(item)
|
||||
)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,11 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
val sheet = DownloadBottomSheetDialog(
|
||||
currentDownloadItem = it,
|
||||
result = downloadViewModel.createResultItemFromDownload(it),
|
||||
type = it.type
|
||||
)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
|
|
|
|||
|
|
@ -127,7 +127,11 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
},
|
||||
longClickDownloadButton = { it: DownloadItem ->
|
||||
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(it), it.type, it, false)
|
||||
val sheet = DownloadBottomSheetDialog(
|
||||
currentDownloadItem = it,
|
||||
result = downloadViewModel.createResultItemFromDownload(it),
|
||||
type = it.type
|
||||
)
|
||||
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||
},
|
||||
scheduleButtonClick = {}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
|
|||
}
|
||||
|
||||
override fun onSelected(commandTemplate: CommandTemplate) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onDelete(commandTemplate: CommandTemplate) {
|
||||
|
|
|
|||
|
|
@ -187,7 +187,6 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
}
|
||||
|
||||
override fun onSelected(cookie: CookieItem) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onDelete(cookie: CookieItem) {
|
||||
|
|
|
|||
|
|
@ -19,15 +19,12 @@ import com.deniscerri.ytdlnis.R
|
|||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.more.settings.SettingsActivity
|
||||
import com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class MoreFragment : Fragment() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.more.settings
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import com.deniscerri.ytdlnis.R
|
||||
|
||||
|
||||
|
|
@ -10,9 +11,23 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
|
|||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.processing_preferences, rootKey)
|
||||
|
||||
val preferredVideoCodec : ListPreference? = findPreference("video_codec")
|
||||
val preferredAudioCodec : ListPreference? = findPreference("audio_codec")
|
||||
val preferredFormatID : EditTextPreference? = findPreference("format_id")
|
||||
val preferredFormatIDAudio : EditTextPreference? = findPreference("format_id_audio")
|
||||
|
||||
preferredVideoCodec?.summary = preferredVideoCodec?.entry
|
||||
preferredVideoCodec?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
preferredVideoCodec.summary = preferredVideoCodec.entries[preferredVideoCodec.findIndexOfValue(newValue.toString())]
|
||||
true
|
||||
}
|
||||
|
||||
preferredAudioCodec?.summary = preferredAudioCodec?.entry
|
||||
preferredAudioCodec?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
preferredAudioCodec.summary = preferredAudioCodec.entries[preferredAudioCodec.findIndexOfValue(newValue.toString())]
|
||||
true
|
||||
}
|
||||
|
||||
preferredFormatID?.title = "${getString(R.string.preferred_format_id)} [${getString(R.string.video)}]"
|
||||
preferredFormatID?.dialogTitle = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.terminal
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavArgument
|
||||
import androidx.navigation.NavGraph
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navArgument
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
class TerminalActivity : BaseActivity() {
|
||||
private lateinit var terminalViewModel: TerminalViewModel
|
||||
private lateinit var navHostFragment: NavHostFragment
|
||||
private var downloadID by Delegates.notNull<Long>()
|
||||
private lateinit var graph: NavGraph
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_terminal)
|
||||
terminalViewModel = ViewModelProvider(this)[TerminalViewModel::class.java]
|
||||
downloadID = savedInstanceState?.getLong("downloadID") ?: 0L
|
||||
handleIntent(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
handleIntent(intent)
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent) {
|
||||
val action = intent.action
|
||||
val type = intent.type
|
||||
Log.e(TAG, "$action $type")
|
||||
var text : String? = null
|
||||
if (action == Intent.ACTION_SEND && type != null) {
|
||||
Log.e(TAG, action)
|
||||
text = if (intent.getStringExtra(Intent.EXTRA_TEXT) == null){
|
||||
val uri = if (Build.VERSION.SDK_INT >= 33){
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
}else{
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
"-a \"${FileUtil.formatPath(uri?.path ?: "")}\""
|
||||
}else{
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
}
|
||||
}
|
||||
navHostFragment = supportFragmentManager.findFragmentById(R.id.frame_layout) as NavHostFragment
|
||||
graph = navHostFragment.navController.navInflater.inflate(R.navigation.terminal_graph)
|
||||
lifecycleScope.launch {
|
||||
val count = withContext(Dispatchers.IO){
|
||||
terminalViewModel.getCount()
|
||||
}
|
||||
val bundle = Bundle()
|
||||
if (count == 0){
|
||||
bundle.putString("share", text ?: "")
|
||||
graph.setStartDestination(R.id.terminalFragment)
|
||||
graph.findNode(graph.startDestinationId)?.addArgument("share", NavArgument.Builder()
|
||||
.setType(NavType.StringType)
|
||||
.setDefaultValue(text ?: "")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
navHostFragment.navController.setGraph(graph, bundle)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "TerminalActivity"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.terminal
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.addCallback
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkQuery
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.adapter.TerminalDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnItemClickListener {
|
||||
private var topAppBar: MaterialToolbar? = null
|
||||
private lateinit var terminalViewModel: TerminalViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
terminalViewModel = ViewModelProvider(this)[TerminalViewModel::class.java]
|
||||
return inflater.inflate(R.layout.fragment_terminal_download_list, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
lifecycleScope.launch {
|
||||
val recycler = view.findViewById<RecyclerView>(R.id.terminal_recycler)
|
||||
val adapter = TerminalDownloadsAdapter(this@TerminalDownloadsListFragment, requireActivity())
|
||||
recycler.adapter = adapter
|
||||
recycler.enableFastScroll()
|
||||
recycler.layoutManager = GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_size))
|
||||
topAppBar = requireActivity().findViewById(R.id.custom_command_toolbar)
|
||||
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
|
||||
topAppBar?.menu?.forEach { it.isVisible = false }
|
||||
topAppBar?.menu?.get(0)?.isVisible = true
|
||||
|
||||
topAppBar?.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when(m.itemId){
|
||||
R.id.add -> {
|
||||
findNavController().navigate(R.id.terminalFragment)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
terminalViewModel.getTerminals().collectLatest {
|
||||
adapter.submitList(it)
|
||||
if (it.isEmpty()){
|
||||
findNavController().navigate(R.id.terminalFragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData("terminal")
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (work == null) return@forEach
|
||||
val id = work.progress.getInt("id", 0)
|
||||
if(id == 0) return@forEach
|
||||
|
||||
val progress = work.progress.getInt("progress", 0)
|
||||
val output = work.progress.getString("output")
|
||||
|
||||
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||
val outputText = view.findViewWithTag<TextView>("$id##output")
|
||||
|
||||
requireActivity().runOnUiThread {
|
||||
kotlin.runCatching {
|
||||
progressBar?.setProgressCompat(progress, true)
|
||||
outputText?.text = output
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onCancelClick(itemID: Long) {
|
||||
terminalViewModel.cancelTerminalDownload(itemID)
|
||||
}
|
||||
|
||||
override fun onCardClick(item: TerminalItem) {
|
||||
val bundle = Bundle()
|
||||
bundle.putLong("id", item.id)
|
||||
findNavController().navigate(R.id.terminalFragment, bundle)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
package com.deniscerri.ytdlnis.ui.more
|
||||
package com.deniscerri.ytdlnis.ui.more.terminal
|
||||
|
||||
import android.app.ActionBar.LayoutParams
|
||||
import android.app.Activity
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.content.Context.INPUT_METHOD_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -23,22 +23,27 @@ import android.widget.ScrollView
|
|||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
|
||||
import com.deniscerri.ytdlnis.work.TerminalDownloadWorker
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -46,13 +51,13 @@ import com.yausername.youtubedl_android.YoutubeDL
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
class TerminalActivity : BaseActivity() {
|
||||
class TerminalFragment : Fragment() {
|
||||
private var topAppBar: MaterialToolbar? = null
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var terminalViewModel: TerminalViewModel
|
||||
private var output: TextView? = null
|
||||
private var input: EditText? = null
|
||||
private var fab: ExtendedFloatingActionButton? = null
|
||||
|
|
@ -60,35 +65,68 @@ class TerminalActivity : BaseActivity() {
|
|||
private lateinit var bottomAppBar: BottomAppBar
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private var downloadID by Delegates.notNull<Int>()
|
||||
private lateinit var downloadFile : File
|
||||
private var downloadID by Delegates.notNull<Long>()
|
||||
private lateinit var imm : InputMethodManager
|
||||
private lateinit var metrics: DisplayMetrics
|
||||
var context: Context? = null
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_terminal)
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
terminalViewModel = ViewModelProvider(this)[TerminalViewModel::class.java]
|
||||
downloadID = 0
|
||||
return inflater.inflate(R.layout.fragment_terminal, container, false)
|
||||
}
|
||||
|
||||
downloadID = savedInstanceState?.getInt("downloadID") ?: (System.currentTimeMillis().toInt() % 100000)
|
||||
downloadFile = File(cacheDir.absolutePath + "/$downloadID.txt")
|
||||
if (! downloadFile.exists()) downloadFile.createNewFile()
|
||||
imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
|
||||
context = baseContext
|
||||
scrollView = findViewById(R.id.custom_command_scrollview)
|
||||
topAppBar = findViewById(R.id.custom_command_toolbar)
|
||||
topAppBar!!.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString("input", input?.text.toString())
|
||||
outState.putString("output", output?.text.toString())
|
||||
outState.putBoolean("run", fab!!.text == getString(R.string.run_command))
|
||||
outState.putLong("downloadID", downloadID)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
arguments?.remove("id")
|
||||
arguments?.remove("share")
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
var bundle = savedInstanceState
|
||||
imm = requireActivity().getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
scrollView = view.findViewById(R.id.custom_command_scrollview)
|
||||
topAppBar = requireActivity().findViewById(R.id.custom_command_toolbar)
|
||||
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
|
||||
|
||||
input = view.findViewById(R.id.command_edittext)
|
||||
|
||||
if (arguments?.getLong("id") != null){
|
||||
downloadID = requireArguments().getLong("id")
|
||||
if(downloadID != 0L){
|
||||
input!!.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
if (arguments?.containsKey("share") == true){
|
||||
if (bundle == null){
|
||||
bundle = Bundle()
|
||||
}
|
||||
bundle.putString("input", arguments?.getString("share"))
|
||||
}
|
||||
|
||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
metrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(metrics)
|
||||
requireActivity().windowManager.defaultDisplay.getMetrics(metrics)
|
||||
|
||||
|
||||
bottomAppBar = findViewById(R.id.bottomAppBar)
|
||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||
var templateCount = 0
|
||||
var shortcutCount = 0
|
||||
|
||||
lifecycleScope.launch {
|
||||
templateCount = withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.getTotalNumber()
|
||||
|
|
@ -112,10 +150,10 @@ class TerminalActivity : BaseActivity() {
|
|||
when(it.itemId){
|
||||
R.id.command_templates -> {
|
||||
if (templateCount == 0){
|
||||
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(requireContext(), getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
lifecycleScope.launch {
|
||||
UiUtil.showCommandTemplates(this@TerminalActivity, commandTemplateViewModel){ templates ->
|
||||
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ templates ->
|
||||
templates.forEach {c ->
|
||||
input!!.text.insert(input!!.selectionStart, c.content + " ")
|
||||
}
|
||||
|
|
@ -130,9 +168,14 @@ class TerminalActivity : BaseActivity() {
|
|||
R.id.shortcuts -> {
|
||||
lifecycleScope.launch {
|
||||
if (shortcutCount > 0){
|
||||
UiUtil.showShortcuts(this@TerminalActivity, commandTemplateViewModel){sh ->
|
||||
input!!.text.insert(input!!.selectionStart, "$sh ")
|
||||
}
|
||||
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel,
|
||||
itemSelected = {sh ->
|
||||
input!!.setText("${input!!.text} $sh")
|
||||
},
|
||||
itemRemoved = {removed ->
|
||||
input!!.setText(input!!.text.replace("(${Regex.escape(removed)})(?!.*\\1)".toRegex(), "").trimEnd())
|
||||
input!!.setSelection(input!!.text.length)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,82 +191,55 @@ class TerminalActivity : BaseActivity() {
|
|||
true
|
||||
}
|
||||
|
||||
output = findViewById(R.id.custom_command_output)
|
||||
output = view.findViewById(R.id.custom_command_output)
|
||||
output!!.setTextIsSelectable(true)
|
||||
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
|
||||
input = findViewById(R.id.command_edittext)
|
||||
input!!.requestFocus()
|
||||
fab = findViewById(R.id.command_fab)
|
||||
fab = view.findViewById(R.id.command_fab)
|
||||
fab!!.setOnClickListener {
|
||||
if (fab!!.text == getString(R.string.run_command)){
|
||||
input!!.visibility = View.GONE
|
||||
output!!.text = "${output!!.text}\n~ $ ${input!!.text}\n"
|
||||
showCancelFab()
|
||||
imm.hideSoftInputFromWindow(input?.windowToken, 0)
|
||||
startDownload(
|
||||
input!!.text.toString()
|
||||
)
|
||||
lifecycleScope.launch {
|
||||
val command = input!!.text.toString().replace("yt-dlp", "")
|
||||
downloadID = withContext(Dispatchers.IO){
|
||||
terminalViewModel.insert(TerminalItem(command = command))
|
||||
}
|
||||
terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command))
|
||||
runWorkerListener()
|
||||
}
|
||||
}else {
|
||||
cancelDownload()
|
||||
terminalViewModel.cancelTerminalDownload(downloadID)
|
||||
input!!.visibility = View.VISIBLE
|
||||
hideCancelFab()
|
||||
}
|
||||
}
|
||||
notificationUtil = NotificationUtil(this)
|
||||
handleIntent(intent)
|
||||
|
||||
WorkManager.getInstance(this)
|
||||
.getWorkInfosForUniqueWorkLiveData(downloadID.toString())
|
||||
.observe(this){ list ->
|
||||
list.forEach {work ->
|
||||
if (work.state == WorkInfo.State.SUCCEEDED || work.state == WorkInfo.State.FAILED || work.state == WorkInfo.State.CANCELLED) {
|
||||
input!!.setText("yt-dlp ")
|
||||
input!!.visibility = View.VISIBLE
|
||||
input!!.requestFocus()
|
||||
input!!.setSelection(input!!.text.length)
|
||||
hideCancelFab()
|
||||
downloadFile.writeText("")
|
||||
}
|
||||
val outputData = work.progress.getString("output")
|
||||
if (!outputData.isNullOrBlank()){
|
||||
runOnUiThread{
|
||||
try {
|
||||
output!!.append("\n$outputData")
|
||||
output!!.scrollTo(0, output!!.height)
|
||||
scrollView!!.fullScroll(View.FOCUS_DOWN)
|
||||
}catch (ignored: Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
notificationUtil = NotificationUtil(requireContext())
|
||||
initMenu()
|
||||
|
||||
input?.enableTextHighlight()
|
||||
output?.enableTextHighlight()
|
||||
|
||||
input?.setText(savedInstanceState?.getString("input") ?: input?.text)
|
||||
input?.append(bundle?.getString("input") ?: "")
|
||||
input!!.requestFocus()
|
||||
input!!.setSelection(input!!.text.length)
|
||||
output?.text = savedInstanceState?.getString("output") ?: output?.text
|
||||
if (savedInstanceState?.getBoolean("run") == true){
|
||||
output?.text = bundle?.getString("output") ?: output?.text
|
||||
if (bundle?.getBoolean("run") == true){
|
||||
showCancelFab()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString("input", input?.text.toString())
|
||||
outState.putString("output", output?.text.toString())
|
||||
outState.putBoolean("run", fab!!.text == getString(R.string.run_command))
|
||||
outState.putInt("downloadID", downloadID)
|
||||
runWorkerListener()
|
||||
}
|
||||
|
||||
private fun initMenu() {
|
||||
topAppBar?.menu?.get(0)?.isVisible = false
|
||||
topAppBar?.menu?.get(1)?.isVisible = true
|
||||
topAppBar?.menu?.get(2)?.isVisible = true
|
||||
topAppBar?.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when(m.itemId){
|
||||
R.id.wrap -> {
|
||||
var scrollView = findViewById<HorizontalScrollView>(R.id.horizontalscroll_output)
|
||||
var scrollView = requireView().findViewById<HorizontalScrollView>(R.id.horizontalscroll_output)
|
||||
if(scrollView != null){
|
||||
val parent = (scrollView.parent as ViewGroup)
|
||||
scrollView.removeAllViews()
|
||||
|
|
@ -232,7 +248,7 @@ class TerminalActivity : BaseActivity() {
|
|||
}else{
|
||||
val parent = output?.parent as ViewGroup
|
||||
parent.removeView(output)
|
||||
scrollView = HorizontalScrollView(this)
|
||||
scrollView = HorizontalScrollView(requireContext())
|
||||
scrollView.layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
|
|
@ -244,7 +260,7 @@ class TerminalActivity : BaseActivity() {
|
|||
}
|
||||
R.id.export_clipboard -> {
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
val clipboard: ClipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipboard: ClipboardManager = requireActivity().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setText(output?.text)
|
||||
}
|
||||
}
|
||||
|
|
@ -252,39 +268,6 @@ class TerminalActivity : BaseActivity() {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
downloadFile.delete()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
handleIntent(intent)
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent) {
|
||||
val action = intent.action
|
||||
val type = intent.type
|
||||
Log.e(TAG, "$action $type")
|
||||
if (action == Intent.ACTION_SEND && type != null) {
|
||||
Log.e(TAG, action)
|
||||
val text = if (intent.getStringExtra(Intent.EXTRA_TEXT) == null){
|
||||
val uri = if (Build.VERSION.SDK_INT >= 33){
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
||||
}else{
|
||||
intent.getParcelableExtra(Intent.EXTRA_STREAM)
|
||||
}
|
||||
"-a \"${FileUtil.formatPath(uri?.path ?: "")}\""
|
||||
}else{
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
}
|
||||
val txt = "yt-dlp $text"
|
||||
input!!.setText(txt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun hideCancelFab() {
|
||||
fab!!.text = getString(R.string.run_command)
|
||||
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24)
|
||||
|
|
@ -299,7 +282,7 @@ class TerminalActivity : BaseActivity() {
|
|||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
contentResolver?.takePersistableUriPermission(
|
||||
requireActivity().contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
|
@ -309,34 +292,43 @@ class TerminalActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun startDownload(command: String?) {
|
||||
val cmd = if (command!!.contains("yt-dlp")) command.replace("yt-dlp", "")
|
||||
else command
|
||||
private fun runWorkerListener(){
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<TerminalDownloadWorker>()
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putInt("id", downloadID)
|
||||
.putString("command", cmd)
|
||||
.build()
|
||||
)
|
||||
.addTag("terminal")
|
||||
.build()
|
||||
WorkManager.getInstance(requireContext())
|
||||
.getWorkInfosByTagLiveData(downloadID.toString())
|
||||
.observe(viewLifecycleOwner){ list ->
|
||||
list.forEach {work ->
|
||||
if (listOf(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED).contains(work.state)) {
|
||||
requireActivity().runOnUiThread {
|
||||
kotlin.runCatching {
|
||||
input!!.setText("yt-dlp ")
|
||||
input!!.visibility = View.VISIBLE
|
||||
input!!.requestFocus()
|
||||
input!!.setSelection(input!!.text.length)
|
||||
hideCancelFab()
|
||||
}
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
val outputData = work.progress.getString("output")
|
||||
if (!outputData.isNullOrBlank()){
|
||||
val pieces = output!!.text.split("\n").toMutableList()
|
||||
val lastLine = pieces.last()
|
||||
requireActivity().runOnUiThread{
|
||||
kotlin.runCatching {
|
||||
showCancelFab()
|
||||
if (listOf(lastLine, outputData).all { it.contains("[download]") }){
|
||||
output!!.text = pieces.dropLast(1).joinToString("\n")
|
||||
}
|
||||
output!!.append("\n$outputData")
|
||||
output!!.scrollTo(0, output!!.height)
|
||||
scrollView!!.fullScroll(View.FOCUS_DOWN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorkManager.getInstance(this).beginUniqueWork(
|
||||
downloadID.toString(),
|
||||
ExistingWorkPolicy.KEEP,
|
||||
workRequest
|
||||
).enqueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelDownload() {
|
||||
YoutubeDL.getInstance().destroyProcessById(downloadID.toString())
|
||||
WorkManager.getInstance(this).cancelAllWorkByTag(downloadID.toString())
|
||||
notificationUtil.cancelDownloadNotification(downloadID)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CustomCommandActivity"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.Cursor
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
|
|
@ -10,15 +12,21 @@ import android.os.Environment
|
|||
import android.provider.DocumentsContract
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.net.toUri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.anggrayudi.storage.callback.FileCallback
|
||||
import com.anggrayudi.storage.callback.FolderCallback
|
||||
import com.anggrayudi.storage.file.copyFolderTo
|
||||
import com.anggrayudi.storage.file.getAbsolutePath
|
||||
import com.anggrayudi.storage.file.moveFileTo
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -250,7 +258,11 @@ object FileUtil {
|
|||
fun scanMedia(files: List<String>, context: Context) : List<String> {
|
||||
try {
|
||||
val paths = files.sortedByDescending { File(it).length() }
|
||||
runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) }
|
||||
runCatching {
|
||||
paths.forEach {
|
||||
MediaScannerConnection.scanFile(context, arrayOf(it), null, null)
|
||||
}
|
||||
}
|
||||
return paths
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
|
|
@ -296,4 +308,20 @@ object FileUtil {
|
|||
val symbols = DecimalFormatSymbols(Locale.US)
|
||||
return "${DecimalFormat("#,##0.#", symbols).format(s / 1024.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}"
|
||||
}
|
||||
|
||||
fun getURIFromResult(f: Fragment, a: FragmentActivity?, callback: (data: String) -> Unit) = f.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
a?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
callback(result.data?.data.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,49 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.DownloadManager
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.util.TypedValue
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.AndroidResources
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
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.LogItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
|
@ -48,20 +72,14 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
|
||||
fun search(query: String): ArrayList<ResultItem?> {
|
||||
return when(sharedPreferences.getString("search_engine", "ytsearch")){
|
||||
"ytsearch" -> try{
|
||||
searchFromPiped(query)
|
||||
}catch (e: Exception){
|
||||
getFromYTDL(query)
|
||||
return runCatching {
|
||||
when(sharedPreferences.getString("search_engine", "ytsearch")){
|
||||
"ytsearch" -> searchFromPiped(query)
|
||||
"ytsearchmusic" -> searchFromPipedMusic(query)
|
||||
else -> throw Exception()
|
||||
}
|
||||
|
||||
"ytsearchmusic" -> try{
|
||||
searchFromPipedMusic(query)
|
||||
}catch (e: Exception){
|
||||
getFromYTDL(query)
|
||||
}
|
||||
|
||||
else -> getFromYTDL(query)
|
||||
}.getOrElse {
|
||||
getFromYTDL(query)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -576,11 +594,9 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val proxy = sharedPreferences.getString("proxy", "")
|
||||
if (proxy!!.isNotBlank()){
|
||||
request.addOption("--proxy", proxy)
|
||||
request.addOption("--proxy", proxy)
|
||||
}
|
||||
|
||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||
|
|
@ -622,7 +638,7 @@ class InfoUtil(private val context: Context) {
|
|||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
||||
}
|
||||
}
|
||||
val website = if (jsonObject.has("ie_key")) jsonObject.getString("ie_key") else jsonObject.getString("extractor")
|
||||
val website = jsonObject.getString(listOf("ie_key", "extractor_key", "extractor").first { jsonObject.has(it) })
|
||||
var playlistTitle: String? = ""
|
||||
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
|
||||
if(playlistTitle.equals(query)) playlistTitle = ""
|
||||
|
|
@ -673,15 +689,17 @@ class InfoUtil(private val context: Context) {
|
|||
website,
|
||||
playlistTitle!!,
|
||||
formats,
|
||||
urls,
|
||||
urls,
|
||||
chapters
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Looper.prepare().run {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
val intent = Intent(context, ErrorDialogActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.putExtra("title", context.resources.getString(R.string.no_results))
|
||||
intent.putExtra("message", e.message)
|
||||
context.startActivity(intent)
|
||||
e.printStackTrace()
|
||||
}
|
||||
return items
|
||||
|
|
@ -975,21 +993,25 @@ class InfoUtil(private val context: Context) {
|
|||
if (aria2) {
|
||||
request.addOption("--downloader", "libaria2c.so")
|
||||
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
||||
} else {
|
||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||
}
|
||||
|
||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||
|
||||
val retries = sharedPreferences.getString("--retries", "")!!
|
||||
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!!
|
||||
|
||||
if(retries.isNotEmpty()) request.addOption("retries", retries)
|
||||
if(fragmentRetries.isNotEmpty()) request.addOption("fragment-retries", fragmentRetries)
|
||||
if(retries.isNotEmpty()) request.addOption("--retries", retries)
|
||||
if(fragmentRetries.isNotEmpty()) request.addOption("--fragment-retries", fragmentRetries)
|
||||
|
||||
val limitRate = sharedPreferences.getString("limit_rate", "")!!
|
||||
if (limitRate.isNotBlank()) request.addOption("-r", limitRate)
|
||||
|
||||
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
|
||||
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
||||
|
||||
val limitRate = sharedPreferences.getString("limit_rate", "")
|
||||
if (limitRate != "") request.addOption("-r", limitRate!!)
|
||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||
request.addOption("--trim-filenames", 120)
|
||||
request.addOption("--trim-filenames", downDir.absolutePath.length + 120)
|
||||
|
||||
if (downloadItem.SaveThumb) {
|
||||
request.addOption("--write-thumbnail")
|
||||
|
|
@ -1175,10 +1197,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
request.addOption("--embed-chapters")
|
||||
}
|
||||
if (downloadItem.videoPreferences.embedSubs) {
|
||||
request.addOption("--embed-subs")
|
||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||
}
|
||||
|
||||
|
||||
var cont = ""
|
||||
val outputContainer = downloadItem.container
|
||||
|
|
@ -1302,14 +1321,19 @@ class InfoUtil(private val context: Context) {
|
|||
val subFormat = sharedPreferences.getString("sub_format", "srt")
|
||||
|
||||
request.addOption("--write-subs")
|
||||
request.addOption("--write-auto-subs")
|
||||
if(subFormat!!.isNotBlank()){
|
||||
request.addOption("--sub-format", "${subFormat}/best")
|
||||
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
||||
}
|
||||
if (!downloadItem.videoPreferences.embedSubs) {
|
||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.embedSubs) {
|
||||
request.addOption("--embed-subs")
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs){
|
||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||
request.addOption("--write-auto-subs")
|
||||
}
|
||||
|
||||
if (downloadItem.videoPreferences.removeAudio){
|
||||
|
|
|
|||
|
|
@ -91,11 +91,14 @@ object UiUtil {
|
|||
if (container == "Default") container = context.getString(R.string.defaultValue)
|
||||
|
||||
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
|
||||
if (audioFormats.isNullOrEmpty()){
|
||||
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
||||
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
||||
|
||||
val audioFormatsTextView = formatCard.findViewById<TextView>(R.id.audio_formats)
|
||||
if (!audioFormats.isNullOrEmpty()) {
|
||||
audioFormatsTextView.text = audioFormats.joinToString("+") { it.format_id }
|
||||
audioFormatsTextView.visibility = View.VISIBLE
|
||||
}else{
|
||||
val title = "${formatNote.uppercase()} + [${audioFormats.joinToString("/") { "(${it.format_id}) ${it.format_note}" }}]"
|
||||
formatCard.findViewById<TextView>(R.id.format_note).text = title
|
||||
audioFormatsTextView.visibility = View.GONE
|
||||
}
|
||||
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
||||
val codec =
|
||||
|
|
@ -729,14 +732,18 @@ object UiUtil {
|
|||
bottomSheet.cancel()
|
||||
}
|
||||
|
||||
bottomSheet.setOnShowListener {
|
||||
val behavior = bottomSheet.behavior
|
||||
val displayMetrics = DisplayMetrics()
|
||||
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
behavior.peekHeight = displayMetrics.heightPixels / 2
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
suspend fun showShortcuts(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: String) -> Unit){
|
||||
suspend fun showShortcuts(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: String) -> Unit, itemRemoved: (itemRemoved: String) -> Unit){
|
||||
val bottomSheet = BottomSheetDialog(activity)
|
||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.template_shortcuts_list)
|
||||
|
|
@ -751,16 +758,24 @@ object UiUtil {
|
|||
val chip = activity.layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
|
||||
chip.text = shortcut.content
|
||||
chip.setOnClickListener {
|
||||
itemSelected(shortcut.content)
|
||||
if (chip.isChecked){
|
||||
itemSelected(shortcut.content)
|
||||
}else{
|
||||
itemRemoved(shortcut.content)
|
||||
}
|
||||
}
|
||||
chipGroup.addView(chip)
|
||||
}
|
||||
|
||||
bottomSheet.setOnShowListener {
|
||||
val behavior = bottomSheet.behavior
|
||||
val displayMetrics = DisplayMetrics()
|
||||
activity.windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
behavior.peekHeight = displayMetrics.heightPixels / 3
|
||||
}
|
||||
|
||||
bottomSheet.show()
|
||||
bottomSheet.window!!.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
fun configureVideo(
|
||||
|
|
@ -913,7 +928,7 @@ object UiUtil {
|
|||
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = context.getString(R.string.file_name_template)
|
||||
if (items.size == 1){
|
||||
if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
|
||||
editText.setText(items[0].customFileNameTemplate)
|
||||
}
|
||||
editText.setSelection(editText.text.length)
|
||||
|
|
@ -936,7 +951,6 @@ object UiUtil {
|
|||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
|
|
@ -1013,7 +1027,7 @@ object UiUtil {
|
|||
val inputLayout = context.layoutInflater.inflate(R.layout.textinput, null)
|
||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = context.getString(R.string.file_name_template)
|
||||
if (items.size == 1){
|
||||
if (items.size == 1 || items.all { it.customFileNameTemplate == items[0].customFileNameTemplate }){
|
||||
editText.setText(items[0].customFileNameTemplate)
|
||||
}
|
||||
editText.setSelection(editText.text.length)
|
||||
|
|
@ -1036,7 +1050,6 @@ object UiUtil {
|
|||
editText.requestFocus()
|
||||
imm.showSoftInput(editText, 0)
|
||||
}, 300)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ class UpdateUtil(var context: Context) {
|
|||
.setTitle(v.tag_name)
|
||||
.setMessage(v.body)
|
||||
.setIcon(R.drawable.ic_update_app)
|
||||
.setNegativeButton("Cancel") { _: DialogInterface?, _: Int -> }
|
||||
.setPositiveButton("Update") { _: DialogInterface?, _: Int ->
|
||||
.setNegativeButton(context.resources.getString(R.string.cancel)) { _: DialogInterface?, _: Int -> }
|
||||
.setPositiveButton(context.resources.getString(R.string.update)) { _: DialogInterface?, _: Int ->
|
||||
runCatching {
|
||||
val releaseVersion = v.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
|
||||
if (releaseVersion == null){
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.widget.Toast
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.deniscerri.ytdlnis.R
|
||||
|
|
@ -19,12 +18,13 @@ import com.deniscerri.ytdlnis.database.models.Format
|
|||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
||||
import com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -39,6 +39,7 @@ class TerminalDownloadWorker(
|
|||
override suspend fun doWork(): Result {
|
||||
itemId = inputData.getInt("id", 0)
|
||||
val command = inputData.getString("command")
|
||||
val dao = DBManager.getInstance(context).terminalDao
|
||||
if (itemId == 0) return Result.failure()
|
||||
if (command!!.isEmpty()) return Result.failure()
|
||||
|
||||
|
|
@ -59,10 +60,6 @@ class TerminalDownloadWorker(
|
|||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
val downloadLocation = sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath())
|
||||
|
||||
val outputFile = File(context.cacheDir.absolutePath + "/$itemId.txt")
|
||||
if (! outputFile.exists()) outputFile.createNewFile()
|
||||
|
||||
request.addOption(
|
||||
"--config-locations",
|
||||
File(context.cacheDir.absolutePath + "/config-TERMINAL[${System.currentTimeMillis()}].txt").apply {
|
||||
|
|
@ -135,9 +132,9 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
|
||||
return runBlocking {
|
||||
it.out.chunked(10000).forEach {
|
||||
it.out.split("\n").forEach {
|
||||
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
|
||||
Thread.sleep(100)
|
||||
Thread.sleep(50)
|
||||
}
|
||||
|
||||
if (logDownloads){
|
||||
|
|
@ -146,13 +143,10 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
}
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
dao.delete(itemId.toLong())
|
||||
return@runBlocking Result.success()
|
||||
}
|
||||
}.onFailure {
|
||||
if (it is YoutubeDL.CanceledException) {
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
return runBlocking {
|
||||
it.message?.chunked(10000)?.forEach {
|
||||
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
|
||||
|
|
@ -169,7 +163,7 @@ class TerminalDownloadWorker(
|
|||
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||
notificationUtil.cancelDownloadNotification(itemId)
|
||||
|
||||
dao.delete(itemId.toLong())
|
||||
return@runBlocking Result.failure()
|
||||
}
|
||||
|
||||
|
|
|
|||
117
app/src/main/res/layout/active_terminal_card.xml
Normal file
117
app/src/main/res/layout/active_terminal_card.xml
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?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"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
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/active_download_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="10dp"
|
||||
app:cardMaxElevation="12dp"
|
||||
android:checkable="true"
|
||||
app:strokeWidth="0dp"
|
||||
app:cardPreventCornerOverlap="true"
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="match_parent"
|
||||
app:trackColor="#000"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:alpha="0.3"
|
||||
android:scaleY="200"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:shadowColor="#000"
|
||||
android:fontFamily="monospace"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toStartOf="@+id/active_download_stop"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_stop"
|
||||
style="?attr/materialIconButtonFilledStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:backgroundTint="?attr/colorSurface"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/baseline_close_24"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintBottom_toTopOf="@+id/output"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="title,active_download_stop" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/output"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:ellipsize="end"
|
||||
android:focusable="true"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="5"
|
||||
android:padding="10dp"
|
||||
android:shadowColor="#000"
|
||||
android:shadowDx="4"
|
||||
android:shadowDy="4"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="#FFF"
|
||||
android:textSize="11sp"
|
||||
android:gravity="bottom"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/barrier" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -1,121 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.TerminalActivity">
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".ui.more.terminal.TerminalActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:liftOnScroll="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:liftOnScroll="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/custom_command_toolbar"
|
||||
android:elevation="0dp"
|
||||
app:title="@string/terminal"
|
||||
app:menu="@menu/terminal_top_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="0dp"
|
||||
app:menu="@menu/terminal_top_menu"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
android:layout_height="match_parent"/>
|
||||
app:title="@string/terminal" />
|
||||
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/frame_layout"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
app:navGraph="@navigation/terminal_graph" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/custom_command_scrollview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/horizontalscroll_output"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/custom_command_output"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="bottom"
|
||||
android:textSize="15sp"
|
||||
android:scrollbars="horizontal|vertical"
|
||||
android:scrollHorizontally="true"
|
||||
android:textIsSelectable="true" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/command_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="start"
|
||||
android:inputType="textMultiLine|textNoSuggestions"
|
||||
android:maxLines="100"
|
||||
android:text="yt-dlp "
|
||||
android:textSize="15sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
style="@style/Widget.Material3.BottomAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
app:menu="@menu/terminal_menu" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/command_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/run_command"
|
||||
app:icon="@drawable/ic_baseline_keyboard_arrow_right_24"
|
||||
app:layout_anchor="@id/bottomAppBar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -22,12 +22,13 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="false"
|
||||
android:text="@string/command_templates"
|
||||
android:textSize="25sp"
|
||||
android:maxLines="2"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintEnd_toStartOf="@+id/command_ok"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView android:layout_width="match_parent"
|
||||
<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"
|
||||
|
|
@ -22,6 +23,29 @@
|
|||
android:orientation="horizontal"
|
||||
android:paddingTop="20dp">
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:id="@+id/shimmer_loading_title"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_loading_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/loading"
|
||||
android:textSize="25sp" />
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
|
|
@ -37,6 +61,27 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.facebook.shimmer.ShimmerFrameLayout
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/shimmer_loading_title"
|
||||
android:id="@+id/shimmer_loading_subtitle"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:singleLine="false"
|
||||
android:text="@string/please_wait"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -49,82 +49,120 @@
|
|||
android:id="@+id/format_note"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
android:maxLines="2"
|
||||
android:paddingStart="10dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start"
|
||||
android:maxLines="2"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="wrap_content"
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="10dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/format_id"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/format_note"
|
||||
app:layout_constraintVertical_bias="1.0">
|
||||
app:layout_constraintTop_toBottomOf="@id/format_note">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
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:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_marginStart="10dp"
|
||||
android:scrollbars="none">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
|
||||
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:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:orientation="horizontal">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/audio_formats"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/rounded_corner"
|
||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:drawableStartCompat="@drawable/ic_music"
|
||||
app:drawableTint="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/codec"
|
||||
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:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_size"
|
||||
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
|
||||
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:gravity="center"
|
||||
android:minWidth="30dp"
|
||||
android:paddingHorizontal="5dp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:clickable="false"
|
||||
android:gravity="bottom|end"
|
||||
android:maxLength="10"
|
||||
android:minWidth="30dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/linearLayout"
|
||||
app:layout_constraintTop_toBottomOf="@+id/format_note" />
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.TerminalActivity">
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.TerminalActivity">
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
android:paddingHorizontal="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:hint="@string/title"
|
||||
app:endIconMode="custom"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
|
|
@ -43,6 +44,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="40"
|
||||
android:hint="@string/author"
|
||||
app:endIconMode="custom"
|
||||
style="@style/Widget.Material3.TextInputLayout.FilledBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.TerminalActivity">
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.TerminalActivity">
|
||||
tools:context="com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
86
app/src/main/res/layout/fragment_terminal.xml
Normal file
86
app/src/main/res/layout/fragment_terminal.xml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/terminal_view"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/custom_command_scrollview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="10dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/horizontalscroll_output"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/custom_command_output"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="bottom"
|
||||
android:scrollbars="horizontal|vertical"
|
||||
android:scrollHorizontally="true"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="15sp" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/command_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="start"
|
||||
android:inputType="textMultiLine|textNoSuggestions"
|
||||
android:maxLines="10000"
|
||||
android:text="yt-dlp "
|
||||
android:textSize="15sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
style="@style/Widget.Material3.BottomAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
app:menu="@menu/terminal_menu" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/command_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/run_command"
|
||||
app:icon="@drawable/ic_baseline_keyboard_arrow_right_24"
|
||||
app:layout_anchor="@id/bottomAppBar" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
21
app/src/main/res/layout/fragment_terminal_download_list.xml
Normal file
21
app/src/main/res/layout/fragment_terminal_download_list.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/active_terminal_tasks"
|
||||
android:layout_width="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/terminal_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -2,6 +2,11 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:title=""
|
||||
android:icon="@drawable/ic_plus"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/add"/>
|
||||
|
||||
<item android:title=""
|
||||
android:icon="@drawable/baseline_wrap_text_24"
|
||||
app:showAsAction="always"
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@
|
|||
</fragment>
|
||||
<activity
|
||||
android:id="@+id/terminalActivity"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.TerminalActivity"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity"
|
||||
android:label="activity_terminal"
|
||||
tools:layout="@layout/activity_terminal" />
|
||||
tools:layout="@layout/fragment_terminal" />
|
||||
<fragment
|
||||
android:id="@+id/cookiesFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.CookiesFragment"
|
||||
|
|
|
|||
24
app/src/main/res/navigation/terminal_graph.xml
Normal file
24
app/src/main/res/navigation/terminal_graph.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation 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"
|
||||
app:startDestination="@id/terminalDownloadsListFragment">
|
||||
<fragment
|
||||
android:id="@+id/terminalDownloadsListFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.terminal.TerminalDownloadsListFragment"
|
||||
tools:layout="@layout/fragment_terminal_downloadList"
|
||||
android:label="TerminalDownloadsListFragment" >
|
||||
<action
|
||||
android:id="@+id/action_terminalDownloadsListFragment_to_terminalFragment2"
|
||||
app:destination="@id/terminalFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/terminalFragment"
|
||||
android:name="com.deniscerri.ytdlnis.ui.more.terminal.TerminalFragment"
|
||||
tools:layout="@layout/fragment_terminal"
|
||||
android:label="TerminalFragment" >
|
||||
<action
|
||||
android:id="@+id/action_terminalFragment2_to_terminalDownloadsListFragment"
|
||||
app:destination="@id/terminalDownloadsListFragment" />
|
||||
</fragment>
|
||||
</navigation>
|
||||
|
|
@ -808,4 +808,17 @@
|
|||
<item>avc|h264</item>
|
||||
<item>hev|h265</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<string-array name="audio_codec">
|
||||
<item>@string/defaultValue</item>
|
||||
<item>M4A</item>
|
||||
<item>OPUS</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="audio_codec_values">
|
||||
<item></item>
|
||||
<item>m4a|mp4a|aac</item>
|
||||
<item>opus</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
|
@ -233,7 +233,7 @@
|
|||
<string name="finished_download_notification_channel_name">Finished Downloads</string>
|
||||
<string name="finished_download_notification_channel_description">Notification for finished or faulty downloads</string>
|
||||
<string name="quick_download">Quick Download</string>
|
||||
<string name="quick_download_summary">Don\'t fetch data beforehand</string>
|
||||
<string name="quick_download_summary">Don\'t fetch data in the download card</string>
|
||||
<string name="remove_audio">Remove Audio</string>
|
||||
<string name="preferred_format_id">Preferred Format ID</string>
|
||||
<string name="download_now">Download Now</string>
|
||||
|
|
@ -303,4 +303,8 @@
|
|||
<string name="update_app_beta">Beta Releases</string>
|
||||
<string name="update_app_beta_summary">Enroll in the beta program. It may contain bugs.</string>
|
||||
<string name="auto">Auto</string>
|
||||
<string name="preferred_video_codec">Preferred Video Codec</string>
|
||||
<string name="preferred_audio_codec">Preferred Audio Codec</string>
|
||||
<string name="loading">Loading</string>
|
||||
<string name="sponsorblock_api_url">Sponsorblock API URL</string>
|
||||
</resources>
|
||||
|
|
@ -54,14 +54,12 @@
|
|||
app:title="@string/preferred_download_type" />
|
||||
|
||||
<SeekBarPreference
|
||||
app:dependency="aria2"
|
||||
android:defaultValue="3"
|
||||
android:icon="@drawable/ic_lines"
|
||||
android:max="25"
|
||||
app:key="concurrent_fragments"
|
||||
app:min="1"
|
||||
app:showSeekBarValue="true"
|
||||
android:dependency="aria2"
|
||||
app:summary="@string/concurrent_fragments_summary"
|
||||
app:title="@string/concurrent_fragments" />
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@
|
|||
app:summary="@string/select_sponsorblock_filtering"
|
||||
app:title="SponsorBlock" />
|
||||
|
||||
<EditTextPreference
|
||||
android:dependency="use_sponsorblock"
|
||||
app:icon="@drawable/ic_money"
|
||||
app:key="sponsorblock_url"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:defaultValue=""
|
||||
app:title="@string/sponsorblock_api_url" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
|
|
@ -145,7 +154,15 @@
|
|||
android:entryValues="@array/video_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:key="video_codec"
|
||||
app:title="@string/codec" />
|
||||
app:title="@string/preferred_video_codec" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue=""
|
||||
android:entries="@array/audio_codec"
|
||||
android:entryValues="@array/audio_codec_values"
|
||||
android:icon="@drawable/ic_code"
|
||||
app:key="audio_codec"
|
||||
app:title="@string/preferred_audio_codec" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
## For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
#
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx1024m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
#
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
#Sun Oct 08 20:53:48 CEST 2023
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.enableJetifier=true
|
||||
android.nonFinalResIds=false
|
||||
android.nonTransitiveRClass=false
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1024M" -Dfile.encoding\=UTF-8
|
||||
|
|
|
|||
Loading…
Reference in a new issue