Merge branch 'main' into weblate-ytdlnis-strings
This commit is contained in:
commit
1b9beccfdc
83 changed files with 1501 additions and 414 deletions
|
|
@ -11,8 +11,8 @@ def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 7
|
def versionMinor = 7
|
||||||
def versionPatch = 6
|
def versionPatch = 6
|
||||||
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||||
def isBeta = true
|
def isBeta = false
|
||||||
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
725
app/schemas/com.deniscerri.ytdl.database.DBManager/17.json
Normal file
725
app/schemas/com.deniscerri.ytdl.database.DBManager/17.json
Normal file
|
|
@ -0,0 +1,725 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 17,
|
||||||
|
"identityHash": "ae22d3024fcae377c9bd9b8ba6f1f299",
|
||||||
|
"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, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `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": "playlistURL",
|
||||||
|
"columnName": "playlistURL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistIndex",
|
||||||
|
"columnName": "playlistIndex",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"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, `playlistURL` TEXT DEFAULT '', `playlistIndex` 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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistURL",
|
||||||
|
"columnName": "playlistURL",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistIndex",
|
||||||
|
"columnName": "playlistIndex",
|
||||||
|
"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, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1)",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "useAsExtraCommandAudio",
|
||||||
|
"columnName": "useAsExtraCommandAudio",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "useAsExtraCommandVideo",
|
||||||
|
"columnName": "useAsExtraCommandVideo",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "sources",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadItemTemplate",
|
||||||
|
"columnName": "downloadItemTemplate",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyNr",
|
||||||
|
"columnName": "everyNr",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyCategory",
|
||||||
|
"columnName": "everyCategory",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyTime",
|
||||||
|
"columnName": "everyTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "weeklyConfig",
|
||||||
|
"columnName": "weeklyConfig",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "monthlyConfig",
|
||||||
|
"columnName": "monthlyConfig",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "status",
|
||||||
|
"columnName": "status",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startsTime",
|
||||||
|
"columnName": "startsTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "endsDate",
|
||||||
|
"columnName": "endsDate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "endsAfterCount",
|
||||||
|
"columnName": "endsAfterCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "runCount",
|
||||||
|
"columnName": "runCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "getOnlyNewUploads",
|
||||||
|
"columnName": "getOnlyNewUploads",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "retryMissingDownloads",
|
||||||
|
"columnName": "retryMissingDownloads",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ignoredLinks",
|
||||||
|
"columnName": "ignoredLinks",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'[]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alreadyProcessedLinks",
|
||||||
|
"columnName": "alreadyProcessedLinks",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae22d3024fcae377c9bd9b8ba6f1f299')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:taskAffinity=""
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@style/Theme.BottomSheet">
|
android:theme="@style/Theme.BottomSheet">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
|
||||||
TerminalItem::class,
|
TerminalItem::class,
|
||||||
ObserveSourcesItem::class
|
ObserveSourcesItem::class
|
||||||
],
|
],
|
||||||
version = 16,
|
version = 17,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration (from = 1, to = 2),
|
AutoMigration (from = 1, to = 2),
|
||||||
AutoMigration (from = 2, to = 3),
|
AutoMigration (from = 2, to = 3),
|
||||||
|
|
@ -57,7 +57,8 @@ import com.deniscerri.ytdl.database.models.TerminalItem
|
||||||
AutoMigration (from = 12, to = 13),
|
AutoMigration (from = 12, to = 13),
|
||||||
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
||||||
AutoMigration (from = 14, to = 15),
|
AutoMigration (from = 14, to = 15),
|
||||||
AutoMigration (from = 15, to = 16, spec = Migrations.resetObserveSources::class)
|
AutoMigration (from = 15, to = 16, spec = Migrations.resetObserveSources::class),
|
||||||
|
AutoMigration (from = 16, to = 17)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
abstract class DBManager : RoomDatabase(){
|
abstract class DBManager : RoomDatabase(){
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
|
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
|
||||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloads WHERE status='Scheduled' ORDER BY downloadStartTime, id")
|
||||||
|
fun getScheduledDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT id FROM downloads WHERE status='Queued' ORDER BY id")
|
@Query("SELECT id FROM downloads WHERE status='Queued' ORDER BY id")
|
||||||
fun getQueuedDownloadsListIDs() : List<Long>
|
fun getQueuedDownloadsListIDs() : List<Long>
|
||||||
|
|
||||||
|
|
@ -123,6 +126,15 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
|
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
|
||||||
fun getDownloadsByIds(ids: List<Long>) : List<DownloadItem>
|
fun getDownloadsByIds(ids: List<Long>) : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloads WHERE status='Processing' AND id >=:id")
|
||||||
|
fun getProcessingItemsFromID(id: Long) : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloads WHERE status='Processing' AND id >=:id and id<=:id2")
|
||||||
|
fun getProcessingItemsBetweenIDs(id: Long, id2: Long) : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
|
||||||
|
fun getDownloadsByIdsFlow(ids: List<Long>) : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
@Query("SELECT id FROM downloads ORDER BY id DESC LIMIT 1")
|
@Query("SELECT id FROM downloads ORDER BY id DESC LIMIT 1")
|
||||||
fun getLastDownloadId(): Long
|
fun getLastDownloadId(): Long
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ interface ResultDao {
|
||||||
@Query("SELECT * FROM results WHERE url=:url LIMIT 1")
|
@Query("SELECT * FROM results WHERE url=:url LIMIT 1")
|
||||||
fun getResultByURL(url: String) : ResultItem?
|
fun getResultByURL(url: String) : ResultItem?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM results where id=:id LIMIT 1")
|
||||||
|
fun getResultByID(id: Long): ResultItem?
|
||||||
|
|
||||||
@Query("SELECT * from results WHERE id > :item1 AND id < :item2 ORDER BY id")
|
@Query("SELECT * from results WHERE id > :item1 AND id < :item2 ORDER BY id")
|
||||||
fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem>
|
fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ data class DownloadItemSimple(
|
||||||
var id: Long,
|
var id: Long,
|
||||||
var url: String,
|
var url: String,
|
||||||
var title: String,
|
var title: String,
|
||||||
|
var playlistTitle: String,
|
||||||
var author: String,
|
var author: String,
|
||||||
var thumb: String,
|
var thumb: String,
|
||||||
var duration: String,
|
var duration: String,
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,7 @@ data class ObserveSourcesItem(
|
||||||
@ColumnInfo(defaultValue = "0")
|
@ColumnInfo(defaultValue = "0")
|
||||||
var getOnlyNewUploads: Boolean,
|
var getOnlyNewUploads: Boolean,
|
||||||
var retryMissingDownloads: Boolean,
|
var retryMissingDownloads: Boolean,
|
||||||
|
@ColumnInfo(defaultValue = "[]")
|
||||||
|
var ignoredLinks: MutableList<String>,
|
||||||
var alreadyProcessedLinks : MutableList<String>
|
var alreadyProcessedLinks : MutableList<String>
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,22 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
return downloadDao.getDownloadById(id)
|
return downloadDao.getDownloadById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getProcessingItemsFromID(id: Long) : Flow<List<DownloadItem>> {
|
||||||
|
return downloadDao.getProcessingItemsFromID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProcessingItemsBetweenIDs(first: Long, last:Long) : List<DownloadItem> {
|
||||||
|
return downloadDao.getProcessingItemsBetweenIDs(first, last)
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllItemsByIDs(ids : List<Long>) : List<DownloadItem>{
|
fun getAllItemsByIDs(ids : List<Long>) : List<DownloadItem>{
|
||||||
return downloadDao.getDownloadsByIds(ids)
|
return downloadDao.getDownloadsByIds(ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllItemsByIDsFlow(ids: List<Long>) : Flow<List<DownloadItem>> {
|
||||||
|
return downloadDao.getDownloadsByIdsFlow(ids)
|
||||||
|
}
|
||||||
|
|
||||||
fun getActiveDownloads() : List<DownloadItem> {
|
fun getActiveDownloads() : List<DownloadItem> {
|
||||||
return downloadDao.getActiveAndPausedDownloadsList()
|
return downloadDao.getActiveAndPausedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +145,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
return downloadDao.getQueuedDownloadsList()
|
return downloadDao.getQueuedDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getScheduledDownloads() : List<DownloadItem> {
|
||||||
|
return downloadDao.getScheduledDownloadsList()
|
||||||
|
}
|
||||||
|
|
||||||
fun getCancelledDownloads() : List<DownloadItem> {
|
fun getCancelledDownloads() : List<DownloadItem> {
|
||||||
return downloadDao.getCancelledDownloadsList()
|
return downloadDao.getCancelledDownloadsList()
|
||||||
}
|
}
|
||||||
|
|
@ -210,8 +226,9 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
var delay = 0L
|
var delay = 0L
|
||||||
if (queuedItems.isNotEmpty()){
|
if (queuedItems.isNotEmpty()){
|
||||||
delay = if (queuedItems[0].downloadStartTime != 0L){
|
val earliestStart = queuedItems.minBy { it.downloadStartTime }
|
||||||
queuedItems[0].downloadStartTime.minus(currentTime)
|
delay = if (earliestStart.downloadStartTime != 0L){
|
||||||
|
earliestStart.downloadStartTime.minus(currentTime)
|
||||||
} else 0
|
} else 0
|
||||||
if (delay <= 60000L) delay = 0L
|
if (delay <= 60000L) delay = 0L
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
resultDao.update(item)
|
resultDao.update(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getItemByID(id: Long) : ResultItem? {
|
||||||
|
return resultDao.getResultByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
fun getItemByURL(url: String): ResultItem? {
|
fun getItemByURL(url: String): ResultItem? {
|
||||||
return resultDao.getResultByURL(url)
|
return resultDao.getResultByURL(url)
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +182,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
val info = getResultsFromSource(downloadItem.url, resetResults = false, addToResults = false, singleItem = true).first()
|
val info = getResultsFromSource(downloadItem.url, resetResults = false, addToResults = false, singleItem = true).first()
|
||||||
if (downloadItem.title.isEmpty()) downloadItem.title = info.title
|
if (downloadItem.title.isEmpty()) downloadItem.title = info.title
|
||||||
if (downloadItem.author.isEmpty()) downloadItem.author = info.author
|
if (downloadItem.author.isEmpty()) downloadItem.author = info.author
|
||||||
|
if (downloadItem.playlistTitle.isEmpty() && downloadItem.playlistTitle != "ytdlnis-Search") downloadItem.playlistTitle = info.playlistTitle
|
||||||
downloadItem.duration = info.duration
|
downloadItem.duration = info.duration
|
||||||
downloadItem.website = info.website
|
downloadItem.website = info.website
|
||||||
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb
|
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
"# $url\n" +
|
||||||
|
"# Generated by YTDLnis\n" +
|
||||||
cookieList.fold(StringBuilder("")) { acc, cookie ->
|
cookieList.fold(StringBuilder("")) { acc, cookie ->
|
||||||
acc.append(cookie.toNetscapeFormat()).append("\n")
|
acc.append(cookie.toNetscapeFormat()).append("\n")
|
||||||
}.toString()
|
}.toString()
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,12 @@ import android.content.res.Resources
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.media3.exoplayer.offline.Download
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
|
|
@ -43,14 +46,21 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.last
|
import kotlinx.coroutines.flow.last
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
@ -65,7 +75,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
val allDownloads : Flow<PagingData<DownloadItem>>
|
val allDownloads : Flow<PagingData<DownloadItem>>
|
||||||
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
val activeDownloads : Flow<List<DownloadItem>>
|
val activeDownloads : Flow<List<DownloadItem>>
|
||||||
val processingDownloads : Flow<List<DownloadItem>>
|
val allProcessingDownloads : Flow<List<DownloadItem>>
|
||||||
|
var mostRecentProcessingDownloads: Flow<List<DownloadItem>>
|
||||||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
|
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||||
|
|
@ -104,11 +115,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
auto, audio, video, command
|
auto, audio, video, command
|
||||||
}
|
}
|
||||||
|
|
||||||
private val urlsForAudioType = listOf(
|
|
||||||
"music",
|
val processingDownloads = MediatorLiveData<List<DownloadItem>>(listOf())
|
||||||
"audio",
|
var processingItemsFlow : ProcessingItemsJob? = null
|
||||||
"soundcloud"
|
var processingItems = MutableStateFlow(false)
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
dbManager = DBManager.getInstance(application)
|
dbManager = DBManager.getInstance(application)
|
||||||
|
|
@ -133,7 +143,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
allDownloads = repository.allDownloads.flow
|
allDownloads = repository.allDownloads.flow
|
||||||
queuedDownloads = repository.queuedDownloads.flow
|
queuedDownloads = repository.queuedDownloads.flow
|
||||||
activeDownloads = repository.activeDownloads
|
activeDownloads = repository.activeDownloads
|
||||||
processingDownloads = repository.processingDownloads
|
allProcessingDownloads = repository.processingDownloads
|
||||||
|
mostRecentProcessingDownloads = repository.getProcessingItemsFromID(0)
|
||||||
savedDownloads = repository.savedDownloads.flow
|
savedDownloads = repository.savedDownloads.flow
|
||||||
scheduledDownloads = repository.scheduledDownloads.flow
|
scheduledDownloads = repository.scheduledDownloads.flow
|
||||||
cancelledDownloads = repository.cancelledDownloads.flow
|
cancelledDownloads = repository.cancelledDownloads.flow
|
||||||
|
|
@ -285,8 +296,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
|
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
|
||||||
|
|
||||||
val customFileNameTemplate = when(historyItem.type) {
|
val customFileNameTemplate = when(historyItem.type) {
|
||||||
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)30B - %(title)120B")
|
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
|
||||||
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)30B - %(title)120B")
|
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,88 +378,83 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
var originItemIDs: List<Long>,
|
var originItemIDs: List<Long>,
|
||||||
var processingDownloadItemIDs: MutableList<Long> = mutableListOf()
|
var processingDownloadItemIDs: MutableList<Long> = mutableListOf()
|
||||||
)
|
)
|
||||||
|
private fun updateProcessingJobData(item: ProcessingItemsJob?) {
|
||||||
var processingItemsJob: MutableLiveData<ProcessingItemsJob?> = MutableLiveData(null)
|
processingItemsFlow = item
|
||||||
var _filteredProcessingItems = MutableStateFlow<List<DownloadItem>>(listOf())
|
|
||||||
var filteredProcessingItems: StateFlow<List<DownloadItem>> = _filteredProcessingItems
|
|
||||||
private fun updateProcessingJobData(item: ProcessingItemsJob) = viewModelScope.launch {
|
|
||||||
processingItemsJob.postValue(item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){
|
||||||
processingItemsJob.postValue(ProcessingItemsJob(null, DownloadItem::class.java.toString(), itemIDs))
|
updateProcessingJobData(ProcessingItemsJob(null, DownloadItem::class.java.toString(), itemIDs))
|
||||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
processingItems.emit(true)
|
||||||
try {
|
try {
|
||||||
itemIDs.chunked(100).forEach { ids ->
|
itemIDs.forEachIndexed { idx, it ->
|
||||||
val items = repository.getAllItemsByIDs(ids)
|
val item = repository.getItemByID(it)
|
||||||
if (!isActive) {
|
if (!isActive) throw CancellationException()
|
||||||
throw CancellationException()
|
|
||||||
}
|
|
||||||
items.forEach {
|
|
||||||
it.id = 0
|
|
||||||
it.status = DownloadRepository.Status.Processing.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
processingItemsJob.value?.apply {
|
item.id = 0
|
||||||
processingDownloadItemIDs.addAll(repository.insertAll(items))
|
item.status = DownloadRepository.Status.Processing.toString()
|
||||||
|
processingItemsFlow?.apply {
|
||||||
|
val id = repository.insert(item)
|
||||||
|
if (idx == 0) mostRecentProcessingDownloads = repository.getProcessingItemsFromID(id)
|
||||||
|
processingDownloadItemIDs.add(id)
|
||||||
updateProcessingJobData(this)
|
updateProcessingJobData(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
processingItems.emit(false)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
processingItemsJob.value?.apply {
|
processingItemsFlow?.apply {
|
||||||
this.processingDownloadItemIDs.chunked(100).forEach {
|
this.processingDownloadItemIDs.chunked(100).forEach {
|
||||||
repository.deleteAllWithIDs(it)
|
repository.deleteAllWithIDs(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processingItemsJob.postValue(null)
|
updateProcessingJobData(null)
|
||||||
|
processingItems.emit(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processingItemsJob.postValue(ProcessingItemsJob(job, DownloadItem::class.java.toString(), itemIDs))
|
updateProcessingJobData(ProcessingItemsJob(job, DownloadItem::class.java.toString(), itemIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
|
fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
processingItemsJob.postValue(ProcessingItemsJob(null, ResultItem::class.java.toString(), itemIDs))
|
updateProcessingJobData(ProcessingItemsJob(null, ResultItem::class.java.toString(), itemIDs))
|
||||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
processingItems.emit(true)
|
||||||
try {
|
try {
|
||||||
itemIDs.chunked(100).forEach { ids ->
|
itemIDs.forEach { id ->
|
||||||
val items = resultRepository.getAllByIDs(ids)
|
val item = resultRepository.getItemByID(id) ?: return@forEach
|
||||||
val downloadItems = items.map {
|
val preferredType = getDownloadType(url = item.url).toString()
|
||||||
val preferredType = getDownloadType(url = it.url).toString()
|
val downloadItem = createDownloadItemFromResult(result = item, givenType = Type.valueOf(
|
||||||
val tmp = createDownloadItemFromResult(result = it, givenType = Type.valueOf(
|
preferredType
|
||||||
preferredType
|
))
|
||||||
))
|
downloadItem.status = DownloadRepository.Status.Processing.toString()
|
||||||
tmp.status = DownloadRepository.Status.Processing.toString()
|
|
||||||
tmp
|
|
||||||
}
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
throw CancellationException()
|
throw CancellationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadNow) {
|
if (downloadNow) {
|
||||||
downloadItems.forEach {
|
downloadItem.status = DownloadRepository.Status.Queued.toString()
|
||||||
it.status = DownloadRepository.Status.Queued.toString()
|
queueDownloads(listOf(downloadItem))
|
||||||
}
|
|
||||||
queueDownloads(downloadItems)
|
|
||||||
}else{
|
}else{
|
||||||
processingItemsJob.value?.apply {
|
processingItemsFlow?.apply {
|
||||||
processingDownloadItemIDs.addAll(repository.insertAll(downloadItems))
|
processingDownloadItemIDs.add(repository.insert(downloadItem))
|
||||||
updateProcessingJobData(this)
|
updateProcessingJobData(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
processingItems.emit(false)
|
||||||
}catch (e: Exception) {
|
}catch (e: Exception) {
|
||||||
processingItemsJob.value?.apply {
|
processingItemsFlow?.apply {
|
||||||
this.processingDownloadItemIDs.chunked(100).forEach {
|
this.processingDownloadItemIDs.chunked(100).forEach {
|
||||||
repository.deleteAllWithIDs(it)
|
repository.deleteAllWithIDs(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processingItemsJob.postValue(null)
|
updateProcessingJobData(null)
|
||||||
|
processingItems.emit(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processingItemsJob.postValue(ProcessingItemsJob(job, ResultItem::class.java.toString(), itemIDs))
|
updateProcessingJobData(ProcessingItemsJob(job, ResultItem::class.java.toString(), itemIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insert(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
|
fun insert(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
|
||||||
|
|
@ -498,7 +504,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
processingItemsJob.value?.apply { this.job?.cancel(CancellationException()) }
|
processingItemsFlow?.apply { this.job?.cancel(CancellationException()) }
|
||||||
repository.cancelActiveQueued()
|
repository.cancelActiveQueued()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,6 +512,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
return repository.getQueuedDownloads()
|
return repository.getQueuedDownloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getScheduled() : List<DownloadItem> {
|
||||||
|
return repository.getScheduledDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
fun getCancelled() : List<DownloadItem> {
|
fun getCancelled() : List<DownloadItem> {
|
||||||
return repository.getCancelledDownloads()
|
return repository.getCancelledDownloads()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,8 @@ class SharedDownloadViewModel(private val context: Context) {
|
||||||
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
|
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
|
||||||
|
|
||||||
val customFileNameTemplate = when(type) {
|
val customFileNameTemplate = when(type) {
|
||||||
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
|
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
|
||||||
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s")
|
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,7 +288,7 @@ class SharedDownloadViewModel(private val context: Context) {
|
||||||
val prefAudio = sharedPreferences.getString("format_importance_audio", itemValues.joinToString(","))!!
|
val prefAudio = sharedPreferences.getString("format_importance_audio", itemValues.joinToString(","))!!
|
||||||
|
|
||||||
prefAudio.split(",").forEachIndexed { idx, s ->
|
prefAudio.split(",").forEachIndexed { idx, s ->
|
||||||
var importance = (itemValues.size - idx) * 10
|
val importance = (itemValues.size - idx) * 10
|
||||||
when(s) {
|
when(s) {
|
||||||
"id" -> {
|
"id" -> {
|
||||||
requirements.add {it: Format -> if (audioFormatIDPreference.contains(it.format_id)) importance else 0}
|
requirements.add {it: Format -> if (audioFormatIDPreference.contains(it.format_id)) importance else 0}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class ShareActivity : BaseActivity() {
|
||||||
// val params = window.attributes
|
// val params = window.attributes
|
||||||
// params.alpha = 0f
|
// params.alpha = 0f
|
||||||
// window.attributes = params
|
// window.attributes = params
|
||||||
// setContentView(R.layout.activity_share)
|
setContentView(R.layout.activity_share)
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
window.run {
|
window.run {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package com.deniscerri.ytdl.services
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.deniscerri.ytdl.database.DBManager
|
import com.deniscerri.ytdl.database.DBManager
|
||||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdl.database.models.ResultItem
|
import com.deniscerri.ytdl.database.models.ResultItem
|
||||||
|
|
@ -18,8 +20,10 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
||||||
class ProcessDownloadsInBackgroundService : Service() {
|
class ProcessDownloadsInBackgroundService : Service() {
|
||||||
|
|
@ -46,7 +50,25 @@ class ProcessDownloadsInBackgroundService : Service() {
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
|
|
||||||
val binding = intent.getBooleanExtra("binding", false)
|
val binding = intent.getBooleanExtra("binding", false)
|
||||||
if (binding) return super.onStartCommand(intent, flags, startId)
|
if (binding) {
|
||||||
|
val cancel = intent.getBooleanExtra("cancel", false)
|
||||||
|
if (cancel) {
|
||||||
|
cancelAllProcessingJobs()
|
||||||
|
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
|
||||||
|
repository.deleteProcessing()
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
if (Build.VERSION.SDK_INT > 23){
|
||||||
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
|
}else{
|
||||||
|
stopForeground(true)
|
||||||
|
}
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val notificationUtil = NotificationUtil(this)
|
val notificationUtil = NotificationUtil(this)
|
||||||
startForeground(System.currentTimeMillis().toInt(), notificationUtil.createProcessingDownloads())
|
startForeground(System.currentTimeMillis().toInt(), notificationUtil.createProcessingDownloads())
|
||||||
|
|
@ -98,7 +120,7 @@ class ProcessDownloadsInBackgroundService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
repository.getAllItemsByIDs(processingItemIDs).apply {
|
repository.getProcessingItemsBetweenIDs(processingItemIDs.first(), processingItemIDs.last()).apply {
|
||||||
this.chunked(100).map {
|
this.chunked(100).map {
|
||||||
if (timeInMillis > 0){
|
if (timeInMillis > 0){
|
||||||
this.forEach { d ->
|
this.forEach { d ->
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,20 @@ open class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
fun askPermissions() {
|
fun askPermissions() {
|
||||||
val permissions = arrayListOf<String>()
|
val permissions = arrayListOf<String>()
|
||||||
if (!checkFilePermission()) {
|
val hasFilePerm = if (Build.VERSION.SDK_INT >= 30) {
|
||||||
if (Build.VERSION.SDK_INT >= 33){
|
true
|
||||||
permissions.add(Manifest.permission.READ_MEDIA_AUDIO)
|
}else {
|
||||||
permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
|
(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
if (Build.VERSION.SDK_INT >= 34){
|
== PackageManager.PERMISSION_GRANTED) &&
|
||||||
permissions.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
|
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
}
|
== PackageManager.PERMISSION_GRANTED)
|
||||||
}else{
|
|
||||||
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasFilePerm){
|
||||||
|
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
if (!checkNotificationPermission()){
|
if (!checkNotificationPermission()){
|
||||||
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
|
permissions.add(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
|
@ -67,20 +69,6 @@ open class BaseActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkFilePermission(): Boolean {
|
|
||||||
return if(Build.VERSION.SDK_INT >= 33){
|
|
||||||
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
|
|
||||||
== PackageManager.PERMISSION_GRANTED) &&
|
|
||||||
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO)
|
|
||||||
== PackageManager.PERMISSION_GRANTED)
|
|
||||||
}else{
|
|
||||||
(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
== PackageManager.PERMISSION_GRANTED) &&
|
|
||||||
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
== PackageManager.PERMISSION_GRANTED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||||
private fun checkNotificationPermission(): Boolean {
|
private fun checkNotificationPermission(): Boolean {
|
||||||
return (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
return (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
|
|
||||||
|
|
@ -915,10 +915,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
queriesConstraint?.isVisible = queriesChipGroup?.childCount!! > 0
|
queriesConstraint?.isVisible = queriesChipGroup?.childCount!! > 0
|
||||||
|
|
||||||
searchSuggestionsAdapter?.getList()?.apply {
|
searchSuggestionsAdapter?.getList()?.apply {
|
||||||
if (this.first().type == SearchSuggestionType.CLIPBOARD){
|
if (this.isNotEmpty()) {
|
||||||
val newList = this.toMutableList().drop(1)
|
if (this.first().type == SearchSuggestionType.CLIPBOARD){
|
||||||
searchSuggestionsAdapter?.submitList(newList)
|
val newList = this.toMutableList().drop(1)
|
||||||
|
searchSuggestionsAdapter?.submitList(newList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,11 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
var title = item.title
|
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
itemTitle.text = title.ifEmpty { item.url }
|
itemTitle.text = title
|
||||||
|
|
||||||
// Author ----------------------------------
|
// Author ----------------------------------
|
||||||
val author = card.findViewById<TextView>(R.id.author)
|
val author = card.findViewById<TextView>(R.id.author)
|
||||||
|
|
@ -107,7 +107,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
|
|
||||||
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
||||||
if (fileSize != "?") sideDetails.add(fileSize)
|
if (fileSize != "?") sideDetails.add(fileSize)
|
||||||
formatDetailsChip.text = sideDetails.joinToString(" · ")
|
formatDetailsChip.text = sideDetails.filter { it.isNotBlank() }.joinToString(" · ")
|
||||||
|
|
||||||
//OUTPUT
|
//OUTPUT
|
||||||
val output = card.findViewById<TextView>(R.id.output)
|
val output = card.findViewById<TextView>(R.id.output)
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,11 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
var title = item.title
|
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
itemTitle.text = title.ifEmpty { item.url }
|
itemTitle.text = title
|
||||||
|
|
||||||
//DOWNLOAD TYPE -----------------------------
|
//DOWNLOAD TYPE -----------------------------
|
||||||
val type = card.findViewById<TextView>(R.id.download_type)
|
val type = card.findViewById<TextView>(R.id.download_type)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.deniscerri.ytdl.ui.adapter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.deniscerri.ytdl.R
|
||||||
|
import com.deniscerri.ytdl.database.models.CommandTemplate
|
||||||
|
import com.deniscerri.ytdl.database.models.GithubRelease
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.popup
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import io.noties.markwon.AbstractMarkwonPlugin
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.MarkwonConfiguration
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class ChangelogAdapter(activity: Activity) : ListAdapter<GithubRelease?, ChangelogAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||||
|
DIFF_CALLBACK
|
||||||
|
).build()) {
|
||||||
|
private val activity: Activity
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.activity = activity
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
var layoutParams: LinearLayout.LayoutParams
|
||||||
|
init {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
layoutParams.setMargins(10, 10, 10, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val cardView = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.changelog_item, parent, false)
|
||||||
|
return ViewHolder(cardView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val it = getItem(position) ?: return
|
||||||
|
val card = holder.itemView
|
||||||
|
|
||||||
|
val layoutParams = holder.layoutParams
|
||||||
|
|
||||||
|
card.findViewById<TextView>(R.id.version).text = it.tag_name
|
||||||
|
card.findViewById<TextView>(R.id.date).text = SimpleDateFormat(
|
||||||
|
DateFormat.getBestDateTimePattern(
|
||||||
|
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
|
||||||
|
|
||||||
|
val mdText = card.findViewById<TextView>(R.id.content)
|
||||||
|
val mw = Markwon.builder(activity).usePlugin(object: AbstractMarkwonPlugin() {
|
||||||
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
|
builder.linkResolver { view, link ->
|
||||||
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||||
|
ContextCompat.startActivity(activity, browserIntent, Bundle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).build()
|
||||||
|
mw.setMarkdown(mdText, it.body)
|
||||||
|
|
||||||
|
val assetGroup = card.findViewById<ChipGroup>(R.id.assets)
|
||||||
|
assetGroup.removeAllViews()
|
||||||
|
it.assets.forEachIndexed { idx, c ->
|
||||||
|
val tmp = activity.layoutInflater.inflate(R.layout.filter_chip, assetGroup, false) as Chip
|
||||||
|
tmp.isCheckable = false
|
||||||
|
tmp.layoutParams = layoutParams
|
||||||
|
tmp.text = c.name
|
||||||
|
tmp.id = idx
|
||||||
|
tmp.setOnClickListener {
|
||||||
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
|
||||||
|
ContextCompat.startActivity(activity, browserIntent, Bundle())
|
||||||
|
}
|
||||||
|
assetGroup!!.addView(tmp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DIFF_CALLBACK: DiffUtil.ItemCallback<GithubRelease> = object : DiffUtil.ItemCallback<GithubRelease>() {
|
||||||
|
override fun areItemsTheSame(oldItem: GithubRelease, newItem: GithubRelease): Boolean {
|
||||||
|
return oldItem.html_url == newItem.html_url
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: GithubRelease, newItem: GithubRelease): Boolean {
|
||||||
|
return oldItem.tag_name == newItem.tag_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
@ -71,14 +72,15 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
duration.text = item.duration
|
duration.text = item.duration
|
||||||
|
duration.isVisible = item.duration != "-1"
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
var title = item.title
|
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
itemTitle.text = title.ifEmpty { item.url }
|
itemTitle.text = title
|
||||||
|
|
||||||
//DOWNLOAD TYPE -----------------------------
|
//DOWNLOAD TYPE -----------------------------
|
||||||
val type = card.findViewById<TextView>(R.id.download_type)
|
val type = card.findViewById<TextView>(R.id.download_type)
|
||||||
|
|
|
||||||
|
|
@ -87,14 +87,16 @@ class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
duration.text = item.duration
|
duration.text = item.duration
|
||||||
|
duration.isVisible = item.duration != "-1"
|
||||||
|
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
var title = item.title
|
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
itemTitle.text = title.ifEmpty { item.url }
|
itemTitle.text = title
|
||||||
|
|
||||||
//DOWNLOAD TYPE -----------------------------
|
//DOWNLOAD TYPE -----------------------------
|
||||||
val type = card.findViewById<TextView>(R.id.download_type)
|
val type = card.findViewById<TextView>(R.id.download_type)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
@ -83,14 +84,15 @@ class ScheduledDownloadAdapter(onItemClickListener: OnItemClickListener, activit
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
duration.text = item.duration
|
duration.text = item.duration
|
||||||
|
duration.isVisible = item.duration != "-1"
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
var title = item.title
|
var title = item.title.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
itemTitle.text = title.ifEmpty { item.url }
|
itemTitle.text = title
|
||||||
|
|
||||||
//DOWNLOAD TYPE -----------------------------
|
//DOWNLOAD TYPE -----------------------------
|
||||||
val type = card.findViewById<TextView>(R.id.download_type)
|
val type = card.findViewById<TextView>(R.id.download_type)
|
||||||
|
|
|
||||||
|
|
@ -498,7 +498,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
suggestedChapters.visibility = View.VISIBLE
|
suggestedChapters.visibility = View.VISIBLE
|
||||||
suggestedChips.removeAllViews()
|
suggestedChips.removeAllViews()
|
||||||
chapters!!.forEach {
|
chapters!!.forEach {
|
||||||
val chip = layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
|
val chip = layoutInflater.inflate(R.layout.suggestion_chip, suggestedChips, false) as Chip
|
||||||
chip.text = it.title
|
chip.text = it.title
|
||||||
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
|
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
|
||||||
chip.isCheckedIconVisible = false
|
chip.isCheckedIconVisible = false
|
||||||
|
|
@ -514,9 +514,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
|
||||||
//replace existing chip to enable click events
|
//replace existing chip to enable click events
|
||||||
val idx = selectedCuts.indexOfFirst { c -> c.contains(it.title) }
|
val idx = selectedCuts.indexOfFirst { c -> c.contains(it.title) }
|
||||||
if (idx > -1){
|
if (idx > -1){
|
||||||
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text == it.title }
|
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text.contains(it.title) }
|
||||||
chipGroup.removeView(chipForDeletion)
|
chipForDeletion?.apply {
|
||||||
createChapterChip(it, idx)
|
chipGroup.removeView(chipForDeletion)
|
||||||
|
createChapterChip(it, idx)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
private lateinit var currentDownloadIDs: List<Long>
|
private lateinit var currentDownloadIDs: List<Long>
|
||||||
private var processingItemsCount : Int = 0
|
private var processingItemsCount : Int = 0
|
||||||
|
|
||||||
private lateinit var jobData: DownloadViewModel.ProcessingItemsJob
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
|
|
@ -174,58 +172,57 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
shimmerTitle = view.findViewById(R.id.shimmer_loading_title)
|
shimmerTitle = view.findViewById(R.id.shimmer_loading_title)
|
||||||
shimmerSubtitle = view.findViewById(R.id.shimmer_loading_subtitle)
|
shimmerSubtitle = view.findViewById(R.id.shimmer_loading_subtitle)
|
||||||
|
|
||||||
downloadViewModel.processingItemsJob.observe(this) {
|
lifecycleScope.launch {
|
||||||
it?.apply {
|
downloadViewModel.processingItems.collectLatest {
|
||||||
lifecycleScope.launch {
|
val loading = it
|
||||||
jobData = it
|
toggleLoadingShimmerTitle(loading)
|
||||||
processingItemsCount = it.processingDownloadItemIDs.size
|
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
|
||||||
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
|
if (!loading){
|
||||||
|
continueInBackgroundSnackBar.dismiss()
|
||||||
val loading = it.originItemIDs.size != processingItemsCount
|
|
||||||
toggleLoadingShimmerTitle(loading)
|
|
||||||
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
|
|
||||||
if (!loading){
|
|
||||||
continueInBackgroundSnackBar.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadViewModel.processingDownloads.map { p -> p.filter { pd -> jobData.processingDownloadItemIDs.contains(pd.id) } }.collectLatest { items ->
|
|
||||||
listAdapter.submitList(items)
|
|
||||||
updateFileSize(items.map { it2 -> it2.format.filesize })
|
|
||||||
|
|
||||||
if (items.isNotEmpty()){
|
|
||||||
if (items.all { it2 -> it2.type == items[0].type }) {
|
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
|
||||||
if (items[0].type != DownloadViewModel.Type.command) {
|
|
||||||
bottomAppBar.menu[3].icon?.alpha = 255
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bottomAppBar.menu[1].icon?.alpha = 30
|
|
||||||
bottomAppBar.menu[3].icon?.alpha = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
val type = items.first().type
|
|
||||||
|
|
||||||
when(type){
|
|
||||||
DownloadViewModel.Type.audio -> {
|
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
|
||||||
}
|
|
||||||
DownloadViewModel.Type.video -> {
|
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
|
||||||
|
|
||||||
}
|
|
||||||
DownloadViewModel.Type.command -> {
|
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.mostRecentProcessingDownloads.collectLatest { items ->
|
||||||
|
processingItemsCount = items.size
|
||||||
|
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
|
||||||
|
listAdapter.submitList(items)
|
||||||
|
updateFileSize(items.map { it2 -> it2.format.filesize })
|
||||||
|
|
||||||
|
if (items.isNotEmpty()){
|
||||||
|
if (items.all { it2 -> it2.type == items[0].type }) {
|
||||||
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
if (items[0].type != DownloadViewModel.Type.command) {
|
||||||
|
bottomAppBar.menu[3].icon?.alpha = 255
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bottomAppBar.menu[1].icon?.alpha = 30
|
||||||
|
bottomAppBar.menu[3].icon?.alpha = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
val type = items.first().type
|
||||||
|
|
||||||
|
when(type){
|
||||||
|
DownloadViewModel.Type.audio -> {
|
||||||
|
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.video -> {
|
||||||
|
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||||
|
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.command -> {
|
||||||
|
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
scheduleBtn.setOnClickListener{
|
scheduleBtn.setOnClickListener{
|
||||||
fun initSchedule(processingFinished: Boolean) {
|
fun initSchedule(processingFinished: Boolean) {
|
||||||
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
UiUtil.showDatePicker(parentFragmentManager) { cal ->
|
||||||
|
|
@ -237,9 +234,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
jobData.job?.cancel(CancellationException())
|
getProcessingItemsData()?.apply {
|
||||||
jobData.job = null
|
this.job?.cancel(CancellationException())
|
||||||
downloadProcessDownloadsInBackground(jobData, processingFinished)
|
this.job = null
|
||||||
|
downloadProcessDownloadsInBackground(this, processingFinished)
|
||||||
|
}
|
||||||
|
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(cal.timeInMillis)
|
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(cal.timeInMillis)
|
||||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||||
|
|
@ -268,9 +267,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
jobData.job?.cancel(CancellationException())
|
getProcessingItemsData()?.apply {
|
||||||
jobData.job = null
|
this.job?.cancel(CancellationException())
|
||||||
downloadProcessDownloadsInBackground(jobData, processingFinished)
|
this.job = null
|
||||||
|
downloadProcessDownloadsInBackground(this, processingFinished)
|
||||||
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,10 +296,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
lifecycleScope.launch{
|
lifecycleScope.launch{
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
downloadViewModel.deleteAllWithID(currentDownloadIDs)
|
||||||
downloadViewModel.moveProcessingToSavedCategory(jobData.processingDownloadItemIDs)
|
downloadViewModel.moveProcessingToSavedCategory(getProcessingDownloadItemIDs())
|
||||||
|
}
|
||||||
|
getProcessingItemsData()?.apply {
|
||||||
|
this.job?.cancel(CancellationException())
|
||||||
|
this.job = null
|
||||||
}
|
}
|
||||||
jobData.job?.cancel(CancellationException())
|
|
||||||
jobData.job = null
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,13 +312,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
val formatListener = object : OnFormatClickListener {
|
val formatListener = object : OnFormatClickListener {
|
||||||
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.updateProcessingFormat(jobData.processingDownloadItemIDs, selectedFormats)
|
downloadViewModel.updateProcessingFormat(getProcessingDownloadItemIDs(), selectedFormats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.updateProcessingAllFormats(jobData.processingDownloadItemIDs, allFormats)
|
downloadViewModel.updateProcessingAllFormats(getProcessingDownloadItemIDs(), allFormats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,10 +326,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
override fun onContinueOnBackground() {
|
override fun onContinueOnBackground() {
|
||||||
requireActivity().lifecycleScope.launch {
|
requireActivity().lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.continueUpdatingFormatsOnBackground(jobData.processingDownloadItemIDs)
|
downloadViewModel.continueUpdatingFormatsOnBackground(getProcessingDownloadItemIDs())
|
||||||
|
}
|
||||||
|
getProcessingItemsData()?.apply {
|
||||||
|
this.job?.cancel(CancellationException())
|
||||||
|
this.job = null
|
||||||
}
|
}
|
||||||
jobData.job?.cancel(CancellationException())
|
|
||||||
jobData.job = null
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -364,7 +369,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
audio!!.setOnClickListener {
|
audio!!.setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.audio)
|
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.audio)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
|
@ -376,7 +381,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
video!!.setOnClickListener {
|
video!!.setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.IO).launch{
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.video)
|
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.video)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
|
@ -388,7 +393,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
command!!.setOnClickListener {
|
command!!.setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.IO).launch{
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.command)
|
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.command)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||||
bottomAppBar.menu[1].icon?.alpha = 255
|
bottomAppBar.menu[1].icon?.alpha = 255
|
||||||
|
|
@ -412,7 +417,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
R.id.format -> {
|
R.id.format -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val res = withContext(Dispatchers.IO){
|
val res = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs,)
|
downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
|
||||||
}
|
}
|
||||||
if (!res.first){
|
if (!res.first){
|
||||||
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
|
||||||
|
|
@ -431,12 +436,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
)
|
)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.updateProcessingCommandFormat(jobData.processingDownloadItemIDs, format)
|
downloadViewModel.updateProcessingCommandFormat(getProcessingDownloadItemIDs(), format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
val items = withContext(Dispatchers.IO){
|
val items = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
|
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
|
||||||
}
|
}
|
||||||
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
val flatFormatCollection = items.map { it.allFormats }.flatten()
|
||||||
val commonFormats = withContext(Dispatchers.IO){
|
val commonFormats = withContext(Dispatchers.IO){
|
||||||
|
|
@ -460,7 +465,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
R.id.more -> {
|
R.id.more -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val res = withContext(Dispatchers.IO){
|
val res = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs)
|
downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
|
||||||
}
|
}
|
||||||
if (!res.first) {
|
if (!res.first) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
@ -481,7 +486,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||||
|
|
||||||
val items = withContext(Dispatchers.IO){
|
val items = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
|
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
|
||||||
}
|
}
|
||||||
|
|
||||||
UiUtil.configureAudio(
|
UiUtil.configureAudio(
|
||||||
|
|
@ -562,7 +567,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
|
||||||
|
|
||||||
val items = withContext(Dispatchers.IO){
|
val items = withContext(Dispatchers.IO){
|
||||||
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
|
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
|
||||||
}
|
}
|
||||||
|
|
||||||
UiUtil.configureVideo(
|
UiUtil.configureVideo(
|
||||||
|
|
@ -700,7 +705,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
downloadViewModel.updateProcessingDownloadPath(jobData.processingDownloadItemIDs, result.data?.data.toString())
|
downloadViewModel.updateProcessingDownloadPath(getProcessingDownloadItemIDs(), result.data?.data.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
val path = FileUtil.formatPath(result.data!!.data.toString())
|
val path = FileUtil.formatPath(result.data!!.data.toString())
|
||||||
|
|
@ -832,9 +837,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
if (jobData.job != null){
|
getProcessingItemsData()?.apply {
|
||||||
jobData.job?.cancel(CancellationException())
|
if (this.job != null){
|
||||||
downloadViewModel.deleteAllWithID(jobData.processingDownloadItemIDs)
|
this.job?.cancel(CancellationException())
|
||||||
|
downloadViewModel.deleteAllWithID(this.processingDownloadItemIDs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.onDismiss(dialog)
|
super.onDismiss(dialog)
|
||||||
}
|
}
|
||||||
|
|
@ -916,6 +923,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getProcessingDownloadItemIDs() : List<Long> {
|
||||||
|
return downloadViewModel.processingItemsFlow?.processingDownloadItemIDs ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getProcessingItemsData() : DownloadViewModel.ProcessingItemsJob? {
|
||||||
|
return downloadViewModel.processingItemsFlow
|
||||||
|
}
|
||||||
|
|
||||||
private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob, processingFinished: Boolean, timeInMillis: Long = 0){
|
private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob, processingFinished: Boolean, timeInMillis: Long = 0){
|
||||||
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
||||||
intent.putExtra("itemType", jobData.originItemType)
|
intent.putExtra("itemType", jobData.originItemType)
|
||||||
|
|
|
||||||
|
|
@ -523,6 +523,11 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
mutableListOf()
|
mutableListOf()
|
||||||
}else{
|
}else{
|
||||||
currentItem?.alreadyProcessedLinks ?: mutableListOf()
|
currentItem?.alreadyProcessedLinks ?: mutableListOf()
|
||||||
|
},
|
||||||
|
ignoredLinks = if (resetProcessedLinks.isChecked || !getOnlyNewUploads.isChecked){
|
||||||
|
mutableListOf()
|
||||||
|
}else{
|
||||||
|
currentItem?.ignoredLinks ?: mutableListOf()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.app.Dialog
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.method.DigitsKeyListener
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
|
@ -106,7 +107,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
|
||||||
count.text = "0 ${resources.getString(R.string.selected)}"
|
count.text = "0 ${resources.getString(R.string.selected)}"
|
||||||
|
|
||||||
fromTextInput = view.findViewById(R.id.from_textinput)
|
fromTextInput = view.findViewById(R.id.from_textinput)
|
||||||
|
fromTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
|
||||||
toTextInput = view.findViewById(R.id.to_textinput)
|
toTextInput = view.findViewById(R.id.to_textinput)
|
||||||
|
toTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
|
||||||
|
|
||||||
|
|
||||||
fromTextInput.editText!!.doAfterTextChanged { _text ->
|
fromTextInput.editText!!.doAfterTextChanged { _text ->
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
@ -251,24 +251,20 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.redownload -> {
|
R.id.redownload -> {
|
||||||
lifecycleScope.launch {
|
val selectedObjects = runBlocking {
|
||||||
val selectedObjects = getSelectedIDs()
|
getSelectedIDs()
|
||||||
val showDownloadCard = preferences.getBoolean("download_card", true)
|
|
||||||
if (showDownloadCard) {
|
|
||||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
|
||||||
downloadViewModel.turnDownloadItemsToProcessingDownloads(selectedObjects)
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
val bundle = Bundle()
|
|
||||||
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
|
|
||||||
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundle)
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
downloadViewModel.reQueueDownloadItems(selectedObjects)
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter.clearCheckedItems()
|
|
||||||
}
|
}
|
||||||
|
val showDownloadCard = preferences.getBoolean("download_card", true)
|
||||||
|
if (showDownloadCard) {
|
||||||
|
downloadViewModel.turnDownloadItemsToProcessingDownloads(selectedObjects)
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
|
||||||
|
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundle)
|
||||||
|
}else {
|
||||||
|
downloadViewModel.reQueueDownloadItems(selectedObjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.clearCheckedItems()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -288,8 +289,6 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
val binder = service as ProcessDownloadsInBackgroundService.LocalBinder
|
val binder = service as ProcessDownloadsInBackgroundService.LocalBinder
|
||||||
mService = binder.service
|
mService = binder.service
|
||||||
mBound = true
|
mBound = true
|
||||||
|
|
||||||
mService.cancelAllProcessingJobs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||||
|
|
@ -299,6 +298,7 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
|
|
||||||
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
|
||||||
intent.putExtra("binding", true)
|
intent.putExtra("binding", true)
|
||||||
|
intent.putExtra("cancel", true)
|
||||||
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -414,7 +414,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
val w = websites[i]
|
val w = websites[i]
|
||||||
if (w == "null" || w.isEmpty()) continue
|
if (w == "null" || w.isEmpty()) continue
|
||||||
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
|
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
|
||||||
tmp.text = w.replaceFirstChar { if (it.isLowerCase()) it.titlecase(java.util.Locale.getDefault()) else it.toString() }
|
tmp.text = w
|
||||||
tmp.id = i
|
tmp.id = i
|
||||||
tmp.setOnClickListener {
|
tmp.setOnClickListener {
|
||||||
Log.e(TAG, tmp.isChecked.toString())
|
Log.e(TAG, tmp.isChecked.toString())
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
|
||||||
): View? {
|
): View? {
|
||||||
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
|
||||||
activity = getActivity()
|
activity = getActivity()
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
return fragmentView
|
return fragmentView
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import android.view.ViewGroup
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
|
@ -30,9 +32,11 @@ import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.models.CookieItem
|
import com.deniscerri.ytdl.database.models.CookieItem
|
||||||
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
|
||||||
import com.deniscerri.ytdl.ui.adapter.CookieAdapter
|
import com.deniscerri.ytdl.ui.adapter.CookieAdapter
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
|
||||||
import com.deniscerri.ytdl.util.FileUtil
|
import com.deniscerri.ytdl.util.FileUtil
|
||||||
import com.deniscerri.ytdl.util.UiUtil
|
import com.deniscerri.ytdl.util.UiUtil
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.materialswitch.MaterialSwitch
|
import com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
|
@ -114,7 +118,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
private fun initChips() {
|
private fun initChips() {
|
||||||
val new = view?.findViewById<Chip>(R.id.newCookie)
|
val new = view?.findViewById<Chip>(R.id.newCookie)
|
||||||
new?.setOnClickListener {
|
new?.setOnClickListener {
|
||||||
showDialog("")
|
showDialog(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,22 +172,25 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDialog(url: String){
|
private fun showDialog(item: CookieItem?){
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val item = withContext(Dispatchers.IO){
|
|
||||||
cookiesViewModel.getByURL(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
builder.setTitle(getString(R.string.cookies))
|
builder.setTitle(getString(R.string.cookies))
|
||||||
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
|
val layout = layoutInflater.inflate(R.layout.cookie_details, null)
|
||||||
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
|
val editText = layout.findViewById<EditText>(R.id.url_edittext)
|
||||||
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
|
layout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
|
||||||
val text = if (url.isNullOrBlank()) "https://" else url
|
val text = item?.url ?: "https://"
|
||||||
editText.setText(text)
|
editText.setText(text)
|
||||||
editText.setSelection(editText.text.length)
|
editText.setSelection(editText.text.length)
|
||||||
builder.setView(inputLayout)
|
val current = layout.findViewById<MaterialCardView>(R.id.current)
|
||||||
|
current.isVisible = item != null
|
||||||
|
item?.apply {
|
||||||
|
current.findViewById<TextView>(R.id.currentText).apply {
|
||||||
|
setText(item.content)
|
||||||
|
enableTextHighlight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.setView(layout)
|
||||||
|
|
||||||
item?.apply {
|
item?.apply {
|
||||||
builder.setNeutralButton(
|
builder.setNeutralButton(
|
||||||
|
|
@ -194,7 +201,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(
|
builder.setPositiveButton(
|
||||||
getString(R.string.get_cookies)
|
if (item == null) getString(R.string.get_cookies) else getString(R.string.update)
|
||||||
) { dialog: DialogInterface?, which: Int ->
|
) { dialog: DialogInterface?, which: Int ->
|
||||||
val myIntent = Intent(requireContext(), WebViewActivity::class.java)
|
val myIntent = Intent(requireContext(), WebViewActivity::class.java)
|
||||||
myIntent.putExtra("url", editText.text.toString())
|
myIntent.putExtra("url", editText.text.toString())
|
||||||
|
|
@ -220,12 +227,8 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "CookiesActivity"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(cookie: CookieItem) {
|
override fun onItemClick(cookie: CookieItem) {
|
||||||
showDialog(cookie.url)
|
showDialog(cookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSelected(cookie: CookieItem) {
|
override fun onSelected(cookie: CookieItem) {
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
|
||||||
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
val itemId = m.itemId
|
val itemId = m.itemId
|
||||||
if (itemId == R.id.remove_logs) {
|
if (itemId == R.id.remove_logs) {
|
||||||
throw Exception("asffffffffff")
|
|
||||||
try{
|
try{
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
deleteDialog.setTitle(getString(R.string.confirm_delete_history))
|
deleteDialog.setTitle(getString(R.string.confirm_delete_history))
|
||||||
|
|
|
||||||
|
|
@ -142,9 +142,9 @@ class FolderSettingsFragment : BaseSettingsFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
|
||||||
videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader)s - %(title)s")
|
videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
|
||||||
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
|
||||||
audioFilenameTemplate?.summary = preferences.getString("file_name_template_audio", "%(uploader)s - %(title)s")
|
audioFilenameTemplate?.summary = preferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
|
||||||
|
|
||||||
videoFilenameTemplate?.setOnPreferenceClickListener {
|
videoFilenameTemplate?.setOnPreferenceClickListener {
|
||||||
UiUtil.showFilenameTemplateDialog(requireActivity(), videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") {
|
UiUtil.showFilenameTemplateDialog(requireActivity(), videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") {
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
"settings" -> json.add("settings", backupSettings(preferences))
|
"settings" -> json.add("settings", backupSettings(preferences))
|
||||||
"downloads" -> json.add("downloads", backupHistory())
|
"downloads" -> json.add("downloads", backupHistory())
|
||||||
"queued" -> json.add("queued", backupQueuedDownloads() )
|
"queued" -> json.add("queued", backupQueuedDownloads() )
|
||||||
|
"scheduled" -> json.add("scheduled", backupScheduledDownloads() )
|
||||||
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
|
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
|
||||||
"errored" -> json.add("errored", backupErroredDownloads() )
|
"errored" -> json.add("errored", backupErroredDownloads() )
|
||||||
"saved" -> json.add("saved", backupSavedDownloads() )
|
"saved" -> json.add("saved", backupSavedDownloads() )
|
||||||
|
|
@ -279,6 +280,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
return JsonArray()
|
return JsonArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun backupScheduledDownloads() : JsonArray {
|
||||||
|
runCatching {
|
||||||
|
val items = withContext(Dispatchers.IO) {
|
||||||
|
downloadViewModel.getScheduled()
|
||||||
|
}
|
||||||
|
val arr = JsonArray()
|
||||||
|
items.forEach {
|
||||||
|
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
return JsonArray()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun backupCancelledDownloads() : JsonArray {
|
private suspend fun backupCancelledDownloads() : JsonArray {
|
||||||
runCatching {
|
runCatching {
|
||||||
val items = withContext(Dispatchers.IO) {
|
val items = withContext(Dispatchers.IO) {
|
||||||
|
|
@ -493,6 +508,25 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
errorMessage.append("\n QUEUED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
errorMessage.append("\n QUEUED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//scheduled downloads restore
|
||||||
|
kotlin.runCatching {
|
||||||
|
if(json.has("scheduled")){
|
||||||
|
val items = json.getAsJsonArray("scheduled")
|
||||||
|
val scheduled = mutableListOf<DownloadItem>()
|
||||||
|
items.forEach {
|
||||||
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||||
|
item.id = 0L
|
||||||
|
scheduled.add(item)
|
||||||
|
}
|
||||||
|
if (scheduled.isNotEmpty()) {
|
||||||
|
finalMessage.append("${getString(R.string.scheduled)}: ${scheduled.count()}\n")
|
||||||
|
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(scheduled)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
errorMessage.append("\n SCHEDULED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||||
|
}
|
||||||
|
|
||||||
//cancelled downloads restore
|
//cancelled downloads restore
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
if(json.has("cancelled")){
|
if(json.has("cancelled")){
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
|
||||||
|
|
||||||
|
|
||||||
formatImportanceAudio?.apply {
|
formatImportanceAudio?.apply {
|
||||||
|
title = "${getString(R.string.format_importance)} [${getString(R.string.audio)}]"
|
||||||
val items = requireContext().getStringArray(R.array.format_importance_audio)
|
val items = requireContext().getStringArray(R.array.format_importance_audio)
|
||||||
val itemValues = requireContext().getStringArray(R.array.format_importance_audio_values).toSet()
|
val itemValues = requireContext().getStringArray(R.array.format_importance_audio_values).toSet()
|
||||||
val prefVideo = prefs.getString("format_importance_audio", itemValues.joinToString(","))!!
|
val prefVideo = prefs.getString("format_importance_audio", itemValues.joinToString(","))!!
|
||||||
|
|
@ -62,7 +63,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
|
||||||
Pair(it, items[itemValues.indexOf(it)])
|
Pair(it, items[itemValues.indexOf(it)])
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
|
|
||||||
showFormatImportanceDialog(getString(R.string.format_importance_audio), itms) { new ->
|
showFormatImportanceDialog(title.toString(), itms) { new ->
|
||||||
editor.putString("format_importance_audio", new.joinToString(",") { it.first }).apply()
|
editor.putString("format_importance_audio", new.joinToString(",") { it.first }).apply()
|
||||||
formatImportanceAudio.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
|
formatImportanceAudio.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +72,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
formatImportanceVideo?.apply {
|
formatImportanceVideo?.apply {
|
||||||
|
title = "${getString(R.string.format_importance)} [${getString(R.string.video)}]"
|
||||||
val items = requireContext().getStringArray(R.array.format_importance_video)
|
val items = requireContext().getStringArray(R.array.format_importance_video)
|
||||||
val itemValues = requireContext().getStringArray(R.array.format_importance_video_values).toSet()
|
val itemValues = requireContext().getStringArray(R.array.format_importance_video_values).toSet()
|
||||||
val prefVideo = prefs.getString("format_importance_video", itemValues.joinToString(","))!!
|
val prefVideo = prefs.getString("format_importance_video", itemValues.joinToString(","))!!
|
||||||
|
|
@ -82,7 +84,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
|
||||||
Pair(it, items[itemValues.indexOf(it)])
|
Pair(it, items[itemValues.indexOf(it)])
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
|
|
||||||
showFormatImportanceDialog(getString(R.string.format_importance_video), itms) {new ->
|
showFormatImportanceDialog(title.toString(), itms) {new ->
|
||||||
editor.putString("format_importance_video", new.joinToString(",") { it.first }).apply()
|
editor.putString("format_importance_video", new.joinToString(",") { it.first }).apply()
|
||||||
formatImportanceVideo.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
|
formatImportanceVideo.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class CrashListener(private val context: Context) : Thread.UncaughtExceptionHand
|
||||||
|
|
||||||
override fun uncaughtException(p0: Thread, p1: Throwable) {
|
override fun uncaughtException(p0: Thread, p1: Throwable) {
|
||||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||||
createLog("${p1.message}\n${p1.stackTrace}")
|
createLog("${p1.message}\n\n${p1.stackTrace.joinToString("\n")}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,15 @@ object FileUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadArchivePath(context: Context) : String {
|
fun getDownloadArchivePath(context: Context) : String {
|
||||||
val folder = PreferenceManager.getDefaultSharedPreferences(context).getString("download_archive_path", context.filesDir.absolutePath + "/")!!
|
var folder = PreferenceManager.getDefaultSharedPreferences(context).getString("download_archive_path", "")!!
|
||||||
|
if (folder == "") {
|
||||||
|
val externalPath = context.getExternalFilesDir(null)
|
||||||
|
folder = if (externalPath == null){
|
||||||
|
context.cacheDir.absolutePath + File.separator
|
||||||
|
}else{
|
||||||
|
externalPath.absolutePath + File.separator
|
||||||
|
}
|
||||||
|
}
|
||||||
return "${formatPath(folder)}download_archive.txt"
|
return "${formatPath(folder)}download_archive.txt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -648,7 +648,7 @@ class InfoUtil(private val context: Context) {
|
||||||
for (result in results) {
|
for (result in results) {
|
||||||
if (result.isNullOrBlank()) continue
|
if (result.isNullOrBlank()) continue
|
||||||
val jsonObject = JSONObject(result)
|
val jsonObject = JSONObject(result)
|
||||||
val title = jsonObject.getStringByAny("track", "alt_title", "title", "webpage_url_basename")
|
var title = jsonObject.getStringByAny("alt_title", "title", "webpage_url_basename")
|
||||||
if (title == "[Private video]" || title == "[Deleted video]") continue
|
if (title == "[Private video]" || title == "[Deleted video]") continue
|
||||||
|
|
||||||
var author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
var author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
||||||
|
|
@ -678,7 +678,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor")
|
var website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor")
|
||||||
|
if (website == "Generic" || website == "HTML5MediaEmbed") website = jsonObject.getStringByAny("webpage_url_domain")
|
||||||
var playlistTitle = jsonObject.getStringByAny("playlist_title")
|
var playlistTitle = jsonObject.getStringByAny("playlist_title")
|
||||||
var playlistURL: String? = ""
|
var playlistURL: String? = ""
|
||||||
var playlistIndex: Int? = null
|
var playlistIndex: Int? = null
|
||||||
|
|
@ -724,6 +725,13 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val type = jsonObject.getStringByAny("_type")
|
||||||
|
if (type == "playlist" && playlistTitle.isEmpty()) {
|
||||||
|
playlistTitle = title
|
||||||
|
title = ""
|
||||||
|
author = ""
|
||||||
|
}
|
||||||
|
|
||||||
val res = ResultItem(0,
|
val res = ResultItem(0,
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
|
|
@ -977,8 +985,8 @@ class InfoUtil(private val context: Context) {
|
||||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||||
|
|
||||||
val retries = sharedPreferences.getString("--retries", "")!!
|
val retries = sharedPreferences.getString("retries", "")!!
|
||||||
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!!
|
val fragmentRetries = sharedPreferences.getString("fragment_retries", "")!!
|
||||||
|
|
||||||
if(retries.isNotEmpty()) request.addOption("--retries", retries)
|
if(retries.isNotEmpty()) request.addOption("--retries", retries)
|
||||||
if(fragmentRetries.isNotEmpty()) request.addOption("--fragment-retries", fragmentRetries)
|
if(fragmentRetries.isNotEmpty()) request.addOption("--fragment-retries", fragmentRetries)
|
||||||
|
|
@ -992,8 +1000,6 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--no-resize-buffer")
|
request.addOption("--no-resize-buffer")
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("--socket-timeout", 60)
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -1017,15 +1023,15 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--keep-fragments")
|
request.addOption("--keep-fragments")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("no_part", false)){
|
|
||||||
request.addOption("--no-part")
|
|
||||||
}
|
|
||||||
|
|
||||||
val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true)
|
val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true)
|
||||||
val thumbnailFormat = sharedPreferences.getString("thumbnail_format", "jpg")
|
val thumbnailFormat = sharedPreferences.getString("thumbnail_format", "jpg")
|
||||||
var filenameTemplate = downloadItem.customFileNameTemplate
|
var filenameTemplate = downloadItem.customFileNameTemplate
|
||||||
|
|
||||||
if(downloadItem.type != DownloadViewModel.Type.command){
|
if(downloadItem.type != DownloadViewModel.Type.command){
|
||||||
|
if (sharedPreferences.getBoolean("no_part", false)){
|
||||||
|
request.addOption("--no-part")
|
||||||
|
}
|
||||||
|
|
||||||
request.addOption("--trim-filenames", 254 - downDir.absolutePath.length)
|
request.addOption("--trim-filenames", 254 - downDir.absolutePath.length)
|
||||||
|
|
||||||
if (downloadItem.SaveThumb) {
|
if (downloadItem.SaveThumb) {
|
||||||
|
|
@ -1068,7 +1074,7 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
|
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$")
|
request.addOption("--parse-metadata", "uploader:^(?P<uploader>.*?)(?:(?= - Topic)|$)")
|
||||||
|
|
||||||
if (embedMetadata){
|
if (embedMetadata){
|
||||||
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
|
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
|
||||||
|
|
@ -1084,7 +1090,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filenameTemplate = if (filenameTemplate.isBlank()){
|
filenameTemplate = if (filenameTemplate.isBlank()){
|
||||||
"%(section_title&{} |)s%(title)s"
|
"%(section_title&{} |)s%(title).170B"
|
||||||
}else{
|
}else{
|
||||||
"%(section_title&{} |)s$filenameTemplate"
|
"%(section_title&{} |)s$filenameTemplate"
|
||||||
}
|
}
|
||||||
|
|
@ -1119,6 +1125,9 @@ class InfoUtil(private val context: Context) {
|
||||||
audioQualityId = "ba/b"
|
audioQualityId = "ba/b"
|
||||||
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
|
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
|
||||||
audioQualityId = "wa/w"
|
audioQualityId = "wa/w"
|
||||||
|
}else if(audioQualityId.contains("kbps_ytdlnisgeneric")){
|
||||||
|
request.addOption("--match-filter", "abr<=${audioQualityId.split("kbps")[0]}")
|
||||||
|
audioQualityId = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1150,7 +1159,7 @@ class InfoUtil(private val context: Context) {
|
||||||
if(ext.isNotBlank()){
|
if(ext.isNotBlank()){
|
||||||
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){
|
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){
|
||||||
request.addOption("--audio-format", ext)
|
request.addOption("--audio-format", ext)
|
||||||
formatSorting.append(",ext::$ext")
|
formatSorting.append(",aext:$ext")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1164,14 +1173,14 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
if (embedMetadata){
|
if (embedMetadata){
|
||||||
request.addOption("--embed-metadata")
|
request.addOption("--embed-metadata")
|
||||||
request.addOption("--parse-metadata", "artist:(?P<meta_album_artist>[^,]+)")
|
request.addOption("--parse-metadata", "%(artist,uploader)s:^(?P<meta_album_artist>[^,]*)")
|
||||||
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist,uploader|)s:%(album_artist)s")
|
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
|
||||||
|
|
||||||
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
|
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
|
||||||
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
|
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
|
||||||
|
|
||||||
if (downloadItem.playlistTitle.isNotEmpty()) {
|
if (downloadItem.playlistTitle.isNotEmpty()) {
|
||||||
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
|
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||||
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(track_number)s")
|
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(track_number)s")
|
||||||
} else {
|
} else {
|
||||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||||
|
|
@ -1239,6 +1248,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var acont = sharedPreferences.getString("audio_format", "")!!
|
||||||
|
if (acont == "Default") acont = ""
|
||||||
|
|
||||||
//format logic
|
//format logic
|
||||||
var videoF = downloadItem.format.format_id
|
var videoF = downloadItem.format.format_id
|
||||||
|
|
@ -1259,6 +1270,11 @@ class InfoUtil(private val context: Context) {
|
||||||
val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!!
|
val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!!
|
||||||
if (downloadItem.videoPreferences.removeAudio) audioF = ""
|
if (downloadItem.videoPreferences.removeAudio) audioF = ""
|
||||||
|
|
||||||
|
if(audioF.contains("kbps_ytdlnisgeneric")){
|
||||||
|
request.addOption("--match-filter", "abr<=${audioF.split("kbps")[0]}")
|
||||||
|
audioF = ""
|
||||||
|
}
|
||||||
|
|
||||||
val f = StringBuilder()
|
val f = StringBuilder()
|
||||||
|
|
||||||
val preferredCodec = sharedPreferences.getString("video_codec", "")
|
val preferredCodec = sharedPreferences.getString("video_codec", "")
|
||||||
|
|
@ -1371,7 +1387,8 @@ class InfoUtil(private val context: Context) {
|
||||||
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
|
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
|
||||||
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
|
||||||
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
|
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
|
||||||
if (cont.isNotBlank()) append(",ext:$cont:")
|
if (cont.isNotBlank()) append(",vext:$cont")
|
||||||
|
if (acont.isNotBlank()) append(",aext:$acont")
|
||||||
if (this.isNotBlank()){
|
if (this.isNotBlank()){
|
||||||
request.addOption("-S", "+hasaud$this")
|
request.addOption("-S", "+hasaud$this")
|
||||||
}
|
}
|
||||||
|
|
@ -1381,14 +1398,7 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("-f", f.toString().replace("/$".toRegex(), ""))
|
request.addOption("-f", f.toString().replace("/$".toRegex(), ""))
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.writeSubs){
|
if (downloadItem.videoPreferences.writeSubs){
|
||||||
val subFormat = sharedPreferences.getString("sub_format", "srt")
|
|
||||||
|
|
||||||
request.addOption("--write-subs")
|
request.addOption("--write-subs")
|
||||||
if(subFormat!!.isNotBlank()){
|
|
||||||
request.addOption("--sub-format", "${subFormat}/best")
|
|
||||||
request.addOption("--convert-subtitles", subFormat ?: "srt")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.embedSubs) {
|
if (downloadItem.videoPreferences.embedSubs) {
|
||||||
|
|
@ -1400,6 +1410,11 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs || downloadItem.videoPreferences.writeAutoSubs){
|
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs || downloadItem.videoPreferences.writeAutoSubs){
|
||||||
|
val subFormat = sharedPreferences.getString("sub_format", "srt")
|
||||||
|
if(subFormat!!.isNotBlank()){
|
||||||
|
request.addOption("--sub-format", "${subFormat}/best")
|
||||||
|
request.addOption("--convert-subtitles", subFormat)
|
||||||
|
}
|
||||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdl.receiver.CancelWorkReceiver
|
import com.deniscerri.ytdl.receiver.CancelWorkReceiver
|
||||||
import com.deniscerri.ytdl.receiver.PauseDownloadNotificationReceiver
|
import com.deniscerri.ytdl.receiver.PauseDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdl.receiver.ResumeActivity
|
import com.deniscerri.ytdl.receiver.ResumeActivity
|
||||||
|
import com.deniscerri.ytdl.services.ProcessDownloadsInBackgroundService
|
||||||
import com.deniscerri.ytdl.util.Extensions.toBitmap
|
import com.deniscerri.ytdl.util.Extensions.toBitmap
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
@ -247,6 +248,8 @@ class NotificationUtil(var context: Context) {
|
||||||
else -> R.drawable.ic_launcher_foreground_large
|
else -> R.drawable.ic_launcher_foreground_large
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val contentText = StringBuilder("$title")
|
||||||
|
|
||||||
val bitmap = iconType.toBitmap(context)
|
val bitmap = iconType.toBitmap(context)
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle("${res.getString(R.string.downloaded)} $title")
|
.setContentTitle("${res.getString(R.string.downloaded)} $title")
|
||||||
|
|
@ -255,15 +258,11 @@ class NotificationUtil(var context: Context) {
|
||||||
.setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString())
|
.setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString())
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||||
.setContentText(title)
|
.setContentText(title)
|
||||||
.setStyle(NotificationCompat.BigTextStyle()
|
|
||||||
.bigText("""
|
|
||||||
$title
|
|
||||||
${filepath?.joinToString("\n")}
|
|
||||||
""".trimIndent()))
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.clearActions()
|
.clearActions()
|
||||||
if (filepath != null){
|
if (filepath != null){
|
||||||
|
contentText.append("\n\n"+ filepath.joinToString("\n"))
|
||||||
try{
|
try{
|
||||||
val uris = filepath.mapNotNull {
|
val uris = filepath.mapNotNull {
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
@ -317,6 +316,7 @@ class NotificationUtil(var context: Context) {
|
||||||
notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent)
|
notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent)
|
||||||
}catch (_: Exception){}
|
}catch (_: Exception){}
|
||||||
}
|
}
|
||||||
|
notificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(contentText.toString().trimIndent()))
|
||||||
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build())
|
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -646,6 +646,16 @@ class NotificationUtil(var context: Context) {
|
||||||
fun createProcessingDownloads() : Notification{
|
fun createProcessingDownloads() : Notification{
|
||||||
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
|
||||||
|
|
||||||
|
val intent = Intent(context, ProcessDownloadsInBackgroundService::class.java)
|
||||||
|
intent.putExtra("binding", true)
|
||||||
|
intent.putExtra("cancel", true)
|
||||||
|
val cancelNotificationPendingIntent = PendingIntent.getService(
|
||||||
|
context,
|
||||||
|
System.currentTimeMillis().toInt(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
notificationBuilder
|
notificationBuilder
|
||||||
.setContentTitle(resources.getString(R.string.processing))
|
.setContentTitle(resources.getString(R.string.processing))
|
||||||
.setSmallIcon(R.drawable.ic_app_icon)
|
.setSmallIcon(R.drawable.ic_app_icon)
|
||||||
|
|
@ -658,6 +668,7 @@ class NotificationUtil(var context: Context) {
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.clearActions()
|
.clearActions()
|
||||||
|
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||||
|
|
||||||
return notificationBuilder.build()
|
return notificationBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ object UiUtil {
|
||||||
else if (formatNote == "worst") formatNote = context.getString(R.string.worst_quality)
|
else if (formatNote == "worst") formatNote = context.getString(R.string.worst_quality)
|
||||||
|
|
||||||
var container = chosenFormat.container
|
var container = chosenFormat.container
|
||||||
if (container == "Default") container = context.getString(R.string.defaultValue)
|
if (container == "Default" || container.isBlank()) container = context.getString(R.string.defaultValue)
|
||||||
|
|
||||||
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
|
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
|
||||||
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
||||||
|
|
@ -580,14 +580,14 @@ object UiUtil {
|
||||||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||||
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
||||||
text = item.title.ifEmpty { "`${context.getString(R.string.defaultValue)}`" }
|
text = item.title.ifEmpty { item.playlistTitle.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } }
|
||||||
setOnLongClickListener{
|
setOnLongClickListener{
|
||||||
showFullTextDialog(context, text.toString(), context.getString(R.string.title))
|
showFullTextDialog(context, text.toString(), context.getString(R.string.title))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
|
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
|
||||||
text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" }
|
text = item.author
|
||||||
setOnLongClickListener{
|
setOnLongClickListener{
|
||||||
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
|
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
|
||||||
true
|
true
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,14 @@ import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||||
import com.deniscerri.ytdl.BuildConfig
|
import com.deniscerri.ytdl.BuildConfig
|
||||||
import com.deniscerri.ytdl.R
|
import com.deniscerri.ytdl.R
|
||||||
import com.deniscerri.ytdl.database.models.GithubRelease
|
import com.deniscerri.ytdl.database.models.GithubRelease
|
||||||
|
import com.deniscerri.ytdl.ui.adapter.ChangelogAdapter
|
||||||
|
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
|
@ -166,71 +171,25 @@ class UpdateUtil(var context: Context) {
|
||||||
|
|
||||||
fun showChangeLog(activity: Activity){
|
fun showChangeLog(activity: Activity){
|
||||||
runCatching {
|
runCatching {
|
||||||
val scrollView = ScrollView(activity)
|
|
||||||
|
|
||||||
val linearLayout = LinearLayout(activity)
|
|
||||||
linearLayout.orientation = LinearLayout.VERTICAL
|
|
||||||
|
|
||||||
val layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
|
|
||||||
layoutParams.setMargins(10, 10, 10, 0)
|
|
||||||
scrollView.layoutParams = layoutParams
|
|
||||||
scrollView.addView(linearLayout)
|
|
||||||
val releases = getGithubReleases()
|
val releases = getGithubReleases()
|
||||||
|
|
||||||
|
val view = activity.layoutInflater.inflate(R.layout.generic_list, null)
|
||||||
|
val adapter = ChangelogAdapter(activity)
|
||||||
|
val recycler = view.findViewById<RecyclerView>(R.id.download_recyclerview)
|
||||||
|
recycler.layoutManager = LinearLayoutManager(activity)
|
||||||
|
recycler.adapter = adapter
|
||||||
|
adapter.submitList(releases)
|
||||||
|
recycler.enableFastScroll()
|
||||||
|
|
||||||
val changeLogDialog = MaterialAlertDialogBuilder(context)
|
val changeLogDialog = MaterialAlertDialogBuilder(context)
|
||||||
.setTitle(activity.getString(R.string.changelog))
|
.setTitle(activity.getString(R.string.changelog))
|
||||||
.setView(scrollView)
|
.setView(view)
|
||||||
.setIcon(R.drawable.ic_chapters)
|
.setIcon(R.drawable.ic_chapters)
|
||||||
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
|
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
changeLogDialog.show()
|
changeLogDialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
releases.forEach {
|
|
||||||
(activity.layoutInflater.inflate(R.layout.changelog_item, null) as MaterialCardView).apply {
|
|
||||||
this.layoutParams = layoutParams
|
|
||||||
findViewById<TextView>(R.id.version).text = it.tag_name
|
|
||||||
findViewById<TextView>(R.id.date).text = SimpleDateFormat(
|
|
||||||
DateFormat.getBestDateTimePattern(
|
|
||||||
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
|
|
||||||
|
|
||||||
val mdText = findViewById<TextView>(R.id.content)
|
|
||||||
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
|
|
||||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
|
||||||
builder.linkResolver { view, link ->
|
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
|
||||||
startActivity(context, browserIntent, Bundle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).build()
|
|
||||||
mw.setMarkdown(mdText, it.body)
|
|
||||||
|
|
||||||
|
|
||||||
val assetGroup = findViewById<ChipGroup>(R.id.assets)
|
|
||||||
it.assets.forEachIndexed { idx, c ->
|
|
||||||
val tmp = activity.layoutInflater.inflate(R.layout.filter_chip, assetGroup, false) as Chip
|
|
||||||
tmp.isCheckable = false
|
|
||||||
tmp.layoutParams = layoutParams
|
|
||||||
tmp.text = c.name
|
|
||||||
tmp.id = idx
|
|
||||||
tmp.setOnClickListener {
|
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
|
|
||||||
startActivity(context, browserIntent, Bundle())
|
|
||||||
}
|
|
||||||
assetGroup!!.addView(tmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main){
|
|
||||||
linearLayout.addView(this@apply)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
if (it.message != null){
|
if (it.message != null){
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,7 @@ class DownloadWorker(
|
||||||
.createPendingIntent()
|
.createPendingIntent()
|
||||||
|
|
||||||
val workNotif = notificationUtil.createDefaultWorkerNotification()
|
val workNotif = notificationUtil.createDefaultWorkerNotification()
|
||||||
val workNotifID = Random.nextInt(900000000, 1000000000)
|
val foregroundInfo = ForegroundInfo(1000000000, workNotif)
|
||||||
val foregroundInfo = ForegroundInfo(workNotifID, workNotif)
|
|
||||||
setForegroundAsync(foregroundInfo)
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
queuedItems.collect { items ->
|
queuedItems.collect { items ->
|
||||||
|
|
@ -265,7 +264,7 @@ class DownloadWorker(
|
||||||
|
|
||||||
val historyItem = HistoryItem(0,
|
val historyItem = HistoryItem(0,
|
||||||
downloadItem.url,
|
downloadItem.url,
|
||||||
downloadItem.title,
|
downloadItem.title.ifEmpty { downloadItem.playlistTitle },
|
||||||
downloadItem.author,
|
downloadItem.author,
|
||||||
downloadItem.duration,
|
downloadItem.duration,
|
||||||
downloadItem.thumb,
|
downloadItem.thumb,
|
||||||
|
|
@ -287,18 +286,18 @@ class DownloadWorker(
|
||||||
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
||||||
)
|
)
|
||||||
|
|
||||||
if (wasQuickDownloaded && createResultItem){
|
// if (wasQuickDownloaded && createResultItem){
|
||||||
runCatching {
|
// runCatching {
|
||||||
eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
|
// eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
|
||||||
runBlocking {
|
// runBlocking {
|
||||||
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
|
// infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
|
||||||
if (res != null) {
|
// if (res != null) {
|
||||||
resultDao.insert(res)
|
// resultDao.insert(res)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
dao.delete(downloadItem.id)
|
dao.delete(downloadItem.id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class ObserveSourceWorker(
|
||||||
.filter { result ->
|
.filter { result ->
|
||||||
//if first run and get new items only is preferred then dont get anything on first run
|
//if first run and get new items only is preferred then dont get anything on first run
|
||||||
if (item.getOnlyNewUploads && item.runCount == 0){
|
if (item.getOnlyNewUploads && item.runCount == 0){
|
||||||
item.alreadyProcessedLinks.add(result.url)
|
item.ignoredLinks.add(result.url)
|
||||||
false
|
false
|
||||||
}else{
|
}else{
|
||||||
true
|
true
|
||||||
|
|
@ -66,6 +66,9 @@ class ObserveSourceWorker(
|
||||||
}
|
}
|
||||||
.filter { result ->
|
.filter { result ->
|
||||||
val history = historyRepo.getAllByURL(result.url)
|
val history = historyRepo.getAllByURL(result.url)
|
||||||
|
|
||||||
|
!item.ignoredLinks.contains(result.url) &&
|
||||||
|
|
||||||
if (item.retryMissingDownloads){
|
if (item.retryMissingDownloads){
|
||||||
//all items that are not present in history
|
//all items that are not present in history
|
||||||
history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }
|
history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,14 @@
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:id="@+id/providers"
|
android:id="@+id/providers"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginTop="5dp"
|
||||||
app:singleSelection="true"
|
app:singleSelection="true"
|
||||||
app:chipSpacingVertical="-10dp"
|
app:chipSpacingVertical="-10dp"
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:singleLine="true" />
|
app:singleLine="true" />
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,14 @@
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:id="@+id/providers"
|
android:id="@+id/providers"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginTop="5dp"
|
||||||
app:singleSelection="true"
|
app:singleSelection="true"
|
||||||
app:chipSpacingVertical="-10dp"
|
app:chipSpacingVertical="-10dp"
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:singleLine="true" />
|
app:singleLine="true" />
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,14 @@
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:id="@+id/providers"
|
android:id="@+id/providers"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginTop="5dp"
|
||||||
app:singleSelection="true"
|
app:singleSelection="true"
|
||||||
app:chipSpacingVertical="-10dp"
|
app:chipSpacingVertical="-10dp"
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:singleLine="true" />
|
app:singleLine="true" />
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,14 @@
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:id="@+id/providers"
|
android:id="@+id/providers"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginTop="5dp"
|
||||||
app:singleSelection="true"
|
app:singleSelection="true"
|
||||||
app:chipSpacingVertical="-10dp"
|
app:chipSpacingVertical="-10dp"
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:singleLine="true" />
|
app:singleLine="true" />
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:cardCornerRadius="20dp"
|
app:cardCornerRadius="20dp"
|
||||||
app:cardElevation="10dp"
|
app:cardElevation="5dp"
|
||||||
app:cardMaxElevation="12dp"
|
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
app:strokeWidth="0dp"
|
app:strokeWidth="0dp"
|
||||||
app:cardPreventCornerOverlap="true"
|
app:cardPreventCornerOverlap="true"
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="15dp"
|
android:layout_marginEnd="15dp"
|
||||||
android:maxLines="2"
|
android:maxLines="3"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
||||||
67
app/src/main/res/layout/cookie_details.xml
Normal file
67
app/src/main/res/layout/cookie_details.xml
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/current"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintHeight_max="300dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/currentText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/url_textinput"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.FilledBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/current">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/url_edittext"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -191,7 +191,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:paddingHorizontal="10dp"
|
android:paddingHorizontal="10dp"
|
||||||
android:text=":"
|
android:text="-"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/download_recyclerview"
|
android:id="@+id/download_recyclerview"
|
||||||
|
|
@ -16,14 +16,16 @@
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
tools:listitem="@layout/active_download_card"
|
||||||
app:layout_constraintHorizontal_bias="0.0"
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
android:paddingBottom="100dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:layout_marginBottom="100dp"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
|
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
android:id="@+id/pause"
|
android:id="@+id/pause"
|
||||||
|
|
@ -50,6 +52,7 @@
|
||||||
|
|
||||||
|
|
||||||
<include layout="@layout/no_results"
|
<include layout="@layout/no_results"
|
||||||
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="match_parent"/>
|
android:layout_width="match_parent"/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@
|
||||||
<AutoCompleteTextView
|
<AutoCompleteTextView
|
||||||
android:id="@+id/container_textview"
|
android:id="@+id/container_textview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:textDirection="locale"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="none"
|
android:inputType="none"
|
||||||
app:simpleItems="@array/audio_containers" />
|
app:simpleItems="@array/audio_containers" />
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,14 @@
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
<com.google.android.material.chip.ChipGroup
|
||||||
android:id="@+id/providers"
|
android:id="@+id/providers"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginTop="5dp"
|
||||||
app:singleSelection="true"
|
app:singleSelection="true"
|
||||||
app:chipSpacingVertical="-10dp"
|
app:chipSpacingVertical="-10dp"
|
||||||
android:paddingEnd="20dp"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:singleLine="true" />
|
app:singleLine="true" />
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
|
|
||||||
<com.google.android.material.chip.Chip
|
<com.google.android.material.chip.Chip
|
||||||
android:id="@+id/info"
|
android:id="@+id/info"
|
||||||
style="@style/Widget.Material3.Chip.Assist"
|
style="@style/Widget.Material3.Chip.Assist.Elevated"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="10dp"
|
||||||
|
|
|
||||||
|
|
@ -392,8 +392,7 @@
|
||||||
<string name="minute">دقيقة</string>
|
<string name="minute">دقيقة</string>
|
||||||
<string name="second">ثانية</string>
|
<string name="second">ثانية</string>
|
||||||
<string name="milliseconds">ملّي ثانية</string>
|
<string name="milliseconds">ملّي ثانية</string>
|
||||||
<string name="format_importance_audio">ترتيب الأولوية في التنسيق (الصوت)</string>
|
<string name="format_importance">ترتيب الأولوية في التنسيق</string>
|
||||||
<string name="format_importance_video">ترتيب الأولوية في التنسيق (الفيديو)</string>
|
|
||||||
<string name="no_audio">فيديو بدون صوت</string>
|
<string name="no_audio">فيديو بدون صوت</string>
|
||||||
<string name="download_archive_folder">مجلد أرشيف التنزيل</string>
|
<string name="download_archive_folder">مجلد أرشيف التنزيل</string>
|
||||||
<string name="location">المسار</string>
|
<string name="location">المسار</string>
|
||||||
|
|
|
||||||
|
|
@ -390,8 +390,7 @@
|
||||||
<string name="clear_scheduled">Planlaşdırılanı Təmizlə</string>
|
<string name="clear_scheduled">Planlaşdırılanı Təmizlə</string>
|
||||||
<string name="minute">Dəqiqə</string>
|
<string name="minute">Dəqiqə</string>
|
||||||
<string name="second">Saniyə</string>
|
<string name="second">Saniyə</string>
|
||||||
<string name="format_importance_audio">Format Vacibliyi Sırası (Audio)</string>
|
<string name="format_importance">Format Vacibliyi Sırası</string>
|
||||||
<string name="format_importance_video">Format Vacibliyi Sırası (Video)</string>
|
|
||||||
<string name="format_importance_note">Format elementinin vacibliyini tənzimləyin. Bu düzülüş yalnız tətbiq yükləmə kartındakı formatları əldə etdikdə və formatı avtomatik seçdikdə istifadə olunacaq!</string>
|
<string name="format_importance_note">Format elementinin vacibliyini tənzimləyin. Bu düzülüş yalnız tətbiq yükləmə kartındakı formatları əldə etdikdə və formatı avtomatik seçdikdə istifadə olunacaq!</string>
|
||||||
<string name="no_audio">Səs olmayan video</string>
|
<string name="no_audio">Səs olmayan video</string>
|
||||||
<string name="download_archive_folder">Yükləmə Arxivi Qovluğu</string>
|
<string name="download_archive_folder">Yükləmə Arxivi Qovluğu</string>
|
||||||
|
|
|
||||||
|
|
@ -394,8 +394,7 @@
|
||||||
<string name="no_audio">ڤیدیۆی بێ دەنگ</string>
|
<string name="no_audio">ڤیدیۆی بێ دەنگ</string>
|
||||||
<string name="download_archive_folder">فۆڵدەری ئەرشیف دابەزێنە</string>
|
<string name="download_archive_folder">فۆڵدەری ئەرشیف دابەزێنە</string>
|
||||||
<string name="process_downloads_background">"هێشتا دابەزاندنەکان لە بارکردندان. بەردەوام بە لە پرۆسێسکردنیان لە پاشبنەمادا و دەست بکە بە دابەزاندن؟ "</string>
|
<string name="process_downloads_background">"هێشتا دابەزاندنەکان لە بارکردندان. بەردەوام بە لە پرۆسێسکردنیان لە پاشبنەمادا و دەست بکە بە دابەزاندن؟ "</string>
|
||||||
<string name="format_importance_audio">ڕێکخستنی گرنگی فۆرمات (دەنگ)</string>
|
<string name="format_importance">ڕێکخستنی گرنگی فۆرمات</string>
|
||||||
<string name="format_importance_video">ڕێکخستنی گرنگی فۆرمات (ڤیدیۆ)</string>
|
|
||||||
<string name="format_importance_note">گرنگی توخمەکانی فۆرمات ڕێکبخە. ئەم فەرمانە تەنها کاتێک بەکاردەهێنرێت کە ئەپەکە فۆرماتەکانی ناو کارتی دابەزاندنەکە دەهێنێت و فۆرماتەکە بە شێوەیەکی ئۆتۆماتیکی هەڵدەبژێرێت!</string>
|
<string name="format_importance_note">گرنگی توخمەکانی فۆرمات ڕێکبخە. ئەم فەرمانە تەنها کاتێک بەکاردەهێنرێت کە ئەپەکە فۆرماتەکانی ناو کارتی دابەزاندنەکە دەهێنێت و فۆرماتەکە بە شێوەیەکی ئۆتۆماتیکی هەڵدەبژێرێت!</string>
|
||||||
<string name="location">شوێن</string>
|
<string name="location">شوێن</string>
|
||||||
<string name="enable_alarm_permission">پێویستە مۆڵەتی SCHEDULE_EXACT_ALARM لە ڕێکخستنەکانی ئەپەکەدا چالاک بکەیت.</string>
|
<string name="enable_alarm_permission">پێویستە مۆڵەتی SCHEDULE_EXACT_ALARM لە ڕێکخستنەکانی ئەپەکەدا چالاک بکەیت.</string>
|
||||||
|
|
|
||||||
|
|
@ -391,10 +391,9 @@
|
||||||
<string name="enable_alarm_permission">V nastavení aplikace je třeba povolit oprávnění SCHEDULE_EXACT_ALARM.</string>
|
<string name="enable_alarm_permission">V nastavení aplikace je třeba povolit oprávnění SCHEDULE_EXACT_ALARM.</string>
|
||||||
<string name="process_downloads_background">"Stahované položky se stále načítají. Pokračovat v jejich zpracování na pozadí a zahájit stahování? "</string>
|
<string name="process_downloads_background">"Stahované položky se stále načítají. Pokračovat v jejich zpracování na pozadí a zahájit stahování? "</string>
|
||||||
<string name="minute">Minuta</string>
|
<string name="minute">Minuta</string>
|
||||||
<string name="format_importance_video">Pořadí důležitosti formátu (Video)</string>
|
<string name="format_importance">Pořadí důležitosti formátu</string>
|
||||||
<string name="second">Sekunda</string>
|
<string name="second">Sekunda</string>
|
||||||
<string name="milliseconds">Milisekundy</string>
|
<string name="milliseconds">Milisekundy</string>
|
||||||
<string name="format_importance_audio">Pořadí důležitosti formátu (Audio)</string>
|
|
||||||
<string name="location">Umístění</string>
|
<string name="location">Umístění</string>
|
||||||
<string name="format_importance_note">Upravte důležitost prvku formátu. Toto pořadí se použije pouze tehdy, když aplikace načte formáty na kartě stahování a automaticky vybere formát!</string>
|
<string name="format_importance_note">Upravte důležitost prvku formátu. Toto pořadí se použije pouze tehdy, když aplikace načte formáty na kartě stahování a automaticky vybere formát!</string>
|
||||||
<string name="no_audio">Video bez zvuku</string>
|
<string name="no_audio">Video bez zvuku</string>
|
||||||
|
|
|
||||||
|
|
@ -390,8 +390,7 @@
|
||||||
<string name="clear_scheduled">Borrar programado</string>
|
<string name="clear_scheduled">Borrar programado</string>
|
||||||
<string name="second">Segundo</string>
|
<string name="second">Segundo</string>
|
||||||
<string name="milliseconds">Milisegundos</string>
|
<string name="milliseconds">Milisegundos</string>
|
||||||
<string name="format_importance_audio">Orden de importancia del formato (audio)</string>
|
<string name="format_importance">Orden de importancia del formato</string>
|
||||||
<string name="format_importance_video">Orden de importancia del formato (vídeo)</string>
|
|
||||||
<string name="format_importance_note">Ajustar la importancia de los elementos de formato. ¡Este orden solo se utilizará cuando la aplicación busque los formatos en la tarjeta de descarga y los seleccione automáticamente!</string>
|
<string name="format_importance_note">Ajustar la importancia de los elementos de formato. ¡Este orden solo se utilizará cuando la aplicación busque los formatos en la tarjeta de descarga y los seleccione automáticamente!</string>
|
||||||
<string name="no_audio">Vídeo sin sonido</string>
|
<string name="no_audio">Vídeo sin sonido</string>
|
||||||
<string name="download_archive_folder">Descargar carpeta de archivos</string>
|
<string name="download_archive_folder">Descargar carpeta de archivos</string>
|
||||||
|
|
|
||||||
|
|
@ -391,8 +391,7 @@
|
||||||
<string name="second">सेकेंड</string>
|
<string name="second">सेकेंड</string>
|
||||||
<string name="minute">मिनट</string>
|
<string name="minute">मिनट</string>
|
||||||
<string name="milliseconds">मिलीसेकेंड</string>
|
<string name="milliseconds">मिलीसेकेंड</string>
|
||||||
<string name="format_importance_audio">प्रारूप महत्व क्रम (ऑडियो)</string>
|
<string name="format_importance">प्रारूप महत्व क्रम</string>
|
||||||
<string name="format_importance_video">प्रारूप महत्व क्रम (वीडियो)</string>
|
|
||||||
<string name="format_importance_note">प्रारूप तत्व महत्व को समायोजित करें। इस आदेश का उपयोग केवल तभी किया जाएगा जब ऐप डाउनलोड कार्ड में प्रारूप लाएगा और प्रारूप का स्वतः चयन करेगा!</string>
|
<string name="format_importance_note">प्रारूप तत्व महत्व को समायोजित करें। इस आदेश का उपयोग केवल तभी किया जाएगा जब ऐप डाउनलोड कार्ड में प्रारूप लाएगा और प्रारूप का स्वतः चयन करेगा!</string>
|
||||||
<string name="no_audio">बिना ऑडियो वाला वीडियो</string>
|
<string name="no_audio">बिना ऑडियो वाला वीडियो</string>
|
||||||
<string name="download_archive_folder">संग्रह फ़ोल्डर डाउनलोड करें</string>
|
<string name="download_archive_folder">संग्रह फ़ोल्डर डाउनलोड करें</string>
|
||||||
|
|
|
||||||
|
|
@ -377,8 +377,7 @@
|
||||||
<string name="display_over_apps">Megjelenés alkalmazások felett</string>
|
<string name="display_over_apps">Megjelenés alkalmazások felett</string>
|
||||||
<string name="display_over_apps_summary">A letöltés kártya mindig legfelül lesz. Kapcsold be, ha a jelenlegi alkalmazást bezárja, hogy a kártya megjelenjen</string>
|
<string name="display_over_apps_summary">A letöltés kártya mindig legfelül lesz. Kapcsold be, ha a jelenlegi alkalmazást bezárja, hogy a kártya megjelenjen</string>
|
||||||
<string name="hour">Óra</string>
|
<string name="hour">Óra</string>
|
||||||
<string name="format_importance_audio">Formátum fontossági sorrend (Hang)</string>
|
<string name="format_importance">Formátum fontossági sorrend</string>
|
||||||
<string name="format_importance_video">Formátum fontossági sorrend (Videó)</string>
|
|
||||||
<string name="no_audio">Videó, hang nélkül</string>
|
<string name="no_audio">Videó, hang nélkül</string>
|
||||||
<string name="download_archive_folder">Letöltési archívum mappa</string>
|
<string name="download_archive_folder">Letöltési archívum mappa</string>
|
||||||
<string name="enable_alarm_permission">Engedélyezned kell a SCHEDULE_EXACT_ALARM engedélyt a beállításokban.</string>
|
<string name="enable_alarm_permission">Engedélyezned kell a SCHEDULE_EXACT_ALARM engedélyt a beállításokban.</string>
|
||||||
|
|
|
||||||
|
|
@ -390,13 +390,12 @@
|
||||||
<string name="move_top">上に移動</string>
|
<string name="move_top">上に移動</string>
|
||||||
<string name="milliseconds">ミリ秒</string>
|
<string name="milliseconds">ミリ秒</string>
|
||||||
<string name="download_archive_folder">ダウンロード記録のフォルダ</string>
|
<string name="download_archive_folder">ダウンロード記録のフォルダ</string>
|
||||||
<string name="format_importance_video">形式の重要度の順序 (動画)</string>
|
<string name="format_importance">形式の重要度の順序</string>
|
||||||
<string name="process_downloads_background">"ダウンロードはまだ読み込み中です。処理をバックグラウンドで続け、ダウンロードを開始しますか? "</string>
|
<string name="process_downloads_background">ダウンロードはまだ読み込み中です。処理をバックグラウンドで続け、ダウンロードを開始しますか?</string>
|
||||||
<string name="minute">分</string>
|
<string name="minute">分</string>
|
||||||
<string name="second">秒</string>
|
<string name="second">秒</string>
|
||||||
<string name="no_audio">音声なし動画</string>
|
<string name="no_audio">音声なし動画</string>
|
||||||
<string name="location">場所</string>
|
<string name="location">場所</string>
|
||||||
<string name="format_importance_audio">形式の重要度の順序 (音声)</string>
|
|
||||||
<string name="enable_alarm_permission">アプリの設定から SCHEDULE_EXACT_ALARM の権限を有効にする必要があります。</string>
|
<string name="enable_alarm_permission">アプリの設定から SCHEDULE_EXACT_ALARM の権限を有効にする必要があります。</string>
|
||||||
<string name="format_importance_note">形式の要素の重要度を調整します。この順序はアプリがダウンロードカード内で形式を取得し、形式を自動選択する際にみ使われます!</string>
|
<string name="format_importance_note">形式の要素の重要度を調整します。この順序はアプリがダウンロードカード内で形式を取得し、形式を自動選択する際にみ使われます!</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -331,8 +331,7 @@
|
||||||
<string name="update_ytdl_stable">Stabiele versie van yt-dlp</string>
|
<string name="update_ytdl_stable">Stabiele versie van yt-dlp</string>
|
||||||
<string name="minute">Minuut</string>
|
<string name="minute">Minuut</string>
|
||||||
<string name="second">Seconde</string>
|
<string name="second">Seconde</string>
|
||||||
<string name="format_importance_audio">Prioriteit van audioformaten</string>
|
<string name="format_importance">Prioriteit formaten</string>
|
||||||
<string name="format_importance_video">Prioriteit van videoformaten</string>
|
|
||||||
<string name="no_audio">Video zonder audio</string>
|
<string name="no_audio">Video zonder audio</string>
|
||||||
<string name="location">Locatie</string>
|
<string name="location">Locatie</string>
|
||||||
<string name="get_new_uploads">Alleen nieuwe uploads ophalen</string>
|
<string name="get_new_uploads">Alleen nieuwe uploads ophalen</string>
|
||||||
|
|
|
||||||
|
|
@ -391,8 +391,7 @@
|
||||||
<string name="minute">ਮਿੰਟ</string>
|
<string name="minute">ਮਿੰਟ</string>
|
||||||
<string name="second">ਸਕਿੰਟ</string>
|
<string name="second">ਸਕਿੰਟ</string>
|
||||||
<string name="milliseconds">ਮਿਲੀਸਕਿੰਟ</string>
|
<string name="milliseconds">ਮਿਲੀਸਕਿੰਟ</string>
|
||||||
<string name="format_importance_audio">ਫਾਰਮੈਟ ਮਹੱਤਵ ਕ੍ਰਮ (ਆਡੀਓ)</string>
|
<string name="format_importance">ਫਾਰਮੈਟ ਮਹੱਤਵ ਕ੍ਰਮ</string>
|
||||||
<string name="format_importance_video">ਫਾਰਮੈਟ ਮਹੱਤਵ ਕ੍ਰਮ (ਵੀਡੀਓ)</string>
|
|
||||||
<string name="no_audio">ਬਿਨਾਂ ਆਡੀਓ ਦੇ ਵੀਡੀਓ</string>
|
<string name="no_audio">ਬਿਨਾਂ ਆਡੀਓ ਦੇ ਵੀਡੀਓ</string>
|
||||||
<string name="download_archive_folder">ਡਾਊਨਲੋਡ ਲਈ ਆਰਕਾਈਵ ਫੋਲਡਰ</string>
|
<string name="download_archive_folder">ਡਾਊਨਲੋਡ ਲਈ ਆਰਕਾਈਵ ਫੋਲਡਰ</string>
|
||||||
<string name="format_importance_note">ਫਾਰਮੈਟ ਤੱਤਾਂ ਦੀ ਮਹੱਤਤਾ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਕ੍ਰਮ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਵਰਤਿਆ ਜਾਵੇਗਾ ਜਦੋਂ ਐਪ ਡਾਉਨਲੋਡ ਕਾਰਡ ਵਿੱਚ ਫਾਰਮੈਟਾਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਅਤੇ ਫਾਰਮੈਟ ਨੂੰ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੁਣਦਾ ਹੈ!</string>
|
<string name="format_importance_note">ਫਾਰਮੈਟ ਤੱਤਾਂ ਦੀ ਮਹੱਤਤਾ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਕ੍ਰਮ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਵਰਤਿਆ ਜਾਵੇਗਾ ਜਦੋਂ ਐਪ ਡਾਉਨਲੋਡ ਕਾਰਡ ਵਿੱਚ ਫਾਰਮੈਟਾਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਅਤੇ ਫਾਰਮੈਟ ਨੂੰ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੁਣਦਾ ਹੈ!</string>
|
||||||
|
|
|
||||||
|
|
@ -388,14 +388,13 @@
|
||||||
<string name="clear_scheduled">Limpar downloads agendados</string>
|
<string name="clear_scheduled">Limpar downloads agendados</string>
|
||||||
<string name="copy_url">Copiar URL</string>
|
<string name="copy_url">Copiar URL</string>
|
||||||
<string name="move_top">Mover para cima</string>
|
<string name="move_top">Mover para cima</string>
|
||||||
<string name="format_importance_video">Ordem de importância do formato (vídeo)</string>
|
<string name="format_importance">Ordem de importância do formato</string>
|
||||||
<string name="minute">Minuto(s)</string>
|
<string name="minute">Minuto(s)</string>
|
||||||
<string name="second">Segundo(s)</string>
|
<string name="second">Segundo(s)</string>
|
||||||
<string name="milliseconds">Milissegundo(s)</string>
|
<string name="milliseconds">Milissegundo(s)</string>
|
||||||
<string name="location">Localização</string>
|
<string name="location">Localização</string>
|
||||||
<string name="download_archive_folder">Pasta para arquivamento de downloads</string>
|
<string name="download_archive_folder">Pasta para arquivamento de downloads</string>
|
||||||
<string name="no_audio">Vídeo sem áudio</string>
|
<string name="no_audio">Vídeo sem áudio</string>
|
||||||
<string name="format_importance_audio">Ordem de importância do formato (áudio)</string>
|
|
||||||
<string name="enable_alarm_permission">Você precisa habilitar a permissão SCHEDULE_EXACT_ALARM nas configurações do aplicativo.</string>
|
<string name="enable_alarm_permission">Você precisa habilitar a permissão SCHEDULE_EXACT_ALARM nas configurações do aplicativo.</string>
|
||||||
<string name="format_importance_note">Ajuste a importância do elemento de formato. Essa ordem só será usada quando o aplicativo buscar os formatos no cartão de download e selecionar automaticamente o formato!</string>
|
<string name="format_importance_note">Ajuste a importância do elemento de formato. Essa ordem só será usada quando o aplicativo buscar os formatos no cartão de download e selecionar automaticamente o formato!</string>
|
||||||
<string name="process_downloads_background">"Os downloads ainda estão sendo carregados. Continuar processando-os em segundo plano e iniciar o download? "</string>
|
<string name="process_downloads_background">"Os downloads ainda estão sendo carregados. Continuar processando-os em segundo plano e iniciar o download? "</string>
|
||||||
|
|
|
||||||
|
|
@ -388,11 +388,10 @@
|
||||||
<string name="move_bottom">Mover para baixo</string>
|
<string name="move_bottom">Mover para baixo</string>
|
||||||
<string name="scheduled">Agendado</string>
|
<string name="scheduled">Agendado</string>
|
||||||
<string name="clear_scheduled">Limpar agendamento</string>
|
<string name="clear_scheduled">Limpar agendamento</string>
|
||||||
<string name="format_importance_video">Ordem preferencial do formato (vídeo)</string>
|
<string name="format_importance">Ordem preferencial do formato</string>
|
||||||
<string name="minute">Minuto</string>
|
<string name="minute">Minuto</string>
|
||||||
<string name="second">Segundo</string>
|
<string name="second">Segundo</string>
|
||||||
<string name="milliseconds">Milissegundos</string>
|
<string name="milliseconds">Milissegundos</string>
|
||||||
<string name="format_importance_audio">Ordem preferencial do formato (áudio)</string>
|
|
||||||
<string name="no_audio">Vídeo sem áudio</string>
|
<string name="no_audio">Vídeo sem áudio</string>
|
||||||
<string name="location">Localização</string>
|
<string name="location">Localização</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -388,13 +388,12 @@
|
||||||
<string name="move_bottom">Двигайтесь вниз</string>
|
<string name="move_bottom">Двигайтесь вниз</string>
|
||||||
<string name="scheduled">Запланированное</string>
|
<string name="scheduled">Запланированное</string>
|
||||||
<string name="clear_scheduled">Очистить Запланированное</string>
|
<string name="clear_scheduled">Очистить Запланированное</string>
|
||||||
<string name="format_importance_video">Порядок важности формата (видео)</string>
|
|
||||||
<string name="download_archive_folder">Скачать папку архива</string>
|
<string name="download_archive_folder">Скачать папку архива</string>
|
||||||
<string name="location">Расположение</string>
|
<string name="location">Расположение</string>
|
||||||
<string name="minute">Минута</string>
|
<string name="minute">Минута</string>
|
||||||
<string name="second">Секунда</string>
|
<string name="second">Секунда</string>
|
||||||
<string name="milliseconds">Миллисекунды</string>
|
<string name="milliseconds">Миллисекунды</string>
|
||||||
<string name="format_importance_audio">Порядок важности формата (аудио)</string>
|
<string name="format_importance">Порядок важности формата</string>
|
||||||
<string name="format_importance_note">Отрегулируйте важность элемента формата. Этот порядок будет использоваться только в том случае, если приложение извлекает форматы из карты загрузки и автоматически выбирает формат!</string>
|
<string name="format_importance_note">Отрегулируйте важность элемента формата. Этот порядок будет использоваться только в том случае, если приложение извлекает форматы из карты загрузки и автоматически выбирает формат!</string>
|
||||||
<string name="no_audio">Видео без звука</string>
|
<string name="no_audio">Видео без звука</string>
|
||||||
<string name="enable_alarm_permission">Вам необходимо включить разрешение SCHEDULE_EXACT_ALARM в настройках приложения.</string>
|
<string name="enable_alarm_permission">Вам необходимо включить разрешение SCHEDULE_EXACT_ALARM в настройках приложения.</string>
|
||||||
|
|
|
||||||
|
|
@ -394,8 +394,7 @@
|
||||||
<string name="process_downloads_background">"Položky na stiahnutie sa stále načítavajú. Pokračovať v ich spracovaní na pozadí a začať sťahovanie? "</string>
|
<string name="process_downloads_background">"Položky na stiahnutie sa stále načítavajú. Pokračovať v ich spracovaní na pozadí a začať sťahovanie? "</string>
|
||||||
<string name="minute">Minúta</string>
|
<string name="minute">Minúta</string>
|
||||||
<string name="second">Sekunda</string>
|
<string name="second">Sekunda</string>
|
||||||
<string name="format_importance_audio">Poradie dôležitosti formátu (Audio)</string>
|
<string name="format_importance">Poradie dôležitosti formátu</string>
|
||||||
<string name="format_importance_video">Poradie dôležitosti formátu (Video)</string>
|
|
||||||
<string name="no_audio">Video bez zvuku</string>
|
<string name="no_audio">Video bez zvuku</string>
|
||||||
<string name="location">Umiestnenie</string>
|
<string name="location">Umiestnenie</string>
|
||||||
<string name="enable_alarm_permission">V nastaveniach aplikácie musíte povoliť oprávnenie SCHEDULE_EXACT_ALARM.</string>
|
<string name="enable_alarm_permission">V nastaveniach aplikácie musíte povoliť oprávnenie SCHEDULE_EXACT_ALARM.</string>
|
||||||
|
|
|
||||||
|
|
@ -390,8 +390,7 @@
|
||||||
<string name="minute">Minuta</string>
|
<string name="minute">Minuta</string>
|
||||||
<string name="second">Sekonda</string>
|
<string name="second">Sekonda</string>
|
||||||
<string name="milliseconds">Milisekonda</string>
|
<string name="milliseconds">Milisekonda</string>
|
||||||
<string name="format_importance_audio">Rradha e rëndësisë së Formateve (Audio)</string>
|
<string name="format_importance">Rradha e rëndësisë së Formateve</string>
|
||||||
<string name="format_importance_video">Rradha e rëndësisë së Formateve (Video)</string>
|
|
||||||
<string name="format_importance_note">Rregullo rëndësinë e elementit të formatit. Kjo porosi do të përdoret vetëm kur aplikacioni merr formatet në kartën e shkarkimit dhe zgjedh automatikisht formatin!</string>
|
<string name="format_importance_note">Rregullo rëndësinë e elementit të formatit. Kjo porosi do të përdoret vetëm kur aplikacioni merr formatet në kartën e shkarkimit dhe zgjedh automatikisht formatin!</string>
|
||||||
<string name="no_audio">Video pa audio</string>
|
<string name="no_audio">Video pa audio</string>
|
||||||
<string name="download_archive_folder">Skedari i arkivës së shkarkimeve</string>
|
<string name="download_archive_folder">Skedari i arkivës së shkarkimeve</string>
|
||||||
|
|
|
||||||
|
|
@ -391,8 +391,7 @@
|
||||||
<string name="second">Секунд</string>
|
<string name="second">Секунд</string>
|
||||||
<string name="milliseconds">Милисекунде</string>
|
<string name="milliseconds">Милисекунде</string>
|
||||||
<string name="minute">Минут</string>
|
<string name="minute">Минут</string>
|
||||||
<string name="format_importance_audio">Редослед важности формата (аудио)</string>
|
<string name="format_importance">Редослед важности формата</string>
|
||||||
<string name="format_importance_video">Редослед важности формата (видео)</string>
|
|
||||||
<string name="format_importance_note">Подесите важност елемента формата. Овај налог ће се користити само када апликација преузме формате на картици за преузимање и аутоматски изабере формат!</string>
|
<string name="format_importance_note">Подесите важност елемента формата. Овај налог ће се користити само када апликација преузме формате на картици за преузимање и аутоматски изабере формат!</string>
|
||||||
<string name="no_audio">Видео без звука</string>
|
<string name="no_audio">Видео без звука</string>
|
||||||
<string name="process_downloads_background">"Преузимања се и даље учитавају. Наставити с обрађивањем преузимања у позадини и започети преузимање? "</string>
|
<string name="process_downloads_background">"Преузимања се и даље учитавају. Наставити с обрађивањем преузимања у позадини и започети преузимање? "</string>
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,6 @@
|
||||||
<string name="move_bottom">கீழே செல்லுங்கள்</string>
|
<string name="move_bottom">கீழே செல்லுங்கள்</string>
|
||||||
<string name="scheduled">திட்டமிடப்பட்ட</string>
|
<string name="scheduled">திட்டமிடப்பட்ட</string>
|
||||||
<string name="clear_scheduled">தெளிவான திட்டமிடப்பட்டுள்ளது</string>
|
<string name="clear_scheduled">தெளிவான திட்டமிடப்பட்டுள்ளது</string>
|
||||||
<string name="format_importance_video">வடிவமைப்பு முக்கியத்துவம் வரிசை (வீடியோ)</string>
|
|
||||||
<string name="you_are_in_latest_version">அண்மைக் கால பதிப்பைப் பயன்படுத்துதல்</string>
|
<string name="you_are_in_latest_version">அண்மைக் கால பதிப்பைப் பயன்படுத்துதல்</string>
|
||||||
<string name="ytdl_already_updating">புதிய பதிப்பை நிறுவுதல்</string>
|
<string name="ytdl_already_updating">புதிய பதிப்பை நிறுவுதல்</string>
|
||||||
<string name="ytdl_updating_started">புதிய பதிப்பைப் பதிவிறக்குகிறது</string>
|
<string name="ytdl_updating_started">புதிய பதிப்பைப் பதிவிறக்குகிறது</string>
|
||||||
|
|
@ -310,7 +309,7 @@
|
||||||
<string name="minute">நிமிடங்கள்</string>
|
<string name="minute">நிமிடங்கள்</string>
|
||||||
<string name="second">இரண்டாவது</string>
|
<string name="second">இரண்டாவது</string>
|
||||||
<string name="milliseconds">மில்லி விநாடிகள்</string>
|
<string name="milliseconds">மில்லி விநாடிகள்</string>
|
||||||
<string name="format_importance_audio">வடிவமைப்பு முக்கியத்துவம் வரிசை (ஆடியோ)</string>
|
<string name="format_importance">வடிவமைப்பு முக்கியத்துவம் வரிசை</string>
|
||||||
<string name="no_download_fragments">துண்டுகளாக பதிவிறக்கம் செய்ய வேண்டாம்</string>
|
<string name="no_download_fragments">துண்டுகளாக பதிவிறக்கம் செய்ய வேண்டாம்</string>
|
||||||
<string name="no_download_fragments_summary">.Part கோப்புகளைப் பயன்படுத்த வேண்டாம் மற்றும் வெளியீட்டு கோப்பில் நேரடியாக எழுதுங்கள்</string>
|
<string name="no_download_fragments_summary">.Part கோப்புகளைப் பயன்படுத்த வேண்டாம் மற்றும் வெளியீட்டு கோப்பில் நேரடியாக எழுதுங்கள்</string>
|
||||||
<string name="concurrent_fragments">ஒரே நேரத்தில் துண்டுகள்</string>
|
<string name="concurrent_fragments">ஒரே நேரத்தில் துண்டுகள்</string>
|
||||||
|
|
|
||||||
|
|
@ -394,8 +394,7 @@
|
||||||
<string name="minute">Dakika</string>
|
<string name="minute">Dakika</string>
|
||||||
<string name="second">Saniye</string>
|
<string name="second">Saniye</string>
|
||||||
<string name="milliseconds">Milisaniye</string>
|
<string name="milliseconds">Milisaniye</string>
|
||||||
<string name="format_importance_audio">Biçim Önem Sırası (Ses)</string>
|
<string name="format_importance">Biçim Önem Sırası</string>
|
||||||
<string name="format_importance_video">Biçim Önem Sırası (Video)</string>
|
|
||||||
<string name="format_importance_note">Biçim öğesinin önemini ayarlayın. Bu sıra yalnızca uygulama indirme kartındaki formatları alıp formatı otomatik seçtiğinde kullanılacaktır!</string>
|
<string name="format_importance_note">Biçim öğesinin önemini ayarlayın. Bu sıra yalnızca uygulama indirme kartındaki formatları alıp formatı otomatik seçtiğinde kullanılacaktır!</string>
|
||||||
<string name="no_audio">Video ama ses olmadan</string>
|
<string name="no_audio">Video ama ses olmadan</string>
|
||||||
<string name="download_archive_folder">Arşiv Klasörünü İndir</string>
|
<string name="download_archive_folder">Arşiv Klasörünü İndir</string>
|
||||||
|
|
|
||||||
|
|
@ -390,12 +390,11 @@
|
||||||
<string name="clear_scheduled">Очистити Заплановане</string>
|
<string name="clear_scheduled">Очистити Заплановане</string>
|
||||||
<string name="minute">Хвилина</string>
|
<string name="minute">Хвилина</string>
|
||||||
<string name="second">Секунда</string>
|
<string name="second">Секунда</string>
|
||||||
<string name="format_importance_audio">Порядок важливості формату (аудіо)</string>
|
|
||||||
<string name="format_importance_note">Налаштуйте важливість елемента формату. Цей порядок буде використовуватися лише в тому випадку, якщо програма витягує формати з карти завантаження та автоматично вибирає формат!</string>
|
<string name="format_importance_note">Налаштуйте важливість елемента формату. Цей порядок буде використовуватися лише в тому випадку, якщо програма витягує формати з карти завантаження та автоматично вибирає формат!</string>
|
||||||
<string name="no_audio">Відео без звуку</string>
|
<string name="no_audio">Відео без звуку</string>
|
||||||
<string name="process_downloads_background">"Завантаження все ще завантажуються. Продовжити їх обробку у фоновому режимі та розпочати завантаження? "</string>
|
<string name="process_downloads_background">"Завантаження все ще завантажуються. Продовжити їх обробку у фоновому режимі та розпочати завантаження? "</string>
|
||||||
<string name="milliseconds">Мілісекунди</string>
|
<string name="milliseconds">Мілісекунди</string>
|
||||||
<string name="format_importance_video">Порядок важливості формату (відео)</string>
|
<string name="format_importance">Порядок важливості формату</string>
|
||||||
<string name="download_archive_folder">Завантажити папку архіву</string>
|
<string name="download_archive_folder">Завантажити папку архіву</string>
|
||||||
<string name="location">Розташування</string>
|
<string name="location">Розташування</string>
|
||||||
<string name="enable_alarm_permission">Вам потрібно включити роздільну здатність SCHEDULE_EXACT_ALARM в установках програми.</string>
|
<string name="enable_alarm_permission">Вам потрібно включити роздільну здатність SCHEDULE_EXACT_ALARM в установках програми.</string>
|
||||||
|
|
|
||||||
|
|
@ -389,8 +389,7 @@
|
||||||
<string name="subtitles">字幕</string>
|
<string name="subtitles">字幕</string>
|
||||||
<string name="move_bottom">移到底部</string>
|
<string name="move_bottom">移到底部</string>
|
||||||
<string name="milliseconds">毫秒</string>
|
<string name="milliseconds">毫秒</string>
|
||||||
<string name="format_importance_audio">格式重要性顺序(音频)</string>
|
<string name="format_importance">格式重要性顺序</string>
|
||||||
<string name="format_importance_video">格式重要性顺序(视频)</string>
|
|
||||||
<string name="format_importance_note">调整格式元素重要性。此顺序仅当应用在下载卡片中获取格式并自动选择格式时才会用到!</string>
|
<string name="format_importance_note">调整格式元素重要性。此顺序仅当应用在下载卡片中获取格式并自动选择格式时才会用到!</string>
|
||||||
<string name="no_audio">没有声音的视频</string>
|
<string name="no_audio">没有声音的视频</string>
|
||||||
<string name="download_archive_folder">下载档案文件夹</string>
|
<string name="download_archive_folder">下载档案文件夹</string>
|
||||||
|
|
|
||||||
|
|
@ -390,10 +390,9 @@
|
||||||
<string name="clear_scheduled">清除排程</string>
|
<string name="clear_scheduled">清除排程</string>
|
||||||
<string name="minute">分鐘</string>
|
<string name="minute">分鐘</string>
|
||||||
<string name="milliseconds">毫秒</string>
|
<string name="milliseconds">毫秒</string>
|
||||||
<string name="format_importance_video">格式重要性順序(影片)</string>
|
|
||||||
<string name="process_downloads_background">"下載仍在載入中。繼續在背景處理它們並開始下載? "</string>
|
<string name="process_downloads_background">"下載仍在載入中。繼續在背景處理它們並開始下載? "</string>
|
||||||
<string name="second">秒</string>
|
<string name="second">秒</string>
|
||||||
<string name="format_importance_audio">格式重要性順序(音訊)</string>
|
<string name="format_importance">格式重要性順序</string>
|
||||||
<string name="download_archive_folder">下載存檔資料夾</string>
|
<string name="download_archive_folder">下載存檔資料夾</string>
|
||||||
<string name="enable_alarm_permission">您需要在應用程式設定中啟用 SCHEDULE_EXACT_ALARM 權限。</string>
|
<string name="enable_alarm_permission">您需要在應用程式設定中啟用 SCHEDULE_EXACT_ALARM 權限。</string>
|
||||||
<string name="location">位置</string>
|
<string name="location">位置</string>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="audio_containers_values">
|
<string-array name="audio_containers_values">
|
||||||
<item>Default</item>
|
<item></item>
|
||||||
<item>mp3</item>
|
<item>mp3</item>
|
||||||
<item>m4a</item>
|
<item>m4a</item>
|
||||||
<item>aac</item>
|
<item>aac</item>
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="video_containers_values">
|
<string-array name="video_containers_values">
|
||||||
<item>Default</item>
|
<item></item>
|
||||||
<item>mp4</item>
|
<item>mp4</item>
|
||||||
<item>webm</item>
|
<item>webm</item>
|
||||||
<item>mkv</item>
|
<item>mkv</item>
|
||||||
|
|
@ -67,11 +67,21 @@
|
||||||
|
|
||||||
<string-array name="audio_formats">
|
<string-array name="audio_formats">
|
||||||
<item>best</item>
|
<item>best</item>
|
||||||
|
<item>192kbps</item>
|
||||||
|
<item>160kbps</item>
|
||||||
|
<item>128kbps</item>
|
||||||
|
<item>96kbps</item>
|
||||||
|
<item>64kbps</item>
|
||||||
<item>worst</item>
|
<item>worst</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="audio_formats_values">
|
<string-array name="audio_formats_values">
|
||||||
<item>ba</item>
|
<item>ba</item>
|
||||||
|
<item>192kbps_ytdlnisgeneric</item>
|
||||||
|
<item>160kbps_ytdlnisgeneric</item>
|
||||||
|
<item>128kbps_ytdlnisgeneric</item>
|
||||||
|
<item>96kbps_ytdlnisgeneric</item>
|
||||||
|
<item>64kbps_ytdlnisgeneric</item>
|
||||||
<item>wa</item>
|
<item>wa</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
|
@ -799,6 +809,7 @@
|
||||||
<item>@string/settings</item>
|
<item>@string/settings</item>
|
||||||
<item>@string/downloads</item>
|
<item>@string/downloads</item>
|
||||||
<item>@string/in_queue</item>
|
<item>@string/in_queue</item>
|
||||||
|
<item>@string/scheduled</item>
|
||||||
<item>@string/cancelled</item>
|
<item>@string/cancelled</item>
|
||||||
<item>@string/errored</item>
|
<item>@string/errored</item>
|
||||||
<item>@string/saved</item>
|
<item>@string/saved</item>
|
||||||
|
|
@ -813,6 +824,7 @@
|
||||||
<item>settings</item>
|
<item>settings</item>
|
||||||
<item>downloads</item>
|
<item>downloads</item>
|
||||||
<item>queued</item>
|
<item>queued</item>
|
||||||
|
<item>scheduled</item>
|
||||||
<item>cancelled</item>
|
<item>cancelled</item>
|
||||||
<item>errored</item>
|
<item>errored</item>
|
||||||
<item>saved</item>
|
<item>saved</item>
|
||||||
|
|
|
||||||
|
|
@ -394,8 +394,7 @@
|
||||||
<string name="minute">Minute</string>
|
<string name="minute">Minute</string>
|
||||||
<string name="second">Second</string>
|
<string name="second">Second</string>
|
||||||
<string name="milliseconds">Milliseconds</string>
|
<string name="milliseconds">Milliseconds</string>
|
||||||
<string name="format_importance_audio">Format Importance Order (Audio)</string>
|
<string name="format_importance">Format Importance Order</string>
|
||||||
<string name="format_importance_video">Format Importance Order (Video)</string>
|
|
||||||
<string name="format_importance_note">Adjust format element importance. This order will only be used when the app fetches the formats in the download card and auto-selects the format!</string>
|
<string name="format_importance_note">Adjust format element importance. This order will only be used when the app fetches the formats in the download card and auto-selects the format!</string>
|
||||||
<string name="no_audio">Video with no Audio</string>
|
<string name="no_audio">Video with no Audio</string>
|
||||||
<string name="download_archive_folder">Download Archive Folder</string>
|
<string name="download_archive_folder">Download Archive Folder</string>
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,14 @@
|
||||||
android:icon="@drawable/ic_textformat"
|
android:icon="@drawable/ic_textformat"
|
||||||
app:key="file_name_template"
|
app:key="file_name_template"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:defaultValue="%(uploader)30B - %(title)120B"
|
app:defaultValue="%(uploader).30B - %(title).170B"
|
||||||
app:title="@string/file_name_template" />
|
app:title="@string/file_name_template" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_textformat"
|
android:icon="@drawable/ic_textformat"
|
||||||
app:key="file_name_template_audio"
|
app:key="file_name_template_audio"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
app:defaultValue="%(uploader)30B - %(title)120B"
|
app:defaultValue="%(uploader).30B - %(title).170B"
|
||||||
app:title="@string/file_name_template" />
|
app:title="@string/file_name_template" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/format">
|
<PreferenceCategory android:title="@string/format">
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="Default"
|
android:defaultValue=""
|
||||||
android:entries="@array/audio_containers"
|
android:entries="@array/audio_containers"
|
||||||
android:entryValues="@array/audio_containers_values"
|
android:entryValues="@array/audio_containers_values"
|
||||||
android:icon="@drawable/ic_code"
|
android:icon="@drawable/ic_code"
|
||||||
|
|
@ -172,7 +172,7 @@
|
||||||
app:title="@string/audio_format" />
|
app:title="@string/audio_format" />
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="Default"
|
android:defaultValue=""
|
||||||
android:entries="@array/video_containers"
|
android:entries="@array/video_containers"
|
||||||
android:entryValues="@array/video_containers_values"
|
android:entryValues="@array/video_containers_values"
|
||||||
android:icon="@drawable/ic_code"
|
android:icon="@drawable/ic_code"
|
||||||
|
|
@ -258,12 +258,12 @@
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_format"
|
android:icon="@drawable/ic_format"
|
||||||
android:key="format_importance_audio"
|
android:key="format_importance_audio"
|
||||||
app:title="@string/format_importance_audio" />
|
app:title="@string/format_importance" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_format"
|
android:icon="@drawable/ic_format"
|
||||||
android:key="format_importance_video"
|
android:key="format_importance_video"
|
||||||
app:title="@string/format_importance_video" />
|
app:title="@string/format_importance" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
|
||||||
71
fastlane/metadata/android/en-US/changelogs/10760.txt
Normal file
71
fastlane/metadata/android/en-US/changelogs/10760.txt
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
## What's New
|
||||||
|
|
||||||
|
- Made finished notification icon color to red
|
||||||
|
- Added download path in the history item details sheet
|
||||||
|
- Fixed Download On Schedule feature. Switched back to AlarmManager as its more accurate than WorkManager
|
||||||
|
- Removed move to top and to bottom in the scheduled tab. Code cleanup
|
||||||
|
- Fixed a huge issue when you press cancel to the quick download item before the data isn't fully fetched, it would go back to being queued after the data fetching is finally done
|
||||||
|
- moved force keyframes at cuts in the general category in processing settings
|
||||||
|
- showed the number when you select all items in the context menu bar
|
||||||
|
- fixed app going always to the search bar when you return to home if you launched the search shortcut from the app menu
|
||||||
|
- Fixed app not hiding update formats button after updating formats
|
||||||
|
- Fixed app not passing ba/b in format logic when using generic best audio format
|
||||||
|
- Fixed meta track parse metadata
|
||||||
|
- Fixed app not cancelling downloads in some cases
|
||||||
|
- Fixed huge issue when using multiple download card and it wouldnt keep user defined changes
|
||||||
|
- Fixed app not removing download notification when cancelling download
|
||||||
|
- Fixed some errors in arabic interface when selecting playlist items
|
||||||
|
- Limited characters in cut interface textboxes to just numbers colons and periods
|
||||||
|
- When user had a defined preferred format id and the user selects a generic 'best' format the app just accounted that format id and didnt consider the preferred video resolution as a fallback
|
||||||
|
- Fixed when trying to schedule a video download and setting it to download as audio as well, the audio is put in queued and not in scheduled
|
||||||
|
- Fixed app crashing when trying to tap adjust download in the errored notification, in case the user removed the download beforehand
|
||||||
|
- Added ability to modify the download item in the queue. But to prevent the app from starting the download prematurely, it is moved to the saved category and after you hit ok it will be requeued. If you cancel the edit card, the item will stay in saved
|
||||||
|
- Fixed app reseting user defined changes in the download card like the download path when it finally updates data
|
||||||
|
- Fixed app crashing when trying to open a download that has no duration in it. Now its disabled
|
||||||
|
- Previously the app would change the state of downloads to Paused, whether it is activepaused or queuedpaused. Since the app now simply pauses all downloads, there is no need to do such transformations especially when you could have many items in the queue. Now simply the app cancels the ytdlp instances and the download worker and stores a state whether the downloads are paused or not
|
||||||
|
- Fixed album playlist track not being saved #482
|
||||||
|
- Fixed retries and fragment retries settings not applying
|
||||||
|
- Fixed container in RTL not showing correctly in video tab in the download card
|
||||||
|
- Fixed bad spacing in RTL for search engines
|
||||||
|
- Fixed app crashing when you multiple select cancelled downloads for redownload
|
||||||
|
- Removed the ability of the app to rename extractors in the history tab. Now they are raw from yt-dlp
|
||||||
|
- Changelog was too slow and buggy, so now its on a recyclerview ultra fast
|
||||||
|
- Fixed the case when you pasted multiple links in the home section and only one item would be registered
|
||||||
|
- Fixed observe sources not downloading new uploads when checked as such
|
||||||
|
- On select playlist items the end textbox was not purely numeric. fixed
|
||||||
|
- Fixed sub format conversion not applying when embedding subtitles
|
||||||
|
- added scheduled as a backup category in teh settings
|
||||||
|
- added ability to show cookie details
|
||||||
|
- fixed app crash in some cases when u copied multiple url and tapped the FAB in home screen
|
||||||
|
- removed excessive file permissions for API 30 and above
|
||||||
|
|
||||||
|
## Format aspect importance
|
||||||
|
|
||||||
|
Now in the settings you can order around which element based on your preferences like codec, container, quality, preferred format id should be prioritised when the app automatically chooses a format for you. The app uses a weigh system to sort formats based on preference.
|
||||||
|
|
||||||
|
- fixed app crashing when swipe to delete a scheduled item
|
||||||
|
- fixed app not modifying time in a scheduled item
|
||||||
|
- added network constraint for observe sources worker
|
||||||
|
- added reconfigure button in errored notification
|
||||||
|
- fixed app not hiding the "Link you copied" after clicking on it
|
||||||
|
- fixed app crashing when trying to toast in the end (#469)
|
||||||
|
- added alternate urls for instagram,facebook,and reddit as supported sites
|
||||||
|
- fixed app not escaping double quotes in filenames
|
||||||
|
- fixed container text alignment for arabic interface
|
||||||
|
- rearranged video quality order in descending order in the settings
|
||||||
|
|
||||||
|
|
||||||
|
## Background processing of items for download
|
||||||
|
|
||||||
|
Someone tested to download a 4000 long playlist in the app. First of all it couldnt even load them in the multiple download card. In those extreme cases the usual recommendation was to just turn quick download on, and consider the whole playlist as a single item and download.
|
||||||
|
|
||||||
|
But still, if a madman wants the ability to modify the list, now the app can handle it and will show the download card. Since it will take a while until all items all processed and converted into download items, a progress bar is added.
|
||||||
|
Still you are able to hit download and schedule button early and let the app continue on its own by using the default configurations of the download items. So its fast like quick download but still separates all items into separate downloads.
|
||||||
|
|
||||||
|
- fixed app using -a "" on observe sources command type downloads
|
||||||
|
- added milliseconds when trying to cut a download
|
||||||
|
- added ability to set the folder location of your download archive path. The file has to be named as download_archive.txt
|
||||||
|
- added feature to number each chapter when using split by chapters
|
||||||
|
- slight changes to downloads already exist dialog
|
||||||
|
- fixed app not considering m.youtube links
|
||||||
|
- other small fixes
|
||||||
Loading…
Reference in a new issue