1.6.8.2-beta
This commit is contained in:
parent
a922633567
commit
559efca908
46 changed files with 1269 additions and 474 deletions
|
|
@ -9,8 +9,8 @@ plugins {
|
||||||
def properties = new Properties()
|
def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 6
|
def versionMinor = 6
|
||||||
def versionPatch = 7
|
def versionPatch = 8
|
||||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 2 // bump for dogfood builds, public betas, etc.
|
||||||
def versionExt = ""
|
def versionExt = ""
|
||||||
|
|
||||||
if (versionBuild > 0){
|
if (versionBuild > 0){
|
||||||
|
|
@ -136,6 +136,7 @@ dependencies {
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
implementation "androidx.navigation:navigation-ui-ktx:$navVer"
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
implementation 'androidx.core:core-ktx:1.10.1'
|
||||||
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||||
|
implementation 'androidx.compose.ui:ui-android:1.5.3'
|
||||||
testImplementation "junit:junit:$junitVer"
|
testImplementation "junit:junit:$junitVer"
|
||||||
androidTestImplementation "junit:junit:$junitVer"
|
androidTestImplementation "junit:junit:$junitVer"
|
||||||
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
||||||
|
|
@ -186,4 +187,6 @@ dependencies {
|
||||||
|
|
||||||
implementation "com.anggrayudi:storage:1.5.5"
|
implementation "com.anggrayudi:storage:1.5.5"
|
||||||
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
implementation 'me.zhanghai.android.fastscroll:library:1.3.0'
|
||||||
|
implementation 'com.google.accompanist:accompanist-webview:0.30.1'
|
||||||
|
implementation 'androidx.compose.material3:material3-android:1.2.0-alpha08'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"identityHash": "6b5efd73749a7138035e1f00349715c8",
|
"identityHash": "0647c4b527efea884e1dc26157cd4189",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "results",
|
"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)",
|
"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 DEFAULT '', `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `creationTime` INTEGER NOT NULL)",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
|
@ -42,7 +42,8 @@
|
||||||
"fieldPath": "thumb",
|
"fieldPath": "thumb",
|
||||||
"columnName": "thumb",
|
"columnName": "thumb",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "website",
|
"fieldPath": "website",
|
||||||
|
|
@ -93,7 +94,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "history",
|
"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)",
|
"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, `command` TEXT NOT NULL DEFAULT '')",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
|
@ -167,6 +168,13 @@
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true,
|
"notNull": true,
|
||||||
"defaultValue": "0"
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "command",
|
||||||
|
"columnName": "command",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -538,7 +546,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6b5efd73749a7138035e1f00349715c8')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0647c4b527efea884e1dc26157cd4189')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
558
app/schemas/com.deniscerri.ytdlnis.database.DBManager/11.json
Normal file
558
app/schemas/com.deniscerri.ytdlnis.database.DBManager/11.json
Normal file
|
|
@ -0,0 +1,558 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 11,
|
||||||
|
"identityHash": "09798517d2b7ce96999c879bcd381c7f",
|
||||||
|
"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, `command` TEXT NOT NULL DEFAULT '')",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "command",
|
||||||
|
"columnName": "command",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, `log` TEXT DEFAULT '')",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "command",
|
||||||
|
"columnName": "command",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "log",
|
||||||
|
"columnName": "log",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, '09798517d2b7ce96999c879bcd381c7f')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -194,6 +194,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
|
||||||
|
<receiver android:name=".receiver.CancelWorkReceiver" />
|
||||||
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
<receiver android:name=".receiver.PauseDownloadNotificationReceiver" />
|
||||||
<receiver android:name=".receiver.AlarmStartReceiver" />
|
<receiver android:name=".receiver.AlarmStartReceiver" />
|
||||||
<receiver android:name=".receiver.AlarmStopReceiver" />
|
<receiver android:name=".receiver.AlarmStopReceiver" />
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,7 @@ import android.os.Looper
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Configuration
|
|
||||||
import androidx.work.Constraints
|
|
||||||
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.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.aria2c.Aria2c
|
||||||
import com.yausername.ffmpeg.FFmpeg
|
import com.yausername.ffmpeg.FFmpeg
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
|
@ -27,8 +15,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,8 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
|
|
||||||
// THUMBNAIL ----------------------------------
|
// THUMBNAIL ----------------------------------
|
||||||
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||||
val imageURL = item!!.thumb
|
val imageURL = item?.thumb
|
||||||
if (imageURL.isNotEmpty()) {
|
if (!imageURL.isNullOrBlank()) {
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
} else {
|
} else {
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
LogItem::class,
|
LogItem::class,
|
||||||
TerminalItem::class
|
TerminalItem::class
|
||||||
],
|
],
|
||||||
version = 10,
|
version = 11,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration (from = 1, to = 2),
|
AutoMigration (from = 1, to = 2),
|
||||||
AutoMigration (from = 2, to = 3),
|
AutoMigration (from = 2, to = 3),
|
||||||
|
|
@ -33,7 +33,8 @@ import kotlinx.coroutines.flow.Flow
|
||||||
AutoMigration (from = 6, to = 7),
|
AutoMigration (from = 6, to = 7),
|
||||||
AutoMigration (from = 7, to = 8),
|
AutoMigration (from = 7, to = 8),
|
||||||
AutoMigration (from = 8, to = 9),
|
AutoMigration (from = 8, to = 9),
|
||||||
AutoMigration (from = 9, to = 10)
|
AutoMigration (from = 9, to = 10),
|
||||||
|
AutoMigration (from = 10, to = 11)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
abstract class DBManager : RoomDatabase(){
|
abstract class DBManager : RoomDatabase(){
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,9 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT id FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||||
|
fun getActiveAndQueuedDownloadIDs() : List<Long>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
@Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='QueuedPaused' or status='Paused'")
|
||||||
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
|
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
|
|
@ -122,8 +125,8 @@ interface DownloadDao {
|
||||||
@Query("DELETE FROM downloads WHERE id in (:list)")
|
@Query("DELETE FROM downloads WHERE id in (:list)")
|
||||||
suspend fun deleteAllWithIDs(list: List<Long>)
|
suspend fun deleteAllWithIDs(list: List<Long>)
|
||||||
|
|
||||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused'")
|
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused' or status='Active' or status='Paused'")
|
||||||
suspend fun cancelQueued()
|
suspend fun cancelActiveQueued()
|
||||||
|
|
||||||
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
@Query("DELETE FROM downloads WHERE status='Processing' AND id=:id")
|
||||||
suspend fun deleteSingleProcessing(id: Long)
|
suspend fun deleteSingleProcessing(id: Long)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ interface HistoryDao {
|
||||||
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
|
@Query("SELECT * FROM history WHERE id=:id LIMIT 1")
|
||||||
suspend fun getHistoryItem(id: Int) : HistoryItem
|
suspend fun getHistoryItem(id: Int) : HistoryItem
|
||||||
|
|
||||||
|
@Query("SELECT * FROM history WHERE url=:url")
|
||||||
|
fun getAllHistoryByURL(url: String) : List<HistoryItem>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insert(item: HistoryItem)
|
suspend fun insert(item: HistoryItem)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,38 @@ import kotlinx.coroutines.flow.Flow
|
||||||
@Dao
|
@Dao
|
||||||
interface TerminalDao {
|
interface TerminalDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM terminalDownloads ORDER BY id")
|
@Query("SELECT id,command FROM terminalDownloads ORDER BY id")
|
||||||
fun getActiveTerminalDownloads() : List<TerminalItem>
|
fun getActiveTerminalDownloads() : List<TerminalItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM terminalDownloads ORDER BY id")
|
@Query("SELECT id,command FROM terminalDownloads ORDER BY id")
|
||||||
fun getActiveTerminalDownloadsFlow() : Flow<List<TerminalItem>>
|
fun getActiveTerminalDownloadsFlow() : Flow<List<TerminalItem>>
|
||||||
|
|
||||||
|
@Query("SELECT * from terminalDownloads where id=:id")
|
||||||
|
fun getActiveTerminalFlow(id: Long) : Flow<TerminalItem>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM terminalDownloads")
|
@Query("SELECT COUNT(*) FROM terminalDownloads")
|
||||||
fun getActiveTerminalsCount() : Int
|
fun getActiveTerminalsCount() : Int
|
||||||
|
|
||||||
|
@Query("UPDATE terminalDownloads set log=:l where id=:id")
|
||||||
|
fun updateTerminalLog(l: String, id: Long)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM terminalDownloads WHERE id=:id LIMIT 1")
|
||||||
|
fun getTerminalById(id: Long) : TerminalItem?
|
||||||
|
@Transaction
|
||||||
|
fun updateLog(line: String, id: Long){
|
||||||
|
val t = getTerminalById(id) ?: return
|
||||||
|
val log = t.log ?: ""
|
||||||
|
val lines = log.split("\n")
|
||||||
|
//clean dublicate progress + add newline
|
||||||
|
val l = if (listOf(line, lines.last()).all { it.contains("[download")}){
|
||||||
|
lines.dropLast(1).joinToString("\n")
|
||||||
|
}else{
|
||||||
|
log
|
||||||
|
} + "\n${line}"
|
||||||
|
|
||||||
|
updateTerminalLog(l, id)
|
||||||
|
}
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(item: TerminalItem) : Long
|
suspend fun insert(item: TerminalItem) : Long
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,7 @@ data class HistoryItem(
|
||||||
val website: String,
|
val website: String,
|
||||||
val format: Format,
|
val format: Format,
|
||||||
@ColumnInfo(defaultValue = "0")
|
@ColumnInfo(defaultValue = "0")
|
||||||
val downloadId: Long
|
val downloadId: Long,
|
||||||
|
@ColumnInfo(defaultValue = "")
|
||||||
|
val command: String = ""
|
||||||
)
|
)
|
||||||
|
|
@ -9,5 +9,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
data class TerminalItem(
|
data class TerminalItem(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
var id: Long = 0,
|
var id: Long = 0,
|
||||||
var command: String
|
var command: String,
|
||||||
|
@ColumnInfo(defaultValue = "")
|
||||||
|
var log: String? = "",
|
||||||
)
|
)
|
||||||
|
|
@ -83,6 +83,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
return downloadDao.getActiveAndQueuedDownloadsList()
|
return downloadDao.getActiveAndQueuedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getActiveAndQueuedDownloadIDs() : List<Long> {
|
||||||
|
return downloadDao.getActiveAndQueuedDownloadIDs()
|
||||||
|
}
|
||||||
|
|
||||||
fun getQueuedDownloads() : List<DownloadItem> {
|
fun getQueuedDownloads() : List<DownloadItem> {
|
||||||
return downloadDao.getQueuedDownloadsList()
|
return downloadDao.getQueuedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
@ -120,8 +124,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cancelQueued(){
|
suspend fun cancelActiveQueued(){
|
||||||
downloadDao.cancelQueued()
|
downloadDao.cancelActiveQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pauseDownloads(){
|
fun pauseDownloads(){
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
|
@ -28,6 +29,7 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
|
|
@ -38,6 +40,7 @@ import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
|
|
@ -82,6 +85,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
private var videoCodec: String?
|
private var videoCodec: String?
|
||||||
private var audioCodec: String?
|
private var audioCodec: String?
|
||||||
private val dao: DownloadDao
|
private val dao: DownloadDao
|
||||||
|
private val historyDao: HistoryDao
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
auto, audio, video, command
|
auto, audio, video, command
|
||||||
|
|
@ -96,6 +100,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
init {
|
init {
|
||||||
dbManager = DBManager.getInstance(application)
|
dbManager = DBManager.getInstance(application)
|
||||||
dao = dbManager.downloadDao
|
dao = dbManager.downloadDao
|
||||||
|
historyDao = dbManager.historyDao
|
||||||
repository = DownloadRepository(dao)
|
repository = DownloadRepository(dao)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
|
|
@ -173,8 +178,20 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return repository.getItemByID(id)
|
return repository.getItemByID(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadType(t: Type, url: String) : Type {
|
fun getDownloadType(t: Type? = null, url: String) : Type {
|
||||||
return when(t){
|
var type = t
|
||||||
|
|
||||||
|
if (type == null){
|
||||||
|
val preferredDownloadType = sharedPreferences.getString("preferred_download_type", Type.auto.toString())
|
||||||
|
type = if (sharedPreferences.getBoolean("remember_download_type", false)){
|
||||||
|
Type.valueOf(sharedPreferences.getString("last_used_download_type",
|
||||||
|
preferredDownloadType)!!)
|
||||||
|
}else{
|
||||||
|
Type.valueOf(preferredDownloadType!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when(type){
|
||||||
Type.auto -> {
|
Type.auto -> {
|
||||||
if (urlsForAudioType.any { url.contains(it) }){
|
if (urlsForAudioType.any { url.contains(it) }){
|
||||||
Type.audio
|
Type.audio
|
||||||
|
|
@ -182,7 +199,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Type.video
|
Type.video
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> t
|
|
||||||
|
else -> type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -448,10 +466,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
|
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
|
||||||
val list : MutableList<DownloadItem> = mutableListOf()
|
val list : MutableList<DownloadItem> = mutableListOf()
|
||||||
var preferredType = sharedPreferences.getString("preferred_download_type", "video")
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
preferredType = getDownloadType(Type.valueOf(preferredType!!), it!!.url).toString()
|
val preferredType = getDownloadType(url = it!!.url).toString()
|
||||||
list.add(createDownloadItemFromResult(result = it, givenType = Type.valueOf(preferredType!!)))
|
list.add(createDownloadItemFromResult(result = it, givenType = Type.valueOf(
|
||||||
|
preferredType
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -487,12 +506,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||||
Log.e("Aa", ids.size.toString())
|
|
||||||
repository.deleteAllWithIDs(ids)
|
repository.deleteAllWithIDs(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelQueued() = viewModelScope.launch(Dispatchers.IO) {
|
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
repository.cancelQueued()
|
repository.cancelActiveQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getQueued() : List<DownloadItem> {
|
fun getQueued() : List<DownloadItem> {
|
||||||
|
|
@ -528,6 +546,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
return repository.getActiveAndQueuedDownloads()
|
return repository.getActiveAndQueuedDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getActiveAndQueuedDownloadIDs() : List<Long>{
|
||||||
|
return repository.getActiveAndQueuedDownloadIDs()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||||
startDownloadWorker(emptyList())
|
startDownloadWorker(emptyList())
|
||||||
|
|
@ -548,7 +570,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
val alarmScheduler = AlarmScheduler(context)
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
var exists = false
|
val existing = mutableListOf<DownloadItem>()
|
||||||
|
|
||||||
//if scheduler is on
|
//if scheduler is on
|
||||||
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
|
||||||
|
|
@ -556,8 +578,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
alarmScheduler.schedule()
|
alarmScheduler.schedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||||
|
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||||
|
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||||
if (activeAndQueuedDownloads.firstOrNull{d ->
|
if (activeAndQueuedDownloads.firstOrNull{d ->
|
||||||
d.id = 0
|
d.id = 0
|
||||||
d.logID = null
|
d.logID = null
|
||||||
|
|
@ -565,23 +590,33 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
d.status = DownloadRepository.Status.Queued.toString()
|
d.status = DownloadRepository.Status.Queued.toString()
|
||||||
d.toString() == it.toString()
|
d.toString() == it.toString()
|
||||||
} != null) {
|
} != null) {
|
||||||
exists = true
|
existing.add(it)
|
||||||
}else{
|
}else{
|
||||||
if (it.id == 0L){
|
//check if downloaded and file exists
|
||||||
val id = runBlocking {repository.insert(it)}
|
val history = historyDao.getAllHistoryByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
|
||||||
it.id = id
|
if (history.any { h -> h.command.replace("-P \"(.*?)\"".toRegex(), "") == parsedCurrentCommand.replace("-P \"(.*?)\"".toRegex(), "") }){
|
||||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
existing.add(it)
|
||||||
repository.update(it)
|
}else{
|
||||||
}
|
if (it.id == 0L){
|
||||||
|
val id = runBlocking {repository.insert(it)}
|
||||||
|
it.id = id
|
||||||
|
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||||
|
repository.update(it)
|
||||||
|
}
|
||||||
|
|
||||||
queuedItems.add(it)
|
queuedItems.add(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists){
|
if (existing.isNotEmpty()){
|
||||||
Looper.prepare().run {
|
val intent = Intent(context, ErrorDialogActivity::class.java)
|
||||||
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
}
|
intent.putExtra("title", context.resources.getString(R.string.download_already_exists))
|
||||||
|
intent.putExtra("message",
|
||||||
|
"${existing.size}/${items.size}\n" +
|
||||||
|
existing.mapIndexed { index, downloadItem -> "${index+1}. ${downloadItem.title}" }.joinToString("\n"))
|
||||||
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
|
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || items.any { it.downloadStartTime > 0L } ){
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,10 @@ class TerminalViewModel(private val application: Application) : AndroidViewModel
|
||||||
return dao.getActiveTerminalDownloadsFlow()
|
return dao.getActiveTerminalDownloadsFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTerminal(id: Long) : Flow<TerminalItem> {
|
||||||
|
return dao.getActiveTerminalFlow(id)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun insert(item: TerminalItem) : Long {
|
suspend fun insert(item: TerminalItem) : Long {
|
||||||
return dao.insert(item)
|
return dao.insert(item)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ class AlarmStopReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
Log.e("assa", "assssssssssssssssssssssssssssss")
|
|
||||||
val active = dbManager.downloadDao.getActiveDownloadsList()
|
val active = dbManager.downloadDao.getActiveDownloadsList()
|
||||||
|
|
||||||
val startingTime = preferences.getString("schedule_start", "00:00")!!
|
val startingTime = preferences.getString("schedule_start", "00:00")!!
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,9 @@ import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class CancelWorkReceiver : BroadcastReceiver() {
|
class CancelWorkReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(c: Context, intent: Intent) {
|
override fun onReceive(c: Context, intent: Intent) {
|
||||||
val id = intent.getStringExtra("workID")
|
val id = intent.getStringExtra("workTag")
|
||||||
if (!id.isNullOrBlank()) {
|
if (!id.isNullOrBlank()){
|
||||||
runCatching {
|
WorkManager.getInstance(c).cancelAllWorkByTag(id)
|
||||||
WorkManager.getInstance(c).cancelAllWorkByTag(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -108,65 +108,56 @@ class ShareActivity : BaseActivity() {
|
||||||
runCatching { supportFragmentManager.popBackStack() }
|
runCatching { supportFragmentManager.popBackStack() }
|
||||||
|
|
||||||
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
|
quickDownload = intent.getBooleanExtra("quick_download", sharedPreferences.getBoolean("quick_download", false) || sharedPreferences.getString("preferred_download_type", "video") == "command")
|
||||||
val intentData = when(action){
|
val url = when(action){
|
||||||
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
|
Intent.ACTION_SEND -> intent.getStringExtra(Intent.EXTRA_TEXT)!!
|
||||||
else -> intent.dataString!!
|
else -> intent.dataString!!
|
||||||
}
|
}
|
||||||
val matcher = Patterns.WEB_URL.matcher(intentData)
|
val matcher = Patterns.WEB_URL.matcher(url)
|
||||||
val inputQuery = if (matcher.find()){
|
val inputQuery = if (matcher.find()){
|
||||||
matcher.group()
|
matcher.group()
|
||||||
}else{
|
}else{
|
||||||
intentData
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
val type = intent.getStringExtra("TYPE")
|
||||||
try {
|
val background = intent.getBooleanExtra("BACKGROUND", false)
|
||||||
val result = withContext(Dispatchers.IO){
|
val command = intent.getStringExtra("COMMAND") ?: ""
|
||||||
resultViewModel.getItemByURL(inputQuery)
|
|
||||||
}
|
|
||||||
if (result == null) throw Exception()
|
|
||||||
showDownloadSheet(result)
|
|
||||||
}catch (e: Exception){
|
|
||||||
resultViewModel.deleteAll()
|
|
||||||
showDownloadSheet(downloadViewModel.createEmptyResultItem(inputQuery))
|
|
||||||
if (matcher.matches()){
|
|
||||||
|
|
||||||
}else{
|
lifecycleScope.launch {
|
||||||
val res = withContext(Dispatchers.IO){
|
var result = withContext(Dispatchers.IO){
|
||||||
resultViewModel.parseQuery(inputQuery, true)
|
resultViewModel.getItemByURL(inputQuery)
|
||||||
}
|
}
|
||||||
if (res.isEmpty()) {
|
if (result == null) {
|
||||||
Toast.makeText(this@ShareActivity, "No Results Found!", Toast.LENGTH_SHORT).show()
|
resultViewModel.deleteAll()
|
||||||
exit()
|
result = downloadViewModel.createEmptyResultItem(inputQuery)
|
||||||
|
}
|
||||||
|
val downloadType = DownloadViewModel.Type.valueOf(type ?: downloadViewModel.getDownloadType(url = result.url).toString())
|
||||||
|
if (sharedPreferences.getBoolean("download_card", true) && !background){
|
||||||
|
val bottomSheet = DownloadBottomSheetDialog(
|
||||||
|
result = result,
|
||||||
|
type = downloadType)
|
||||||
|
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
|
||||||
|
}else{
|
||||||
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
val downloadItem = downloadViewModel.createDownloadItemFromResult(
|
||||||
|
result = result,
|
||||||
|
givenType = downloadType)
|
||||||
|
|
||||||
|
if (downloadType == DownloadViewModel.Type.command && command.isNotBlank()){
|
||||||
|
downloadItem.format.format_note = command
|
||||||
}else{
|
}else{
|
||||||
if (res.size == 1){
|
downloadItem.extraCommands = downloadItem.extraCommands + " $command"
|
||||||
showDownloadSheet(res[0]!!)
|
|
||||||
}else{
|
|
||||||
showSelectPlaylistItems(res.toList())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||||
}
|
}
|
||||||
|
this@ShareActivity.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDownloadSheet(it: ResultItem){
|
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(
|
|
||||||
result = it,
|
|
||||||
type = downloadType)
|
|
||||||
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
|
|
||||||
}else{
|
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
|
||||||
val downloadItem = downloadViewModel.createDownloadItemFromResult(
|
|
||||||
result = it,
|
|
||||||
givenType = downloadType)
|
|
||||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
|
||||||
}
|
|
||||||
this.finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSelectPlaylistItems(it: List<ResultItem?>){
|
private fun showSelectPlaylistItems(it: List<ResultItem?>){
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,9 @@ open class ErrorDialogActivity : BaseActivity() {
|
||||||
}.setOnDismissListener {
|
}.setOnDismissListener {
|
||||||
this.finish()
|
this.finish()
|
||||||
}
|
}
|
||||||
errDialog.show()
|
this.runOnUiThread {
|
||||||
|
errDialog.show()
|
||||||
|
}
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -706,7 +706,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
|
if (sharedPreferences!!.getBoolean("download_card", true) && selectedObjects!!.size == 1) {
|
||||||
showSingleDownloadSheet(
|
showSingleDownloadSheet(
|
||||||
selectedObjects!![0],
|
selectedObjects!![0],
|
||||||
DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
|
downloadViewModel.getDownloadType(url = selectedObjects!![0].url)
|
||||||
)
|
)
|
||||||
}else{
|
}else{
|
||||||
val downloadList = withContext(Dispatchers.IO){
|
val downloadList = withContext(Dispatchers.IO){
|
||||||
|
|
@ -714,15 +714,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
if (sharedPreferences!!.getBoolean("download_card", true)) {
|
||||||
if (selectedObjects!!.size == 1){
|
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
|
||||||
showSingleDownloadSheet(
|
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
|
||||||
selectedObjects!![0],
|
|
||||||
DownloadViewModel.Type.valueOf(sharedPreferences!!.getString("preferred_download_type", "video")!!)
|
|
||||||
)
|
|
||||||
}else{
|
|
||||||
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
|
|
||||||
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
downloadViewModel.queueDownloads(downloadList)
|
downloadViewModel.queueDownloads(downloadList)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -10,9 +11,11 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
|
@ -41,6 +44,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
private lateinit var commandTemplateDao: CommandTemplateDao
|
private lateinit var commandTemplateDao: CommandTemplateDao
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
||||||
|
private lateinit var sharedPreferences : SharedPreferences
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -48,6 +52,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
|
||||||
onDownloadItemUpdateListener = listener
|
onDownloadItemUpdateListener = listener
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
@ -153,6 +158,10 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
tabLayout.selectTab(tabLayout.getTabAt(position))
|
tabLayout.selectTab(tabLayout.getTabAt(position))
|
||||||
runCatching {
|
runCatching {
|
||||||
|
sharedPreferences.edit(commit = true) {
|
||||||
|
putString("last_used_download_type",
|
||||||
|
listOf(Type.audio, Type.video, Type.command)[position].toString())
|
||||||
|
}
|
||||||
updateTitleAuthorWhenSwitching()
|
updateTitleAuthorWhenSwitching()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.isEmpty
|
import androidx.core.view.isEmpty
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
|
@ -140,17 +142,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||||
|
|
||||||
val callback = FileUtil.getURIFromResult(this@DownloadAudioFragment, requireActivity()){result ->
|
pathResultLauncher.launch(intent)
|
||||||
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)
|
freeSpace = view.findViewById(R.id.freespace)
|
||||||
val free = FileUtil.convertFileSize(
|
val free = FileUtil.convertFileSize(
|
||||||
|
|
@ -296,4 +288,28 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
state.putBoolean("updated", true)
|
state.putBoolean("updated", true)
|
||||||
onViewCreated(requireView(),savedInstanceState = state)
|
onViewCreated(requireView(),savedInstanceState = state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var pathResultLauncher = 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloadcard
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Looper
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
|
|
@ -16,6 +19,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -158,6 +162,11 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sharedPreferences.edit(commit = true) {
|
||||||
|
putString("last_used_download_type",
|
||||||
|
type.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
|
|
@ -184,6 +193,10 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
tabLayout.selectTab(tabLayout.getTabAt(position))
|
tabLayout.selectTab(tabLayout.getTabAt(position))
|
||||||
runCatching {
|
runCatching {
|
||||||
|
sharedPreferences.edit(commit = true) {
|
||||||
|
putString("last_used_download_type",
|
||||||
|
listOf(Type.audio, Type.video, Type.command)[position].toString())
|
||||||
|
}
|
||||||
updateTitleAuthorWhenSwitching()
|
updateTitleAuthorWhenSwitching()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -326,13 +339,18 @@ class DownloadBottomSheetDialog(private var result: ResultItem, private val type
|
||||||
|
|
||||||
if (result.url.isBlank()){
|
if (result.url.isBlank()){
|
||||||
withContext(Dispatchers.Main){dismiss()}
|
withContext(Dispatchers.Main){dismiss()}
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = resultViewModel.parseQueries(listOf(result.url))
|
val result = resultViewModel.parseQuery(result.url, true)
|
||||||
if (result.isEmpty()){
|
if (result.isEmpty()){
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
|
Looper.prepare().run {
|
||||||
|
Toast.makeText(requireContext(), "No Results Found!", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.size == 1 && result[0] != null){
|
if (result.size == 1 && result[0] != null){
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,9 @@ import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -132,7 +134,10 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
item.downloadStartTime = it.timeInMillis
|
item.downloadStartTime = it.timeInMillis
|
||||||
}
|
}
|
||||||
runBlocking {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(items)
|
val chunks = items.chunked(10)
|
||||||
|
for (c in chunks) {
|
||||||
|
downloadViewModel.queueDownloads(c)
|
||||||
|
}
|
||||||
val first = items.first()
|
val first = items.first()
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(first.downloadStartTime)
|
||||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||||
|
|
@ -145,7 +150,11 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
scheduleBtn.isEnabled = false
|
scheduleBtn.isEnabled = false
|
||||||
download.isEnabled = false
|
download.isEnabled = false
|
||||||
runBlocking {
|
runBlocking {
|
||||||
downloadViewModel.queueDownloads(items)
|
val chunks = items.chunked(10)
|
||||||
|
for (c in chunks) {
|
||||||
|
downloadViewModel.queueDownloads(c)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
@ -206,13 +215,13 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onContinueOnBackground(items: List<DownloadItem?>) {
|
override fun onContinueOnBackground() {
|
||||||
requireActivity().lifecycleScope.launch {
|
requireActivity().lifecycleScope.launch {
|
||||||
val ids = mutableListOf<Long>()
|
val ids = mutableListOf<Long>()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
items.map { it!!.status = DownloadRepository.Status.Saved.toString() }
|
items.map { it.status = DownloadRepository.Status.Saved.toString() }
|
||||||
items.forEach {
|
items.forEach {
|
||||||
ids.add(downloadViewModel.repository.insert(it!!))
|
ids.add(downloadViewModel.repository.insert(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val id = System.currentTimeMillis().toInt()
|
val id = System.currentTimeMillis().toInt()
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -130,16 +131,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||||
val callback = FileUtil.getURIFromResult(this@DownloadVideoFragment, requireActivity()){ result ->
|
pathResultLauncher.launch(intent)
|
||||||
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)
|
freeSpace = view.findViewById(R.id.freespace)
|
||||||
|
|
@ -311,5 +303,27 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
onViewCreated(requireView(),savedInstanceState = state)
|
onViewCreated(requireView(),savedInstanceState = state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var pathResultLauncher = 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
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +130,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
if (items.size > 10){
|
if (items.size > 10){
|
||||||
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
continueInBackgroundSnackBar = Snackbar.make(view, R.string.update_formats_background, Snackbar.LENGTH_LONG)
|
||||||
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
continueInBackgroundSnackBar.setAction(R.string.ok) {
|
||||||
listener.onContinueOnBackground(items)
|
listener.onContinueOnBackground()
|
||||||
this@FormatSelectionBottomSheetDialog.dismiss()
|
this@FormatSelectionBottomSheetDialog.dismiss()
|
||||||
}
|
}
|
||||||
continueInBackgroundSnackBar.show()
|
continueInBackgroundSnackBar.show()
|
||||||
|
|
@ -369,7 +369,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
|
|
||||||
interface OnFormatClickListener{
|
interface OnFormatClickListener{
|
||||||
fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>)
|
fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>)
|
||||||
fun onContinueOnBackground(items : List<DownloadItem?>) {}
|
fun onContinueOnBackground() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormatTuple internal constructor(
|
class FormatTuple internal constructor(
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
infoUtil = InfoUtil(requireActivity())
|
infoUtil = InfoUtil(requireActivity())
|
||||||
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
|
|
||||||
|
|
@ -160,14 +160,12 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val notificationUtil = NotificationUtil(requireContext())
|
val notificationUtil = NotificationUtil(requireContext())
|
||||||
val activeAndQueued = withContext(Dispatchers.IO){
|
val activeAndQueued = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getActiveAndQueuedDownloads()
|
downloadViewModel.getActiveAndQueuedDownloadIDs()
|
||||||
}
|
}
|
||||||
activeAndQueued.forEach {
|
downloadViewModel.cancelActiveQueued()
|
||||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
activeAndQueued.forEach { id ->
|
||||||
downloadViewModel.updateDownload(it)
|
|
||||||
val id = it.id.toInt()
|
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,15 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
newCookie.isEnabled = useCookiesPref
|
newCookie.isEnabled = useCookiesPref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initChips() {
|
||||||
|
val new = view?.findViewById<Chip>(R.id.newCookie)
|
||||||
|
new?.setOnClickListener {
|
||||||
|
showDialog("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initMenu() {
|
private fun initMenu() {
|
||||||
|
topAppBar.menu.getItem(1).isChecked = preferences.getBoolean("use_header", false)
|
||||||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
when (m.itemId) {
|
when (m.itemId) {
|
||||||
R.id.delete_cookies -> {
|
R.id.delete_cookies -> {
|
||||||
|
|
@ -131,18 +139,15 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
cookiesViewModel.exportToClipboard()
|
cookiesViewModel.exportToClipboard()
|
||||||
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
|
Snackbar.make(recyclerView, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
R.id.use_header -> {
|
||||||
|
m.isChecked = !m.isChecked
|
||||||
|
preferences.edit().putBoolean("use_header", m.isChecked).apply()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initChips() {
|
|
||||||
val new = view?.findViewById<Chip>(R.id.newCookie)
|
|
||||||
new?.setOnClickListener {
|
|
||||||
showDialog("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showDialog(url: String){
|
private fun showDialog(url: String){
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
builder.setTitle(getString(R.string.cookies))
|
builder.setTitle(getString(R.string.cookies))
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,29 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more
|
package com.deniscerri.ytdlnis.ui.more
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.BaseActivity
|
import com.deniscerri.ytdlnis.ui.BaseActivity
|
||||||
|
import com.google.accompanist.web.AccompanistWebChromeClient
|
||||||
|
import com.google.accompanist.web.AccompanistWebViewClient
|
||||||
|
import com.google.accompanist.web.WebView
|
||||||
|
import com.google.accompanist.web.rememberWebViewState
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
@ -24,12 +35,14 @@ import kotlinx.coroutines.withContext
|
||||||
class WebViewActivity : BaseActivity() {
|
class WebViewActivity : BaseActivity() {
|
||||||
private lateinit var cookiesViewModel: CookieViewModel
|
private lateinit var cookiesViewModel: CookieViewModel
|
||||||
private lateinit var webView: WebView
|
private lateinit var webView: WebView
|
||||||
|
private lateinit var webViewCompose: ComposeView
|
||||||
private lateinit var toolbar: MaterialToolbar
|
private lateinit var toolbar: MaterialToolbar
|
||||||
private lateinit var generateBtn: MaterialButton
|
private lateinit var generateBtn: MaterialButton
|
||||||
private lateinit var cookieManager: CookieManager
|
private lateinit var cookieManager: CookieManager
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
private lateinit var cookies: String
|
private lateinit var cookies: String
|
||||||
|
private lateinit var webViewClient: AccompanistWebViewClient
|
||||||
|
private lateinit var preferences: SharedPreferences
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -40,13 +53,20 @@ class WebViewActivity : BaseActivity() {
|
||||||
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
|
||||||
toolbar = appbar.findViewById(R.id.webviewToolbar)
|
toolbar = appbar.findViewById(R.id.webviewToolbar)
|
||||||
generateBtn = toolbar.findViewById(R.id.generate)
|
generateBtn = toolbar.findViewById(R.id.generate)
|
||||||
|
webViewCompose = findViewById(R.id.webview_compose)
|
||||||
cookieManager = CookieManager.getInstance()
|
cookieManager = CookieManager.getInstance()
|
||||||
cookieManager.removeAllCookies(null)
|
cookieManager.removeAllCookies(null)
|
||||||
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
webView = findViewById(R.id.webview)
|
webViewClient = object : AccompanistWebViewClient() {
|
||||||
webView.settings.javaScriptEnabled = true
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
webView.settings.domStorageEnabled = true
|
super.onPageFinished(view, url)
|
||||||
webView.settings.javaScriptCanOpenWindowsAutomatically = true
|
kotlin.runCatching {
|
||||||
|
toolbar.title = view?.title ?: ""
|
||||||
|
cookies = CookieManager.getInstance().getCookie(view?.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cookieManager = CookieManager.getInstance()
|
cookieManager = CookieManager.getInstance()
|
||||||
|
|
||||||
|
|
@ -55,21 +75,8 @@ class WebViewActivity : BaseActivity() {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
val webViewClient: WebViewClient = object : WebViewClient() {
|
|
||||||
|
|
||||||
override fun onPageFinished(view: WebView, url: String) {
|
|
||||||
super.onPageFinished(view, url)
|
|
||||||
kotlin.runCatching {
|
|
||||||
toolbar.title = view.title
|
|
||||||
cookies = CookieManager.getInstance().getCookie(view.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
webView.webViewClient = webViewClient
|
|
||||||
|
|
||||||
generateBtn.setOnClickListener {
|
generateBtn.setOnClickListener {
|
||||||
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
||||||
Log.e("asas", it)
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
|
|
@ -91,8 +98,40 @@ class WebViewActivity : BaseActivity() {
|
||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
webView.loadUrl(url)
|
webViewCompose.apply {
|
||||||
|
setContent { WebViewView() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
@Composable
|
||||||
|
fun WebViewView() {
|
||||||
|
val webViewChromeClient = remember {
|
||||||
|
object : AccompanistWebChromeClient() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues ->
|
||||||
|
WebView(
|
||||||
|
state = rememberWebViewState(url), client = webViewClient, chromeClient = webViewChromeClient,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize(),
|
||||||
|
captureBackPresses = false, factory = { context ->
|
||||||
|
WebView(context).apply {
|
||||||
|
settings.run {
|
||||||
|
javaScriptEnabled = true
|
||||||
|
domStorageEnabled = true
|
||||||
|
javaScriptCanOpenWindowsAutomatically = true
|
||||||
|
preferences.edit().putString("useragent_header", userAgentString).apply()
|
||||||
|
}
|
||||||
|
cookieManager.setAcceptThirdPartyCookies(this, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
import com.deniscerri.ytdlnis.receiver.AlarmStartReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.AlarmStopReceiver
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
@ -21,6 +20,14 @@ class DownloadSettingsFragment : BaseSettingsFragment() {
|
||||||
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
setPreferencesFromResource(R.xml.downloading_preferences, rootKey)
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
|
val rememberDownloadType = findPreference<SwitchPreferenceCompat>("remember_download_type")
|
||||||
|
val downloadType = findPreference<ListPreference>("preferred_download_type")
|
||||||
|
downloadType?.isEnabled = rememberDownloadType?.isChecked == false
|
||||||
|
rememberDownloadType?.setOnPreferenceClickListener {
|
||||||
|
downloadType?.isEnabled = !rememberDownloadType.isChecked
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
val useScheduler = findPreference<SwitchPreferenceCompat>("use_scheduler")
|
val useScheduler = findPreference<SwitchPreferenceCompat>("use_scheduler")
|
||||||
val scheduleStart = findPreference<Preference>("schedule_start")
|
val scheduleStart = findPreference<Preference>("schedule_start")
|
||||||
scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
|
scheduleStart?.summary = preferences.getString("schedule_start", "00:00")
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.HorizontalScrollView
|
import android.widget.HorizontalScrollView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
|
@ -39,6 +40,7 @@ import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnItemClickListener {
|
class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnItemClickListener {
|
||||||
private var topAppBar: MaterialToolbar? = null
|
private var topAppBar: MaterialToolbar? = null
|
||||||
|
private lateinit var noResults: RelativeLayout
|
||||||
private lateinit var terminalViewModel: TerminalViewModel
|
private lateinit var terminalViewModel: TerminalViewModel
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
|
@ -52,6 +54,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
noResults = view.findViewById(R.id.no_results)
|
||||||
val recycler = view.findViewById<RecyclerView>(R.id.terminal_recycler)
|
val recycler = view.findViewById<RecyclerView>(R.id.terminal_recycler)
|
||||||
val adapter = TerminalDownloadsAdapter(this@TerminalDownloadsListFragment, requireActivity())
|
val adapter = TerminalDownloadsAdapter(this@TerminalDownloadsListFragment, requireActivity())
|
||||||
recycler.adapter = adapter
|
recycler.adapter = adapter
|
||||||
|
|
@ -73,9 +76,7 @@ class TerminalDownloadsListFragment : Fragment(), TerminalDownloadsAdapter.OnIte
|
||||||
|
|
||||||
terminalViewModel.getTerminals().collectLatest {
|
terminalViewModel.getTerminals().collectLatest {
|
||||||
adapter.submitList(it)
|
adapter.submitList(it)
|
||||||
if (it.isEmpty()){
|
noResults.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE
|
||||||
findNavController().navigate(R.id.terminalFragment)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,12 @@ import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomappbar.BottomAppBar
|
import com.google.android.material.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.lang.StringBuilder
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -102,11 +105,13 @@ class TerminalFragment : Fragment() {
|
||||||
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
|
topAppBar!!.setNavigationOnClickListener { requireActivity().finish() }
|
||||||
|
|
||||||
input = view.findViewById(R.id.command_edittext)
|
input = view.findViewById(R.id.command_edittext)
|
||||||
|
fab = view.findViewById(R.id.command_fab)
|
||||||
|
|
||||||
if (arguments?.getLong("id") != null){
|
if (arguments?.getLong("id") != null){
|
||||||
downloadID = requireArguments().getLong("id")
|
downloadID = requireArguments().getLong("id")
|
||||||
if(downloadID != 0L){
|
if(downloadID != 0L){
|
||||||
input!!.visibility = View.GONE
|
input!!.visibility = View.GONE
|
||||||
|
showCancelFab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,6 +150,7 @@ class TerminalFragment : Fragment() {
|
||||||
}else{
|
}else{
|
||||||
bottomAppBar.menu.getItem(1).icon?.alpha = 255
|
bottomAppBar.menu.getItem(1).icon?.alpha = 255
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
bottomAppBar.setOnMenuItemClickListener {
|
bottomAppBar.setOnMenuItemClickListener {
|
||||||
when(it.itemId){
|
when(it.itemId){
|
||||||
|
|
@ -195,7 +201,6 @@ class TerminalFragment : Fragment() {
|
||||||
output!!.setTextIsSelectable(true)
|
output!!.setTextIsSelectable(true)
|
||||||
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
|
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
|
||||||
input!!.requestFocus()
|
input!!.requestFocus()
|
||||||
fab = view.findViewById(R.id.command_fab)
|
|
||||||
fab!!.setOnClickListener {
|
fab!!.setOnClickListener {
|
||||||
if (fab!!.text == getString(R.string.run_command)){
|
if (fab!!.text == getString(R.string.run_command)){
|
||||||
input!!.visibility = View.GONE
|
input!!.visibility = View.GONE
|
||||||
|
|
@ -205,7 +210,7 @@ class TerminalFragment : Fragment() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val command = input!!.text.toString().replace("yt-dlp", "")
|
val command = input!!.text.toString().replace("yt-dlp", "")
|
||||||
downloadID = withContext(Dispatchers.IO){
|
downloadID = withContext(Dispatchers.IO){
|
||||||
terminalViewModel.insert(TerminalItem(command = command))
|
terminalViewModel.insert(TerminalItem(command = command, log = output!!.text.toString()))
|
||||||
}
|
}
|
||||||
terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command))
|
terminalViewModel.startTerminalDownloadWorker(TerminalItem(downloadID, command))
|
||||||
runWorkerListener()
|
runWorkerListener()
|
||||||
|
|
@ -293,6 +298,22 @@ class TerminalFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runWorkerListener(){
|
private fun runWorkerListener(){
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
terminalViewModel.getTerminal(downloadID).collectLatest {
|
||||||
|
kotlin.runCatching {
|
||||||
|
requireActivity().runOnUiThread{
|
||||||
|
if (it != null){
|
||||||
|
if (!it.log.isNullOrBlank()) {
|
||||||
|
output?.text = it.log
|
||||||
|
}
|
||||||
|
output?.scrollTo(0, output!!.height)
|
||||||
|
scrollView?.fullScroll(View.FOCUS_DOWN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WorkManager.getInstance(requireContext())
|
WorkManager.getInstance(requireContext())
|
||||||
.getWorkInfosByTagLiveData(downloadID.toString())
|
.getWorkInfosByTagLiveData(downloadID.toString())
|
||||||
|
|
@ -310,25 +331,9 @@ class TerminalFragment : Fragment() {
|
||||||
}
|
}
|
||||||
return@forEach
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -309,19 +309,4 @@ object FileUtil {
|
||||||
return "${DecimalFormat("#,##0.#", symbols).format(s / 1024.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}"
|
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,49 +1,26 @@
|
||||||
package com.deniscerri.ytdlnis.util
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.DownloadManager
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
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.os.Looper
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.navigation.findNavController
|
|
||||||
import androidx.preference.AndroidResources
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.App
|
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
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.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.ErrorDialogActivity
|
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.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
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.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
@ -475,6 +452,12 @@ class InfoUtil(private val context: Context) {
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
|
|
@ -482,6 +465,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--proxy", proxy)
|
request.addOption("--proxy", proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val res = YoutubeDL.getInstance().execute(request)
|
val res = YoutubeDL.getInstance().execute(request)
|
||||||
val results: Array<String?> = try {
|
val results: Array<String?> = try {
|
||||||
val lineSeparator = System.getProperty("line.separator")
|
val lineSeparator = System.getProperty("line.separator")
|
||||||
|
|
@ -528,6 +513,12 @@ class InfoUtil(private val context: Context) {
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -536,6 +527,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--proxy", proxy)
|
request.addOption("--proxy", proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
YoutubeDL.getInstance().execute(request){ progress, _, line ->
|
YoutubeDL.getInstance().execute(request){ progress, _, line ->
|
||||||
try{
|
try{
|
||||||
if (line.isNotBlank()){
|
if (line.isNotBlank()){
|
||||||
|
|
@ -592,6 +585,12 @@ class InfoUtil(private val context: Context) {
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
|
|
@ -599,6 +598,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--proxy", proxy)
|
request.addOption("--proxy", proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||||
val results: List<String?> = try {
|
val results: List<String?> = try {
|
||||||
val lineSeparator = System.getProperty("line.separator")
|
val lineSeparator = System.getProperty("line.separator")
|
||||||
|
|
@ -759,6 +760,12 @@ class InfoUtil(private val context: Context) {
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -768,6 +775,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--proxy", proxy)
|
request.addOption("--proxy", proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||||
val jsonObject = JSONObject(youtubeDLResponse.out)
|
val jsonObject = JSONObject(youtubeDLResponse.out)
|
||||||
|
|
||||||
|
|
@ -921,6 +930,12 @@ class InfoUtil(private val context: Context) {
|
||||||
FileUtil.getCookieFile(context){
|
FileUtil.getCookieFile(context){
|
||||||
request.addOption("--cookies", it)
|
request.addOption("--cookies", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -930,6 +945,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--proxy", proxy)
|
request.addOption("--proxy", proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
||||||
val results: Array<String?> = try {
|
val results: Array<String?> = try {
|
||||||
val lineSeparator = System.getProperty("line.separator")
|
val lineSeparator = System.getProperty("line.separator")
|
||||||
|
|
@ -958,22 +975,6 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFilePaths(request: YoutubeDLRequest) : List<String>{
|
|
||||||
return try{
|
|
||||||
request.addOption("--print", "filename")
|
|
||||||
val res = YoutubeDL.getInstance().execute(request)
|
|
||||||
val results: Array<String?> = try {
|
|
||||||
val lineSeparator = System.getProperty("line.separator")
|
|
||||||
res.out.split(lineSeparator!!).toTypedArray()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
arrayOf(res.out)
|
|
||||||
}
|
|
||||||
results.filter { !it.isNullOrBlank() }.map { it!!.substring(0, it.lastIndexOf(".")) }.map { it.substring(it.lastIndexOf("/") + 1, it.length) }
|
|
||||||
}catch (e: Exception){
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||||
val url = downloadItem.url
|
val url = downloadItem.url
|
||||||
val request = YoutubeDLRequest(url)
|
val request = YoutubeDLRequest(url)
|
||||||
|
|
@ -982,6 +983,9 @@ class InfoUtil(private val context: Context) {
|
||||||
val downDir : File
|
val downDir : File
|
||||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||||
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
||||||
|
request.addOption("--print", "after_video:'%(filename)s'")
|
||||||
|
request.addOption("--progress")
|
||||||
|
request.addOption("-v")
|
||||||
}else{
|
}else{
|
||||||
val cacheDir = FileUtil.getCachePath(context)
|
val cacheDir = FileUtil.getCachePath(context)
|
||||||
downDir = File(cacheDir, downloadItem.id.toString())
|
downDir = File(cacheDir, downloadItem.id.toString())
|
||||||
|
|
@ -1010,6 +1014,27 @@ class InfoUtil(private val context: Context) {
|
||||||
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
|
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
|
||||||
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("restrict_filenames", true)) request.addOption("--restrict-filenames")
|
||||||
|
if (sharedPreferences.getBoolean("use_cookies", false)){
|
||||||
|
FileUtil.getCookieFile(context){
|
||||||
|
request.addOption("--cookies", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val proxy = sharedPreferences.getString("proxy", "")
|
||||||
|
if (proxy!!.isNotBlank()){
|
||||||
|
request.addOption("--proxy", proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||||
|
if(keepCache){
|
||||||
|
request.addOption("--part")
|
||||||
|
request.addOption("--keep-fragments")
|
||||||
|
}
|
||||||
|
|
||||||
|
val preferredAudioCodec = sharedPreferences.getString("audio_codec", "")!!
|
||||||
|
val aCodecPref = "ba[acodec~='^($preferredAudioCodec)']"
|
||||||
|
|
||||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||||
request.addOption("--trim-filenames", downDir.absolutePath.length + 120)
|
request.addOption("--trim-filenames", downDir.absolutePath.length + 120)
|
||||||
|
|
||||||
|
|
@ -1106,34 +1131,19 @@ class InfoUtil(private val context: Context) {
|
||||||
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader,channel)s")
|
downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader,channel)s")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
|
|
||||||
request.addOption("--restrict-filenames")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
|
||||||
FileUtil.getCookieFile(context){
|
|
||||||
request.addOption("--cookies", it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
|
||||||
if (proxy!!.isNotBlank()){
|
|
||||||
request.addOption("--proxy", proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
|
||||||
if(keepCache){
|
|
||||||
request.addOption("--part")
|
|
||||||
request.addOption("--keep-fragments")
|
|
||||||
}
|
|
||||||
|
|
||||||
when(type){
|
when(type){
|
||||||
DownloadViewModel.Type.audio -> {
|
DownloadViewModel.Type.audio -> {
|
||||||
val supportedContainers = context.resources.getStringArray(R.array.audio_containers)
|
val supportedContainers = context.resources.getStringArray(R.array.audio_containers)
|
||||||
|
|
||||||
var audioQualityId : String = downloadItem.format.format_id
|
var audioQualityId : String = downloadItem.format.format_id
|
||||||
if (audioQualityId.isBlank() || audioQualityId == "0" || audioQualityId == context.getString(R.string.best_quality) || audioQualityId == "best") audioQualityId = ""
|
if (audioQualityId.isBlank() || listOf("0", context.getString(R.string.best_quality), "best", "").contains(audioQualityId)){
|
||||||
else if (audioQualityId == context.getString(R.string.worst_quality) || audioQualityId == "worst") audioQualityId = "worstaudio"
|
audioQualityId = ""
|
||||||
|
if (preferredAudioCodec.isNotBlank()){
|
||||||
|
audioQualityId = "${aCodecPref}/bestaudio"
|
||||||
|
}
|
||||||
|
}else if (listOf(context.getString(R.string.worst_quality), "worst").contains(audioQualityId)){
|
||||||
|
audioQualityId = "worstaudio"
|
||||||
|
}
|
||||||
|
|
||||||
val ext = downloadItem.container
|
val ext = downloadItem.container
|
||||||
if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId)
|
if (audioQualityId.isNotBlank()) request.addOption("-f", audioQualityId)
|
||||||
|
|
@ -1230,6 +1240,9 @@ class InfoUtil(private val context: Context) {
|
||||||
val f = StringBuilder()
|
val f = StringBuilder()
|
||||||
|
|
||||||
if(!usingGenericFormat){
|
if(!usingGenericFormat){
|
||||||
|
if (preferredAudioCodec.isNotBlank()){
|
||||||
|
f.append("$videof+$aCodecPref/")
|
||||||
|
}
|
||||||
val aa = if (audiof.isNotBlank()) "+$audiof" else ""
|
val aa = if (audiof.isNotBlank()) "+$audiof" else ""
|
||||||
f.append("$videof$aa/$videof/best")
|
f.append("$videof$aa/$videof/best")
|
||||||
|
|
||||||
|
|
@ -1257,7 +1270,12 @@ class InfoUtil(private val context: Context) {
|
||||||
.toString()
|
.toString()
|
||||||
.split(",")
|
.split(",")
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.ifEmpty { listOf(audiof) }
|
.ifEmpty {
|
||||||
|
val list = mutableListOf<String>()
|
||||||
|
if (preferredAudioCodec.isNotBlank()) list.add("ba[acodec~='^($preferredAudioCodec)']")
|
||||||
|
list.add(audiof)
|
||||||
|
list
|
||||||
|
}
|
||||||
|
|
||||||
val preferredAudioFormats = if (downloadItem.videoPreferences.audioFormatIDs.isEmpty()) {
|
val preferredAudioFormats = if (downloadItem.videoPreferences.audioFormatIDs.isEmpty()) {
|
||||||
preferredAudioFormatIDs
|
preferredAudioFormatIDs
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,26 @@ import android.app.PendingIntent
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.App.Companion.instance
|
||||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class NotificationUtil(var context: Context) {
|
class NotificationUtil(var context: Context) {
|
||||||
|
|
@ -27,6 +34,15 @@ class NotificationUtil(var context: Context) {
|
||||||
private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID)
|
private val commandDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID)
|
||||||
private val finishedDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_FINISHED_CHANNEL_ID)
|
private val finishedDownloadNotificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||||
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
private val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
|
||||||
|
private val resources: Resources
|
||||||
|
private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val confTmp = Configuration(context.resources.configuration)
|
||||||
|
confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
|
||||||
|
val metrics = DisplayMetrics()
|
||||||
|
resources = Resources(context.assets, metrics, confTmp)
|
||||||
|
}
|
||||||
|
|
||||||
fun createNotificationChannel() {
|
fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
|
@ -35,7 +51,7 @@ class NotificationUtil(var context: Context) {
|
||||||
)
|
)
|
||||||
|
|
||||||
//downloading worker notification
|
//downloading worker notification
|
||||||
var name: CharSequence = context.getString(R.string.downloading)
|
var name: CharSequence = resources.getString(R.string.downloading)
|
||||||
var description = "WorkManager Default Notification"
|
var description = "WorkManager Default Notification"
|
||||||
val importance = NotificationManager.IMPORTANCE_LOW
|
val importance = NotificationManager.IMPORTANCE_LOW
|
||||||
var channel = NotificationChannel(DOWNLOAD_WORKER_CHANNEL_ID, name, importance)
|
var channel = NotificationChannel(DOWNLOAD_WORKER_CHANNEL_ID, name, importance)
|
||||||
|
|
@ -43,30 +59,30 @@ class NotificationUtil(var context: Context) {
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
|
||||||
//gui downloads
|
//gui downloads
|
||||||
name = context.getString(R.string.download_notification_channel_name)
|
name = resources.getString(R.string.download_notification_channel_name)
|
||||||
description = context.getString(R.string.download_notification_channel_description)
|
description = resources.getString(R.string.download_notification_channel_description)
|
||||||
channel = NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
channel = NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||||
channel.description = description
|
channel.description = description
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
|
||||||
//command downloads
|
//command downloads
|
||||||
name = context.getString(R.string.command_download_notification_channel_name)
|
name = resources.getString(R.string.command_download_notification_channel_name)
|
||||||
description =
|
description =
|
||||||
context.getString(R.string.command_download_notification_channel_description)
|
resources.getString(R.string.command_download_notification_channel_description)
|
||||||
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
channel = NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance)
|
||||||
channel.description = description
|
channel.description = description
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
|
||||||
//finished or errored downloads
|
//finished or errored downloads
|
||||||
name = context.getString(R.string.finished_download_notification_channel_name)
|
name = resources.getString(R.string.finished_download_notification_channel_name)
|
||||||
description =
|
description =
|
||||||
context.getString(R.string.finished_download_notification_channel_description)
|
resources.getString(R.string.finished_download_notification_channel_description)
|
||||||
channel = NotificationChannel(DOWNLOAD_FINISHED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
channel = NotificationChannel(DOWNLOAD_FINISHED_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
||||||
channel.description = description
|
channel.description = description
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
|
||||||
//misc
|
//misc
|
||||||
name = context.getString(R.string.misc)
|
name = resources.getString(R.string.misc)
|
||||||
description = ""
|
description = ""
|
||||||
channel = NotificationChannel(DOWNLOAD_MISC_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
channel = NotificationChannel(DOWNLOAD_MISC_CHANNEL_ID, name, NotificationManager.IMPORTANCE_HIGH)
|
||||||
channel.description = description
|
channel.description = description
|
||||||
|
|
@ -87,12 +103,12 @@ class NotificationUtil(var context: Context) {
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_WORKER_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_WORKER_CHANNEL_ID)
|
||||||
|
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setContentTitle(context.getString(R.string.downloading))
|
.setContentTitle(resources.getString(R.string.downloading))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
R.drawable.ic_launcher_foreground_large
|
R.drawable.ic_launcher_foreground_large
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -118,7 +134,7 @@ class NotificationUtil(var context: Context) {
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
android.R.drawable.stat_sys_download
|
android.R.drawable.stat_sys_download
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -140,7 +156,7 @@ class NotificationUtil(var context: Context) {
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
R.drawable.ic_launcher_foreground_large
|
R.drawable.ic_launcher_foreground_large
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -157,7 +173,7 @@ class NotificationUtil(var context: Context) {
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
notificationBuilder.addAction(0, context.getString(R.string.resume), resumeNotificationPendingIntent)
|
notificationBuilder.addAction(0, resources.getString(R.string.resume), resumeNotificationPendingIntent)
|
||||||
notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID + itemID, notificationBuilder.build())
|
notificationManager.notify(DOWNLOAD_RESUME_NOTIFICATION_ID + itemID, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,11 +181,11 @@ class NotificationUtil(var context: Context) {
|
||||||
val notificationBuilder = getBuilder(channel)
|
val notificationBuilder = getBuilder(channel)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(context.getString(R.string.updating_download_data))
|
.setContentTitle(resources.getString(R.string.updating_download_data))
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
android.R.drawable.stat_sys_download
|
android.R.drawable.stat_sys_download
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -185,11 +201,11 @@ class NotificationUtil(var context: Context) {
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle("${context.getString(R.string.downloaded)} $title")
|
.setContentTitle("${resources.getString(R.string.downloaded)} $title")
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
R.drawable.ic_launcher_foreground_large
|
R.drawable.ic_launcher_foreground_large
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -228,8 +244,8 @@ class NotificationUtil(var context: Context) {
|
||||||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
notificationBuilder.addAction(0, context.getString(R.string.Open_File), openNotificationPendingIntent)
|
notificationBuilder.addAction(0, resources.getString(R.string.Open_File), openNotificationPendingIntent)
|
||||||
notificationBuilder.addAction(0, context.getString(R.string.share), shareNotificationPendingIntent)
|
notificationBuilder.addAction(0, resources.getString(R.string.share), shareNotificationPendingIntent)
|
||||||
}catch (_: Exception){}
|
}catch (_: Exception){}
|
||||||
}
|
}
|
||||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||||
|
|
@ -263,12 +279,12 @@ class NotificationUtil(var context: Context) {
|
||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle("${context.getString(R.string.failed_download)}: $title")
|
.setContentTitle("${resources.getString(R.string.failed_download)}: $title")
|
||||||
.setContentText(error)
|
.setContentText(error)
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
R.drawable.ic_launcher_foreground_large
|
R.drawable.ic_launcher_foreground_large
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -278,7 +294,7 @@ class NotificationUtil(var context: Context) {
|
||||||
.clearActions()
|
.clearActions()
|
||||||
if (logID != null){
|
if (logID != null){
|
||||||
notificationBuilder.setContentIntent(errorPendingIntent)
|
notificationBuilder.setContentIntent(errorPendingIntent)
|
||||||
notificationBuilder.addAction(0, context.getString(R.string.logs), errorPendingIntent)
|
notificationBuilder.addAction(0, resources.getString(R.string.logs), errorPendingIntent)
|
||||||
}
|
}
|
||||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +315,7 @@ class NotificationUtil(var context: Context) {
|
||||||
|
|
||||||
val notificationBuilder = getBuilder(channel)
|
val notificationBuilder = getBuilder(channel)
|
||||||
var contentText = ""
|
var contentText = ""
|
||||||
if (queue > 1) contentText += """${queue - 1} ${context.getString(R.string.items_left)}""" + "\n"
|
if (queue > 1) contentText += """${queue - 1} ${resources.getString(R.string.items_left)}""" + "\n"
|
||||||
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
|
contentText += desc.replace("\\[.*?\\] ".toRegex(), "")
|
||||||
|
|
||||||
val pauseIntent = Intent(context, PauseDownloadNotificationReceiver::class.java)
|
val pauseIntent = Intent(context, PauseDownloadNotificationReceiver::class.java)
|
||||||
|
|
@ -326,8 +342,8 @@ class NotificationUtil(var context: Context) {
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||||
.clearActions()
|
.clearActions()
|
||||||
.addAction(0, context.getString(R.string.pause), pauseNotificationPendingIntent)
|
.addAction(0, resources.getString(R.string.pause), pauseNotificationPendingIntent)
|
||||||
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
|
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||||
notificationManager.notify(id, notificationBuilder.build())
|
notificationManager.notify(id, notificationBuilder.build())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
@ -342,13 +358,13 @@ class NotificationUtil(var context: Context) {
|
||||||
val notificationBuilder = getBuilder(downloadMiscChannelId)
|
val notificationBuilder = getBuilder(downloadMiscChannelId)
|
||||||
|
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setContentTitle(context.getString(R.string.move_temporary_files))
|
.setContentTitle(resources.getString(R.string.move_temporary_files))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
.setCategory(Notification.CATEGORY_PROGRESS)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
android.R.drawable.stat_sys_download
|
android.R.drawable.stat_sys_download
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -367,7 +383,7 @@ class NotificationUtil(var context: Context) {
|
||||||
val contentText = "${progress}/${totalFiles}"
|
val contentText = "${progress}/${totalFiles}"
|
||||||
try {
|
try {
|
||||||
notificationBuilder.setProgress(100, progress, false)
|
notificationBuilder.setProgress(100, progress, false)
|
||||||
.setContentTitle(context.getString(R.string.move_temporary_files))
|
.setContentTitle(resources.getString(R.string.move_temporary_files))
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||||
notificationManager.notify(id, notificationBuilder.build())
|
notificationManager.notify(id, notificationBuilder.build())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -383,7 +399,7 @@ class NotificationUtil(var context: Context) {
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
android.R.drawable.stat_sys_download
|
android.R.drawable.stat_sys_download
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -394,26 +410,16 @@ class NotificationUtil(var context: Context) {
|
||||||
return notificationBuilder.build()
|
return notificationBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFormatsUpdateNotification(workID: Int): Notification {
|
fun createFormatsUpdateNotification(): Notification {
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||||
|
|
||||||
val cancelIntent = Intent(context, CancelWorkReceiver::class.java)
|
|
||||||
cancelIntent.putExtra("workID", workID)
|
|
||||||
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
context,
|
|
||||||
workID,
|
|
||||||
cancelIntent,
|
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
return notificationBuilder
|
return notificationBuilder
|
||||||
.setContentTitle(context.getString(R.string.update_formats_background))
|
.setContentTitle(resources.getString(R.string.update_formats_background))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setCategory(Notification.CATEGORY_PROGRESS)
|
.setCategory(Notification.CATEGORY_PROGRESS)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
android.R.drawable.stat_sys_download
|
android.R.drawable.stat_sys_download
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -423,23 +429,37 @@ class NotificationUtil(var context: Context) {
|
||||||
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
|
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
|
||||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||||
.clearActions()
|
.clearActions()
|
||||||
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFormatUpdateNotification(
|
fun updateFormatUpdateNotification(
|
||||||
id: Int,
|
workID: Int,
|
||||||
|
workTag: String,
|
||||||
progress: Int,
|
progress: Int,
|
||||||
queue: Int,
|
queue: Int,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||||
val contentText = """${queue - progress} ${context.getString(R.string.items_left)}"""
|
val contentText = """${queue - progress} ${resources.getString(R.string.items_left)}"""
|
||||||
|
|
||||||
|
|
||||||
|
val cancelIntent = Intent(context, CancelWorkReceiver::class.java)
|
||||||
|
cancelIntent.putExtra("workTag", workTag)
|
||||||
|
val cancelNotificationPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
workID,
|
||||||
|
cancelIntent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
notificationBuilder.setProgress(queue, progress, false)
|
notificationBuilder.setProgress(queue, progress, false)
|
||||||
.setContentTitle(context.getString(R.string.update_formats_background))
|
.setContentTitle(resources.getString(R.string.update_formats_background))
|
||||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||||
notificationManager.notify(id, notificationBuilder.build())
|
.clearActions()
|
||||||
|
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||||
|
notificationManager.notify(workID, notificationBuilder.build())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
@ -460,11 +480,11 @@ class NotificationUtil(var context: Context) {
|
||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(context.getString(R.string.finished_download_notification_channel_name))
|
.setContentTitle(resources.getString(R.string.finished_download_notification_channel_name))
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
.setSmallIcon(R.drawable.ic_launcher_foreground_large)
|
||||||
.setLargeIcon(
|
.setLargeIcon(
|
||||||
BitmapFactory.decodeResource(
|
BitmapFactory.decodeResource(
|
||||||
context.resources,
|
resources,
|
||||||
R.drawable.ic_launcher_foreground_large
|
R.drawable.ic_launcher_foreground_large
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.await
|
import androidx.work.await
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
|
|
@ -49,7 +50,7 @@ class DownloadWorker(
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
if (isStopped) return Result.success()
|
if (isStopped) return Result.success()
|
||||||
|
|
||||||
val notificationUtil = NotificationUtil(context)
|
val notificationUtil = NotificationUtil(App.instance)
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val dbManager = DBManager.getInstance(context)
|
val dbManager = DBManager.getInstance(context)
|
||||||
val dao = dbManager.downloadDao
|
val dao = dbManager.downloadDao
|
||||||
|
|
@ -119,6 +120,8 @@ class DownloadWorker(
|
||||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||||
|
|
||||||
|
|
||||||
|
val commandString = infoUtil.parseYTDLRequestString(request)
|
||||||
val logItem = LogItem(
|
val logItem = LogItem(
|
||||||
0,
|
0,
|
||||||
downloadItem.title.ifEmpty { downloadItem.url },
|
downloadItem.title.ifEmpty { downloadItem.url },
|
||||||
|
|
@ -126,7 +129,7 @@ class DownloadWorker(
|
||||||
"Title: ${downloadItem.title}\n" +
|
"Title: ${downloadItem.title}\n" +
|
||||||
"URL: ${downloadItem.url}\n" +
|
"URL: ${downloadItem.url}\n" +
|
||||||
"Type: ${downloadItem.type}\n" +
|
"Type: ${downloadItem.type}\n" +
|
||||||
"Command:\n ${infoUtil.parseYTDLRequestString(request)}\n\n",
|
"Command:\n ${commandString}\n\n",
|
||||||
downloadItem.format,
|
downloadItem.format,
|
||||||
downloadItem.type,
|
downloadItem.type,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
@ -142,6 +145,8 @@ class DownloadWorker(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||||
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to downloadItem.id))
|
||||||
|
|
@ -162,16 +167,18 @@ class DownloadWorker(
|
||||||
runBlocking {
|
runBlocking {
|
||||||
var finalPaths : List<String>?
|
var finalPaths : List<String>?
|
||||||
|
|
||||||
//if there was no cache used
|
if (noCache){
|
||||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
|
||||||
val p = infoUtil.getFilePaths(request)
|
val p = it.out.split("\r")
|
||||||
|
.filter { it.startsWith("'/storage") }
|
||||||
|
.map { it.removePrefix("'") }
|
||||||
|
.map { it.removeSuffix("'\n") }
|
||||||
finalPaths = File(FileUtil.formatPath(downloadLocation))
|
finalPaths = File(FileUtil.formatPath(downloadLocation))
|
||||||
.walkTopDown()
|
.walkTopDown()
|
||||||
.filter { it.isFile && p.contains(it.nameWithoutExtension) }
|
.filter { it.isFile && p.any { f -> f.contains(it.nameWithoutExtension) }}
|
||||||
.sortedByDescending { it.length() }
|
.sortedByDescending { it.length() }
|
||||||
.map { it.absolutePath }
|
.map { it.absolutePath }
|
||||||
.toList()
|
.toList()
|
||||||
FileUtil.scanMedia(finalPaths, context)
|
FileUtil.scanMedia(finalPaths, context)
|
||||||
if (finalPaths.isEmpty()){
|
if (finalPaths.isEmpty()){
|
||||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||||
|
|
@ -208,7 +215,7 @@ class DownloadWorker(
|
||||||
val unixtime = System.currentTimeMillis() / 1000
|
val unixtime = System.currentTimeMillis() / 1000
|
||||||
val file = File(finalPaths?.first()!!)
|
val file = File(finalPaths?.first()!!)
|
||||||
downloadItem.format.filesize = file.length()
|
downloadItem.format.filesize = file.length()
|
||||||
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
|
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id, commandString)
|
||||||
historyDao.insert(historyItem)
|
historyDao.insert(historyItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.work.ForegroundInfo
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
|
@ -43,7 +44,7 @@ class MoveCacheFilesWorker(
|
||||||
workerParams: WorkerParameters
|
workerParams: WorkerParameters
|
||||||
) : Worker(context, workerParams) {
|
) : Worker(context, workerParams) {
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
val notificationUtil = NotificationUtil(context)
|
val notificationUtil = NotificationUtil(App.instance)
|
||||||
val id = System.currentTimeMillis().toInt()
|
val id = System.currentTimeMillis().toInt()
|
||||||
|
|
||||||
val downloadFolders = File(context.cacheDir.absolutePath + "/downloads")
|
val downloadFolders = File(context.cacheDir.absolutePath + "/downloads")
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,12 @@ class TerminalDownloadWorker(
|
||||||
if (cookiesFile.exists()){
|
if (cookiesFile.exists()){
|
||||||
request.addOption("--cookies", cookiesFile.absolutePath)
|
request.addOption("--cookies", cookiesFile.absolutePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
||||||
|
val header = sharedPreferences.getString("useragent_header", "")
|
||||||
|
if (useHeader && !header.isNullOrBlank()){
|
||||||
|
request.addOption("--add-header","User-Agent:${header}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("-P", FileUtil.getDefaultCommandPath() + "/" + itemId)
|
request.addOption("-P", FileUtil.getDefaultCommandPath() + "/" + itemId)
|
||||||
|
|
@ -83,7 +89,7 @@ class TerminalDownloadWorker(
|
||||||
"Terminal Download",
|
"Terminal Download",
|
||||||
"Downloading:\n" +
|
"Downloading:\n" +
|
||||||
"Terminal Download\n" +
|
"Terminal Download\n" +
|
||||||
"Command: ${infoUtil.parseYTDLRequestString(request)}\n\n",
|
"Command: ${command}\n\n",
|
||||||
Format(),
|
Format(),
|
||||||
DownloadViewModel.Type.command,
|
DownloadViewModel.Type.command,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
@ -110,10 +116,9 @@ class TerminalDownloadWorker(
|
||||||
line, progress.toInt(), 0, title,
|
line, progress.toInt(), 0, title,
|
||||||
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID
|
||||||
)
|
)
|
||||||
if (logDownloads){
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
if (logDownloads) logRepo.update(line, logItem.id)
|
||||||
logRepo.update(line, logItem.id)
|
dao.updateLog(line, itemId.toLong())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
|
|
@ -132,39 +137,30 @@ class TerminalDownloadWorker(
|
||||||
}
|
}
|
||||||
|
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
it.out.split("\n").forEach {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
|
if (logDownloads) logRepo.update(it.out, logItem.id)
|
||||||
Thread.sleep(50)
|
dao.updateLog(it.out, itemId.toLong())
|
||||||
}
|
Thread.sleep(1000)
|
||||||
|
dao.delete(itemId.toLong())
|
||||||
if (logDownloads){
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
logRepo.update(it.out, logItem.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
notificationUtil.cancelDownloadNotification(itemId)
|
notificationUtil.cancelDownloadNotification(itemId)
|
||||||
dao.delete(itemId.toLong())
|
|
||||||
return@runBlocking Result.success()
|
Result.success()
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
it.message?.chunked(10000)?.forEach {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
|
if (it.message != null){
|
||||||
Thread.sleep(100)
|
if (logDownloads) logRepo.update(it.message!!, logItem.id)
|
||||||
}
|
dao.updateLog(it.message!!, itemId.toLong())
|
||||||
|
Thread.sleep(1000)
|
||||||
if (logDownloads){
|
dao.delete(itemId.toLong())
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
if (it.message != null){
|
|
||||||
logRepo.update(it.message!!, logItem.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
notificationUtil.cancelDownloadNotification(itemId)
|
||||||
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
|
||||||
Log.e(TAG, context.getString(R.string.failed_download), it)
|
Log.e(TAG, context.getString(R.string.failed_download), it)
|
||||||
notificationUtil.cancelDownloadNotification(itemId)
|
Result.failure()
|
||||||
dao.delete(itemId.toLong())
|
|
||||||
return@runBlocking Result.failure()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,23 @@
|
||||||
package com.deniscerri.ytdlnis.work
|
package com.deniscerri.ytdlnis.work
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.work.CoroutineWorker
|
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.Worker
|
import androidx.work.Worker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.workDataOf
|
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
|
||||||
import com.deniscerri.ytdlnis.database.Converters
|
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil.Companion.FORMAT_UPDATING_NOTIFICATION_ID
|
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
class UpdatePlaylistFormatsWorker(
|
class UpdatePlaylistFormatsWorker(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
workerParams: WorkerParameters
|
workerParams: WorkerParameters
|
||||||
) : CoroutineWorker(context, workerParams) {
|
) : Worker(context, workerParams) {
|
||||||
override suspend fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
val dbManager = DBManager.getInstance(context)
|
val dbManager = DBManager.getInstance(context)
|
||||||
val dao = dbManager.downloadDao
|
val dao = dbManager.downloadDao
|
||||||
val resDao = dbManager.resultDao
|
val resDao = dbManager.resultDao
|
||||||
|
|
@ -55,47 +28,59 @@ class UpdatePlaylistFormatsWorker(
|
||||||
val workID = inputData.getInt("id", 0)
|
val workID = inputData.getInt("id", 0)
|
||||||
if (workID == 0) return Result.failure()
|
if (workID == 0) return Result.failure()
|
||||||
|
|
||||||
val notification = notificationUtil.createFormatsUpdateNotification(workID)
|
val notification = notificationUtil.createFormatsUpdateNotification()
|
||||||
val foregroundInfo = ForegroundInfo(FORMAT_UPDATING_NOTIFICATION_ID, notification)
|
val foregroundInfo = ForegroundInfo(workID, notification)
|
||||||
setForeground(foregroundInfo)
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
ids.forEach {
|
|
||||||
if (!isStopped){
|
|
||||||
val d = dao.getDownloadById(it)
|
|
||||||
val r = resDao.getResultByURL(d.url)
|
|
||||||
|
|
||||||
if (d.allFormats.isNotEmpty()){
|
return try{
|
||||||
|
ids.forEach {
|
||||||
|
if (!isStopped){
|
||||||
|
val d = dao.getDownloadById(it)
|
||||||
|
val r = resDao.getResultByURL(d.url)
|
||||||
|
|
||||||
|
if (d.allFormats.isNotEmpty()){
|
||||||
|
count++
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
d.allFormats.clear()
|
||||||
|
d.allFormats.addAll(infoUtil.getFormats(d.url))
|
||||||
|
d.format = vm.getFormat(d.allFormats,d.type)
|
||||||
|
|
||||||
|
r.formats.clear()
|
||||||
|
r.formats.addAll(d.allFormats)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
resDao.update(r)
|
||||||
|
dao.delete(d.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
count++
|
count++
|
||||||
return@forEach
|
notificationUtil.updateFormatUpdateNotification(workID, UpdatePlaylistFormatsWorker::class.java.name, count, ids.size)
|
||||||
|
}else{
|
||||||
|
throw Exception()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runCatching {
|
Result.success()
|
||||||
d.allFormats.clear()
|
}catch (e: Exception){
|
||||||
d.allFormats.addAll(infoUtil.getFormats(d.url))
|
notificationUtil.cancelDownloadNotification(workID)
|
||||||
d.format = vm.getFormat(d.allFormats,d.type)
|
ids.clear()
|
||||||
|
Result.failure()
|
||||||
r.formats.clear()
|
}finally {
|
||||||
r.formats.addAll(d.allFormats)
|
if (ids.isNotEmpty()){
|
||||||
|
notificationUtil.showFormatsUpdatedNotification(ids)
|
||||||
dao.update(d)
|
|
||||||
resDao.update(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
count++
|
|
||||||
notificationUtil.updateFormatUpdateNotification(FORMAT_UPDATING_NOTIFICATION_ID, count, ids.size)
|
|
||||||
}else{
|
|
||||||
notificationUtil.cancelDownloadNotification(FORMAT_UPDATING_NOTIFICATION_ID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopped() {
|
||||||
|
Log.e("asd", "Asd")
|
||||||
runBlocking {
|
super.onStopped()
|
||||||
notificationUtil.showFormatsUpdatedNotification(ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.success()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,4 +18,7 @@
|
||||||
|
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
|
<include layout="@layout/history_no_results"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -43,14 +43,16 @@
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
|
android:id="@+id/rl"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<WebView
|
<androidx.compose.ui.platform.ComposeView
|
||||||
android:id="@+id/webview"
|
android:id="@+id/webview_compose"
|
||||||
android:layout_width="match_parent"
|
tools:composableName="com.deniscerri.ytdlnis.ui.more.WebViewActivity.WebViewView"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
android:layout_width="match_parent"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@
|
||||||
android:title="@string/delete_all_cookies"
|
android:title="@string/delete_all_cookies"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/use_header"
|
||||||
|
android:title="@string/user_agent_header"
|
||||||
|
android:checkable="true"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/import_clipboard"
|
android:id="@+id/import_clipboard"
|
||||||
android:title="@string/import_from_clipboard"
|
android:title="@string/import_from_clipboard"
|
||||||
|
|
|
||||||
|
|
@ -307,4 +307,7 @@
|
||||||
<string name="preferred_audio_codec">Preferred Audio Codec</string>
|
<string name="preferred_audio_codec">Preferred Audio Codec</string>
|
||||||
<string name="loading">Loading</string>
|
<string name="loading">Loading</string>
|
||||||
<string name="sponsorblock_api_url">Sponsorblock API URL</string>
|
<string name="sponsorblock_api_url">Sponsorblock API URL</string>
|
||||||
|
<string name="remember_download_type">Remember Download Type</string>
|
||||||
|
<string name="remember_download_type_summary">Select the last opened download type for the current item</string>
|
||||||
|
<string name="user_agent_header">User-Agent header</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -53,6 +53,19 @@
|
||||||
app:summary="@string/preferred_download_type_summary"
|
app:summary="@string/preferred_download_type_summary"
|
||||||
app:title="@string/preferred_download_type" />
|
app:title="@string/preferred_download_type" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
android:icon="@drawable/ic_download_type"
|
||||||
|
android:key="remember_download_type"
|
||||||
|
app:summary="@string/remember_download_type_summary"
|
||||||
|
app:title="@string/remember_download_type" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:defaultValue="video"
|
||||||
|
android:key="last_used_download_type"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
<SeekBarPreference
|
<SeekBarPreference
|
||||||
android:defaultValue="3"
|
android:defaultValue="3"
|
||||||
android:icon="@drawable/ic_lines"
|
android:icon="@drawable/ic_lines"
|
||||||
|
|
@ -130,5 +143,15 @@
|
||||||
/>
|
/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:defaultValue=""
|
||||||
|
android:key="use_header"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:defaultValue=""
|
||||||
|
android:key="useragent_header"
|
||||||
|
app:isPreferenceVisible="false" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -1,38 +1,49 @@
|
||||||
download audio/video files from more than 1000 websites
|
<b>YTDLnis</b> lets you download audio/video files from more than 1000 websites, and offers a lot of features. For example:
|
||||||
process playlists
|
|
||||||
edit every playlist item separately just like in a normal download item.
|
<b>process playlists</b>
|
||||||
select a common format for all items and/or select multiple audio formats in case you are downloading them as a video
|
|
||||||
select a download path for all items
|
- edit every playlist item separately just like in a normal download item.
|
||||||
select a filename template for all items
|
- select a common format for all items and/or select multiple audio formats in case you are downloading them as a video
|
||||||
batch update download type to audio/video/custom command in one click
|
- select a download path for all items
|
||||||
queue downloads and schedule them by date and time
|
- select a filename template for all items
|
||||||
you can also schedule multiple items at the same time
|
- batch update download type to audio/video/custom command in one click
|
||||||
download multiple items at the same time
|
- queue downloads and schedule them by date and time
|
||||||
use custom commands and templates or go full yt-dlp mode with a built in terminal
|
- you can also schedule multiple items at the same time
|
||||||
you can backup and restore templates, so you can share with your buddies
|
- download multiple items at the same time
|
||||||
COOKIES support. Log in with your accounts and download private/unavailable videos, unlock premium formats etc.
|
- use custom commands and templates or go full yt-dlp mode with a built in terminal
|
||||||
cut videos based on timestamps and video chapters (This yt-dlp feature is experimental on the original project)
|
- you can backup and restore templates, so you can share with your buddies
|
||||||
you can make unlimited cuts
|
|
||||||
remove sponsorblock elements from the item
|
<b>COOKIES support</b>
|
||||||
embed them as a chapters in your video
|
|
||||||
embed subtitles/metadata/chapters etc
|
- Log in with your accounts and download private/unavailable videos, unlock premium formats etc.
|
||||||
modify metadata such as title and author
|
|
||||||
split item into separate files depending on its chapters
|
<b>Different ways of modifying the file</b>
|
||||||
select different download formats
|
|
||||||
bottom card right from the share menu, no need to open the app
|
- cut videos based on timestamps and video chapters (This yt-dlp feature is experimental on the original project)
|
||||||
you can create a txt file and fill it with links/playlists/search queries separate by a new line and the app will process them
|
- you can make unlimited cuts
|
||||||
search or insert a link from the app
|
- remove sponsorblock elements from the item
|
||||||
you can stack searches so you can process them at the same time
|
- embed them as a chapters in your video
|
||||||
log downloads in case of problems
|
- embed subtitles/metadata/chapters etc
|
||||||
re-download cancelled or failed downloads
|
- modify metadata such as title and author
|
||||||
you can use gestures to swipe left to redownload and right to delete
|
- split item into separate files depending on its chapters
|
||||||
you can long click the redownload button in the details sheet to show the download card for more functionality
|
- select different download formats
|
||||||
incognito mode when you don't want to save a download history or logs
|
|
||||||
quick download mode
|
<b>Other features</b>
|
||||||
download immediately without having to wait for data to process. Turn off the bottom card and it will instantly start
|
|
||||||
open / share downloaded files right from the finished notification
|
- bottom card right from the share menu, no need to open the app
|
||||||
most yt-dlp features are implemented, suggestions are welcome
|
- you can create a txt file and fill it with links/playlists/search queries separate by a new line and the app will process them
|
||||||
Material You interface
|
- search or insert a link from the app
|
||||||
Theming options
|
- you can stack searches so you can process them at the same time
|
||||||
Backup and restore features. (Almost everything can be backed up)
|
- log downloads in case of problems
|
||||||
MVVM Architecture w/ WorkManager
|
- re-download cancelled or failed downloads
|
||||||
|
- you can use gestures to swipe left to redownload and right to delete
|
||||||
|
- you can long click the redownload button in the details sheet to show the download card for more functionality
|
||||||
|
- incognito mode when you don't want to save a download history or logs
|
||||||
|
- quick download mode
|
||||||
|
- download immediately without having to wait for data to process. Turn off the bottom card and it will instantly start
|
||||||
|
- open / share downloaded files right from the finished notification
|
||||||
|
- most yt-dlp features are implemented, suggestions are welcome
|
||||||
|
- Material You interface
|
||||||
|
- Theming options
|
||||||
|
- Backup and restore features. (Almost everything can be backed up)
|
||||||
|
- MVVM Architecture w/ WorkManager
|
||||||
Loading…
Reference in a new issue