1.7.3
This commit is contained in:
parent
39c086aa6b
commit
177ba4881c
147 changed files with 2645 additions and 1341 deletions
|
|
@ -10,7 +10,7 @@ plugins {
|
||||||
def properties = new Properties()
|
def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 7
|
def versionMinor = 7
|
||||||
def versionPatch = 2
|
def versionPatch = 3
|
||||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||||
def versionExt = ""
|
def versionExt = ""
|
||||||
|
|
||||||
|
|
|
||||||
724
app/schemas/com.deniscerri.ytdlnis.database.DBManager/15.json
Normal file
724
app/schemas/com.deniscerri.ytdlnis.database.DBManager/15.json
Normal file
|
|
@ -0,0 +1,724 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 15,
|
||||||
|
"identityHash": "3f4252e3a5d5fbf3d4706a4d999055f1",
|
||||||
|
"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": "observeSources",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `status` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyWeekDay` TEXT NOT NULL, `everyMonthDay` INTEGER NOT NULL, `everyTime` INTEGER NOT NULL, `startsTime` INTEGER NOT NULL, `startsMonth` TEXT NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `alreadyProcessedLinks` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadItemTemplate",
|
||||||
|
"columnName": "downloadItemTemplate",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "status",
|
||||||
|
"columnName": "status",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyNr",
|
||||||
|
"columnName": "everyNr",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyCategory",
|
||||||
|
"columnName": "everyCategory",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyWeekDay",
|
||||||
|
"columnName": "everyWeekDay",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyMonthDay",
|
||||||
|
"columnName": "everyMonthDay",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "everyTime",
|
||||||
|
"columnName": "everyTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startsTime",
|
||||||
|
"columnName": "startsTime",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "startsMonth",
|
||||||
|
"columnName": "startsMonth",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "endsDate",
|
||||||
|
"columnName": "endsDate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "endsAfterCount",
|
||||||
|
"columnName": "endsAfterCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "runCount",
|
||||||
|
"columnName": "runCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "getOnlyNewUploads",
|
||||||
|
"columnName": "getOnlyNewUploads",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "retryMissingDownloads",
|
||||||
|
"columnName": "retryMissingDownloads",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "alreadyProcessedLinks",
|
||||||
|
"columnName": "alreadyProcessedLinks",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"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, '3f4252e3a5d5fbf3d4706a4d999055f1')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
||||||
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,13 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,30 @@
|
||||||
package com.deniscerri.ytdlnis.database
|
package com.deniscerri.ytdlnis.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.*
|
import androidx.room.AutoMigration
|
||||||
import androidx.room.migration.AutoMigrationSpec
|
import androidx.room.Database
|
||||||
import androidx.room.migration.Migration
|
import androidx.room.Room
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.work.impl.Migration_11_12
|
import androidx.room.TypeConverters
|
||||||
import androidx.work.impl.Migration_12_13
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.*
|
import com.deniscerri.ytdlnis.database.dao.CookieDao
|
||||||
import com.deniscerri.ytdlnis.database.models.*
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.LogDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.SearchHistoryDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.TerminalDao
|
||||||
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
|
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||||
|
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
@Database(
|
@Database(
|
||||||
|
|
@ -24,7 +40,7 @@ import com.deniscerri.ytdlnis.database.models.*
|
||||||
TerminalItem::class,
|
TerminalItem::class,
|
||||||
ObserveSourcesItem::class
|
ObserveSourcesItem::class
|
||||||
],
|
],
|
||||||
version = 14,
|
version = 15,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration (from = 1, to = 2),
|
AutoMigration (from = 1, to = 2),
|
||||||
AutoMigration (from = 2, to = 3),
|
AutoMigration (from = 2, to = 3),
|
||||||
|
|
@ -39,6 +55,7 @@ import com.deniscerri.ytdlnis.database.models.*
|
||||||
AutoMigration (from = 11, to = 12),
|
AutoMigration (from = 11, to = 12),
|
||||||
AutoMigration (from = 12, to = 13),
|
AutoMigration (from = 12, to = 13),
|
||||||
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
||||||
|
AutoMigration (from = 14, to = 15),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
abstract class DBManager : RoomDatabase(){
|
abstract class DBManager : RoomDatabase(){
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
@ -10,7 +14,7 @@ interface CookieDao {
|
||||||
fun getAllCookies() : List<CookieItem>
|
fun getAllCookies() : List<CookieItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM cookies WHERE url = :url LIMIT 1")
|
@Query("SELECT * FROM cookies WHERE url = :url LIMIT 1")
|
||||||
fun getByURL(url: String) : CookieItem
|
fun getByURL(url: String) : CookieItem?
|
||||||
|
|
||||||
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
@Query("SELECT * FROM cookies ORDER BY id DESC")
|
||||||
fun getAllCookiesFlow() : Flow<List<CookieItem>>
|
fun getAllCookiesFlow() : Flow<List<CookieItem>>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.RewriteQueriesToDropUnusedColumns
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
|
import androidx.room.Upsert
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
|
|
@ -19,14 +26,14 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||||
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)")
|
||||||
fun getActiveDownloadsCountFlow() : Flow<Int>
|
fun getDownloadsCountFlow(statuses: List<String>) : Flow<Int>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||||
fun getDownloadsCountByStatusFlow(status : List<String>) : Flow<Int>
|
fun getDownloadsCountByStatusFlow(status : List<String>) : Flow<Int>
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)")
|
||||||
fun getDownloadsCountByStatus(status : List<String>) : Int
|
fun getDownloadsCountByStatus(statuses : List<String>) : Int
|
||||||
|
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
|
|
@ -79,6 +86,9 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
|
@Query("SELECT id FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||||
|
fun getQueuedDownloadsListIDs() : List<Long>
|
||||||
|
|
||||||
@RewriteQueriesToDropUnusedColumns
|
@RewriteQueriesToDropUnusedColumns
|
||||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||||
fun getCancelledDownloads() : PagingSource<Int, DownloadItemSimple>
|
fun getCancelledDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||||
|
|
@ -207,28 +217,30 @@ interface DownloadDao {
|
||||||
fun getURLsByStatus(status: List<String>) : List<String>
|
fun getURLsByStatus(status: List<String>) : List<String>
|
||||||
|
|
||||||
@Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
|
@Query("UPDATE downloads SET downloadStartTime=0 where id in (:list)")
|
||||||
fun resetScheduleTimeForItems(list: List<Long>)
|
suspend fun resetScheduleTimeForItems(list: List<Long>)
|
||||||
|
|
||||||
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
@Query("Update downloads SET status='Queued', downloadStartTime = 0 WHERE id in (:list)")
|
||||||
fun reQueueDownloadItems(list: List<Long>)
|
suspend fun reQueueDownloadItems(list: List<Long>)
|
||||||
|
|
||||||
@Query("Update downloads SET status='Saved' WHERE status='Processing'")
|
@Query("Update downloads SET status='Saved' WHERE status='Processing'")
|
||||||
fun updateProcessingtoSavedStatus()
|
fun updateProcessingtoSavedStatus()
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
fun putAtTopOfTheQueue(id: Long){
|
suspend fun putAtTopOfTheQueue(existingIDs: List<Long>){
|
||||||
val downloads = getQueuedDownloadsList()
|
val downloads = getQueuedDownloadsListIDs()
|
||||||
val newID = downloads.first().id
|
val newIDs = downloads.sortedBy { it }.take(existingIDs.size)
|
||||||
|
|
||||||
updateDownloadID(id, -id)
|
resetScheduleTimeForItems(existingIDs)
|
||||||
resetScheduleTimeForItems(listOf(-id))
|
existingIDs.forEach { updateDownloadID(it, -it) }
|
||||||
|
downloads.filter { !existingIDs.contains(it) }.reversed().forEach {
|
||||||
downloads.reversed().dropWhile { it.id == id }.forEach {
|
updateDownloadID(it, it + existingIDs.size)
|
||||||
updateDownloadID(it.id, it.id + 1)
|
}
|
||||||
|
|
||||||
|
existingIDs.forEachIndexed { idx, it ->
|
||||||
|
updateDownloadID(-it, newIDs[idx])
|
||||||
}
|
}
|
||||||
updateDownloadID(-id, newID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("Update downloads set id=:newId where id=:id")
|
@Query("Update downloads set id=:newId where id=:id")
|
||||||
fun updateDownloadID(id: Long, newId: Long)
|
suspend fun updateDownloadID(id: Long, newId: Long)
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
@ -27,6 +32,11 @@ interface ResultDao {
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertMultiple(items: List<ResultItem?>) : List<Long>
|
suspend fun insertMultiple(items: List<ResultItem?>) : List<Long>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
suspend fun insertMultipleNoDuplicates(items: List<ResultItem>) : List<Long> {
|
||||||
|
return insertMultiple(items.filter { getResultByURL(it.url) == null })
|
||||||
|
}
|
||||||
|
|
||||||
@Update(onConflict = OnConflictStrategy.REPLACE)
|
@Update(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun update(item: ResultItem)
|
suspend fun update(item: ResultItem)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
package com.deniscerri.ytdlnis.database.dao
|
package com.deniscerri.ytdlnis.database.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.appendLineToLog
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
|
@ -28,16 +32,7 @@ interface TerminalDao {
|
||||||
fun updateLog(line: String, id: Long){
|
fun updateLog(line: String, id: Long){
|
||||||
val t = getTerminalById(id) ?: return
|
val t = getTerminalById(id) ?: return
|
||||||
val log = t.log ?: ""
|
val log = t.log ?: ""
|
||||||
val lines = log.split("\n")
|
updateTerminalLog(log.appendLineToLog(line), id)
|
||||||
//clean dublicate progress + add newline
|
|
||||||
var newLine = line
|
|
||||||
if (newLine.contains("[download")){
|
|
||||||
newLine = "[download]" + line.split("[download]").last()
|
|
||||||
}
|
|
||||||
|
|
||||||
val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
|
||||||
|
|
||||||
updateTerminalLog(l, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import androidx.room.PrimaryKey
|
||||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Entity(tableName = "observeSources")
|
@Entity(tableName = "observeSources")
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|
@ -32,6 +30,8 @@ data class ObserveSourcesItem(
|
||||||
var endsAfterCount: Int,
|
var endsAfterCount: Int,
|
||||||
@ColumnInfo(defaultValue = "0")
|
@ColumnInfo(defaultValue = "0")
|
||||||
var runCount: Int,
|
var runCount: Int,
|
||||||
|
@ColumnInfo(defaultValue = "0")
|
||||||
|
var getOnlyNewUploads: Boolean,
|
||||||
var retryMissingDownloads: Boolean,
|
var retryMissingDownloads: Boolean,
|
||||||
var alreadyProcessedLinks : MutableList<String>
|
var alreadyProcessedLinks : MutableList<String>
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
package com.deniscerri.ytdlnis.database.models
|
package com.deniscerri.ytdlnis.database.models
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
|
||||||
|
|
||||||
data class SearchSuggestionItem(
|
data class SearchSuggestionItem(
|
||||||
var text: String,
|
var text: String,
|
||||||
val type: SearchSuggestionType,
|
val type: SearchSuggestionType,
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,6 @@ data class VideoPreferences (
|
||||||
var writeAutoSubs: Boolean = false,
|
var writeAutoSubs: Boolean = false,
|
||||||
var subsLanguages: String = "en.*,.*-orig",
|
var subsLanguages: String = "en.*,.*-orig",
|
||||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||||
var removeAudio: Boolean = false
|
var removeAudio: Boolean = false,
|
||||||
|
var alsoDownloadAsAudio: Boolean = false
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class CookieRepository(private val cookieDao: CookieDao) {
|
||||||
return cookieDao.getAllCookies()
|
return cookieDao.getAllCookies()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getByURL(url: String) : CookieItem {
|
fun getByURL(url: String) : CookieItem? {
|
||||||
return cookieDao.getByURL(url)
|
return cookieDao.getByURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,32 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.os.Looper
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.NetworkType
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.WorkInfo
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.await
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
class DownloadRepository(private val downloadDao: DownloadDao) {
|
class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
|
|
@ -36,12 +53,20 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
pagingSourceFactory = {downloadDao.getSavedDownloads()}
|
pagingSourceFactory = {downloadDao.getSavedDownloads()}
|
||||||
)
|
)
|
||||||
|
|
||||||
val activeDownloadsCount : Flow<Int> = downloadDao.getActiveDownloadsCountFlow()
|
val activeDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Active, Status.ActivePaused, Status.PausedReQueued).toListString())
|
||||||
|
val queuedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Queued, Status.QueuedPaused).toListString())
|
||||||
|
val cancelledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Cancelled).toListString())
|
||||||
|
val erroredDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Error).toListString())
|
||||||
|
val savedDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Saved).toListString())
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing
|
Active, ActivePaused, PausedReQueued, Queued, QueuedPaused, Error, Cancelled, Saved, Processing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<Status>.toListString() : List<String>{
|
||||||
|
return this.map { it.toString() }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun insert(item: DownloadItem) : Long {
|
suspend fun insert(item: DownloadItem) : Long {
|
||||||
return downloadDao.insert(item)
|
return downloadDao.insert(item)
|
||||||
}
|
}
|
||||||
|
|
@ -162,4 +187,53 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.removeAllLogID()
|
downloadDao.removeAllLogID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context, inputData: Data.Builder = Data.Builder()) {
|
||||||
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||||
|
val workManager = WorkManager.getInstance(context)
|
||||||
|
|
||||||
|
val currentWork = workManager.getWorkInfosByTag("download").await()
|
||||||
|
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
||||||
|
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
var delay = 0L
|
||||||
|
if (queuedItems.isNotEmpty()){
|
||||||
|
delay = if (queuedItems[0].downloadStartTime != 0L){
|
||||||
|
queuedItems[0].downloadStartTime.minus(currentTime)
|
||||||
|
} else 0
|
||||||
|
if (delay <= 60000L) delay = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val workConstraints = Constraints.Builder()
|
||||||
|
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||||
|
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
|
.addTag("download")
|
||||||
|
.setConstraints(workConstraints.build())
|
||||||
|
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(inputData.build())
|
||||||
|
|
||||||
|
queuedItems.forEach {
|
||||||
|
workRequest.addTag(it.id.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
workManager.enqueueUniqueWork(
|
||||||
|
System.currentTimeMillis().toString(),
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
workRequest.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||||
|
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
||||||
|
Looper.prepare().run {
|
||||||
|
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
|
||||||
|
|
||||||
class HistoryRepository(private val historyDao: HistoryDao) {
|
class HistoryRepository(private val historyDao: HistoryDao) {
|
||||||
val items : Flow<List<HistoryItem>> = historyDao.getAllHistory()
|
val items : Flow<List<HistoryItem>> = historyDao.getAllHistory()
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@ package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
import com.deniscerri.ytdlnis.database.dao.LogDao
|
import com.deniscerri.ytdlnis.database.dao.LogDao
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.appendLineToLog
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import java.util.regex.MatchResult
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
class LogRepository(private val logDao: LogDao) {
|
class LogRepository(private val logDao: LogDao) {
|
||||||
val items : Flow<List<LogItem>> = logDao.getAllLogsFlow()
|
val items : Flow<List<LogItem>> = logDao.getAllLogsFlow()
|
||||||
|
|
@ -43,16 +42,7 @@ class LogRepository(private val logDao: LogDao) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val item = getItem(id) ?: return
|
val item = getItem(id) ?: return
|
||||||
val log = item.content ?: ""
|
val log = item.content ?: ""
|
||||||
val lines = log.split("\n")
|
item.content = log.appendLineToLog(line)
|
||||||
//clean dublicate progress + add newline
|
|
||||||
var newLine = line
|
|
||||||
if (newLine.contains("[download")){
|
|
||||||
newLine = "[download]" + line.split("[download]").last()
|
|
||||||
}
|
|
||||||
|
|
||||||
val l = lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
|
||||||
item.content = l
|
|
||||||
|
|
||||||
//item.content += "\n$line"
|
//item.content += "\n$line"
|
||||||
logDao.update(item)
|
logDao.update(item)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao
|
import com.deniscerri.ytdlnis.database.dao.ObserveSourcesDao
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
|
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) {
|
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) {
|
||||||
|
|
@ -57,4 +62,16 @@ class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao)
|
||||||
observeSourcesDao.update(item)
|
observeSourcesDao.update(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cancelObservationTaskByID(context: Context, id: Long){
|
||||||
|
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
||||||
|
alarmManager.cancel(
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
id.toInt(),
|
||||||
|
Intent(context, ObserveAlarmReceiver::class.java),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,17 +1,24 @@
|
||||||
package com.deniscerri.ytdlnis.database.repository
|
package com.deniscerri.ytdlnis.database.repository
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Patterns
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
|
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
|
||||||
private val tag: String = "ResultRepository"
|
private val tag: String = "ResultRepository"
|
||||||
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
|
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
|
||||||
var itemCount = MutableStateFlow(-1)
|
var itemCount = MutableStateFlow(-1)
|
||||||
|
|
||||||
|
enum class SourceType {
|
||||||
|
YOUTUBE_VIDEO, YOUTUBE_PLAYLIST, SEARCH_QUERY, YT_DLP
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun insert(it: ResultItem){
|
suspend fun insert(it: ResultItem){
|
||||||
resultDao.insert(it)
|
resultDao.insert(it)
|
||||||
}
|
}
|
||||||
|
|
@ -28,46 +35,52 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
resultDao.insertMultiple(items)
|
resultDao.insertMultiple(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun search(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
|
suspend fun search(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : ArrayList<ResultItem>{
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
if (resetResults) deleteAll()
|
if (resetResults) deleteAll()
|
||||||
val res = infoUtil.search(inputQuery)
|
val res = infoUtil.search(inputQuery)
|
||||||
itemCount.value = res.size
|
itemCount.value = res.size
|
||||||
val ids = resultDao.insertMultiple(res)
|
if (addToResults){
|
||||||
ids.forEachIndexed { index, id ->
|
val ids = resultDao.insertMultiple(res)
|
||||||
res[index]?.id = id
|
ids.forEachIndexed { index, id ->
|
||||||
|
res[index].id = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
|
suspend fun getYoutubeVideo(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : ArrayList<ResultItem>{
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val v = infoUtil.getYoutubeVideo(inputQuery)
|
val v = infoUtil.getYoutubeVideo(inputQuery) ?: return arrayListOf()
|
||||||
if (resetResults) {
|
if (resetResults) {
|
||||||
deleteAll()
|
deleteAll()
|
||||||
itemCount.value = v.size
|
itemCount.value = v.size
|
||||||
}else{
|
}else{
|
||||||
v.filter { it?.playlistTitle.isNullOrBlank() }.forEach { it?.playlistTitle = "ytdlnis-Search" }
|
v.filter { it.playlistTitle.isBlank() }.forEach { it.playlistTitle = "ytdlnis-Search" }
|
||||||
}
|
}
|
||||||
val ids = resultDao.insertMultiple(v)
|
if (addToResults){
|
||||||
ids.forEachIndexed { index, id ->
|
val ids = resultDao.insertMultiple(v)
|
||||||
v[index]?.id = id
|
ids.forEachIndexed { index, id ->
|
||||||
|
v[index].id = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ArrayList(v)
|
return ArrayList(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getPlaylist(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
|
suspend fun getPlaylist(inputQuery: String, resetResults: Boolean, addToResults: Boolean) : ArrayList<ResultItem>{
|
||||||
val query = inputQuery.split("list=".toRegex()).dropLastWhile { it.isEmpty() }
|
val query = inputQuery.split("list=".toRegex()).dropLastWhile { it.isEmpty() }
|
||||||
.toTypedArray()[1].split("&").first()
|
.toTypedArray()[1].split("&").first()
|
||||||
var nextPageToken = ""
|
var nextPageToken = ""
|
||||||
if (resetResults) deleteAll()
|
if (resetResults) deleteAll()
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val items : ArrayList<ResultItem?> = arrayListOf()
|
val items = arrayListOf<ResultItem>()
|
||||||
do {
|
do {
|
||||||
val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "")
|
val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0].playlistTitle else "")
|
||||||
val ids = resultDao.insertMultiple(tmp.videos.toList())
|
if (addToResults){
|
||||||
ids.forEachIndexed { index, id ->
|
val ids = resultDao.insertMultiple(tmp.videos.toList())
|
||||||
tmp.videos[index]?.id = id
|
ids.forEachIndexed { index, id ->
|
||||||
|
tmp.videos[index].id = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
items.addAll(tmp.videos)
|
items.addAll(tmp.videos)
|
||||||
val tmpToken = tmp.nextPageToken
|
val tmpToken = tmp.nextPageToken
|
||||||
|
|
@ -79,19 +92,23 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDefault(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?> {
|
suspend fun getDefault(inputQuery: String, resetResults: Boolean, addToResults: Boolean, singleItem: Boolean = false) : ArrayList<ResultItem> {
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val items = infoUtil.getFromYTDL(inputQuery)
|
val items = infoUtil.getFromYTDL(inputQuery, singleItem)
|
||||||
if (resetResults) {
|
if (resetResults) {
|
||||||
deleteAll()
|
deleteAll()
|
||||||
itemCount.value = items.size
|
itemCount.value = items.size
|
||||||
}else{
|
}else{
|
||||||
items.filter { it?.playlistTitle.isNullOrBlank() }.forEach { it!!.playlistTitle = "ytdlnis-Search" }
|
items.filter { it.playlistTitle.isNullOrBlank() }.forEach { it.playlistTitle = "ytdlnis-Search" }
|
||||||
}
|
}
|
||||||
val ids = resultDao.insertMultiple(items.toList())
|
|
||||||
ids.forEachIndexed { index, id ->
|
if (addToResults){
|
||||||
items[index]?.id = id
|
val ids = resultDao.insertMultiple(items.toList())
|
||||||
|
ids.forEachIndexed { index, id ->
|
||||||
|
items[index].id = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,4 +128,59 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
fun getItemByURL(url: String): ResultItem? {
|
fun getItemByURL(url: String): ResultItem? {
|
||||||
return resultDao.getResultByURL(url)
|
return resultDao.getResultByURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getResultsFromSource(inputQuery: String, resetResults: Boolean, addToResults: Boolean = true, singleItem: Boolean = false) : ArrayList<ResultItem> {
|
||||||
|
return when(getQueryType(inputQuery)){
|
||||||
|
SourceType.YOUTUBE_VIDEO -> {
|
||||||
|
getYoutubeVideo(inputQuery, resetResults, addToResults)
|
||||||
|
}
|
||||||
|
SourceType.YOUTUBE_PLAYLIST -> {
|
||||||
|
if (singleItem){
|
||||||
|
getDefault(inputQuery, resetResults, addToResults, true)
|
||||||
|
}else{
|
||||||
|
getPlaylist(inputQuery, resetResults, addToResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SourceType.SEARCH_QUERY -> {
|
||||||
|
search(inputQuery, resetResults, addToResults)
|
||||||
|
}
|
||||||
|
SourceType.YT_DLP -> {
|
||||||
|
getDefault(inputQuery, resetResults, addToResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getQueryType(inputQuery: String) : SourceType {
|
||||||
|
var type = SourceType.SEARCH_QUERY
|
||||||
|
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||||
|
val m = p.matcher(inputQuery)
|
||||||
|
if (m.find()) {
|
||||||
|
type = SourceType.YOUTUBE_VIDEO
|
||||||
|
if (inputQuery.contains("playlist?list=")) {
|
||||||
|
type = SourceType.YOUTUBE_PLAYLIST
|
||||||
|
}
|
||||||
|
} else if (Patterns.WEB_URL.matcher(inputQuery).matches()) {
|
||||||
|
type = SourceType.YT_DLP
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateDownloadItem(
|
||||||
|
downloadItem: DownloadItem
|
||||||
|
) : DownloadItem? {
|
||||||
|
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
||||||
|
runCatching {
|
||||||
|
val info = getResultsFromSource(downloadItem.url, resetResults = false, addToResults = false, singleItem = true).first()
|
||||||
|
if (downloadItem.title.isEmpty()) downloadItem.title = info.title
|
||||||
|
if (downloadItem.author.isEmpty()) downloadItem.author = info.author
|
||||||
|
downloadItem.duration = info.duration
|
||||||
|
downloadItem.website = info.website
|
||||||
|
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb
|
||||||
|
return downloadItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -17,12 +17,11 @@ import com.deniscerri.ytdlnis.BuildConfig
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
import com.deniscerri.ytdlnis.database.repository.CookieRepository
|
||||||
import com.deniscerri.ytdlnis.ui.more.CookiesFragment
|
|
||||||
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
import com.deniscerri.ytdlnis.ui.more.WebViewActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
|
|
@ -44,7 +43,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
return repository.getAll()
|
return repository.getAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getByURL(url: String) : CookieItem {
|
fun getByURL(url: String) : CookieItem? {
|
||||||
return repository.getByURL(url)
|
return repository.getByURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +74,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SdCardPath")
|
@SuppressLint("SdCardPath")
|
||||||
fun getCookiesFromDB() : Result<String> = kotlin.runCatching {
|
fun getCookiesFromDB(url: String) : Result<String> = kotlin.runCatching {
|
||||||
CookieManager.getInstance().run {
|
CookieManager.getInstance().run {
|
||||||
if (!hasCookies()) throw Exception("There is no cookies in the database!")
|
if (!hasCookies()) throw Exception("There is no cookies in the database!")
|
||||||
flush()
|
flush()
|
||||||
|
|
@ -83,30 +82,35 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
val dbPath = File("/data/data/${BuildConfig.APPLICATION_ID}/").walkTopDown().find { it.name == "Cookies" }
|
val dbPath = File("/data/data/${BuildConfig.APPLICATION_ID}/").walkTopDown().find { it.name == "Cookies" }
|
||||||
?: throw Exception("Cookies File not found!")
|
?: throw Exception("Cookies File not found!")
|
||||||
|
|
||||||
SQLiteDatabase.openDatabase(
|
val db = SQLiteDatabase.openDatabase(
|
||||||
dbPath.absolutePath, null, OPEN_READONLY
|
dbPath.absolutePath, null, OPEN_READONLY
|
||||||
).run {
|
)
|
||||||
val projection = arrayOf(
|
|
||||||
CookieObject.HOST,
|
|
||||||
CookieObject.EXPIRY,
|
|
||||||
CookieObject.PATH,
|
|
||||||
CookieObject.NAME,
|
|
||||||
CookieObject.VALUE,
|
|
||||||
CookieObject.SECURE
|
|
||||||
)
|
|
||||||
val cookieList = mutableListOf<WebViewActivity.CookieItem>()
|
|
||||||
query(
|
|
||||||
"cookies", projection, null, null, null, null, null
|
|
||||||
).run {
|
|
||||||
while (moveToNext()) {
|
|
||||||
val expiry = getLong(getColumnIndexOrThrow(CookieObject.EXPIRY))
|
|
||||||
val name = getString(getColumnIndexOrThrow(CookieObject.NAME))
|
|
||||||
val value = getString(getColumnIndexOrThrow(CookieObject.VALUE))
|
|
||||||
val path = getString(getColumnIndexOrThrow(CookieObject.PATH))
|
|
||||||
val secure = getLong(getColumnIndexOrThrow(CookieObject.SECURE)) == 1L
|
|
||||||
val hostKey = getString(getColumnIndexOrThrow(CookieObject.HOST))
|
|
||||||
|
|
||||||
val host = if (hostKey[0] != '.') ".$hostKey" else hostKey
|
val projection = arrayOf(
|
||||||
|
CookieObject.HOST,
|
||||||
|
CookieObject.EXPIRY,
|
||||||
|
CookieObject.PATH,
|
||||||
|
CookieObject.NAME,
|
||||||
|
CookieObject.VALUE,
|
||||||
|
CookieObject.SECURE
|
||||||
|
)
|
||||||
|
val cookieList = mutableListOf<WebViewActivity.CookieItem>()
|
||||||
|
db.query(
|
||||||
|
"cookies", projection, null, null, null, null, null
|
||||||
|
).run {
|
||||||
|
while (moveToNext()) {
|
||||||
|
val expiry = getLong(getColumnIndexOrThrow(CookieObject.EXPIRY))
|
||||||
|
val name = getString(getColumnIndexOrThrow(CookieObject.NAME))
|
||||||
|
val value = getString(getColumnIndexOrThrow(CookieObject.VALUE))
|
||||||
|
val path = getString(getColumnIndexOrThrow(CookieObject.PATH))
|
||||||
|
val secure = getLong(getColumnIndexOrThrow(CookieObject.SECURE)) == 1L
|
||||||
|
val hostKey = getString(getColumnIndexOrThrow(CookieObject.HOST))
|
||||||
|
|
||||||
|
|
||||||
|
val host = if (hostKey[0] != '.') ".$hostKey" else hostKey
|
||||||
|
|
||||||
|
val requiredHost = URL(url).host
|
||||||
|
if(host.contains(requiredHost)){
|
||||||
cookieList.add(
|
cookieList.add(
|
||||||
WebViewActivity.CookieItem(
|
WebViewActivity.CookieItem(
|
||||||
domain = host,
|
domain = host,
|
||||||
|
|
@ -118,13 +122,13 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
close()
|
|
||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
cookieList.fold(StringBuilder("")) { acc, cookie ->
|
|
||||||
acc.append(cookie.toNetscapeFormat()).append("\n")
|
|
||||||
}.toString()
|
|
||||||
}
|
}
|
||||||
|
db.close()
|
||||||
|
cookieList.fold(StringBuilder("")) { acc, cookie ->
|
||||||
|
acc.append(cookie.toNetscapeFormat()).append("\n")
|
||||||
|
}.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCookiesFile() = viewModelScope.launch(Dispatchers.IO) {
|
fun updateCookiesFile() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,25 @@
|
||||||
package com.deniscerri.ytdlnis.database.viewmodel
|
package com.deniscerri.ytdlnis.database.viewmodel
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.os.Looper
|
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.core.updateTransition
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Constraints
|
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.await
|
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
|
||||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
|
@ -41,12 +30,11 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
import com.deniscerri.ytdlnis.database.models.VideoPreferences
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.FormatTuple
|
import com.deniscerri.ytdlnis.ui.downloadcard.FormatTuple
|
||||||
import com.deniscerri.ytdlnis.util.DownloadUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
|
||||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -59,7 +47,6 @@ import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadViewModel(private val application: Application) : AndroidViewModel(application) {
|
class DownloadViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
|
|
@ -72,11 +59,16 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
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 processingDownloads : Flow<List<DownloadItem>>
|
||||||
val activeDownloadsCount : Flow<Int>
|
|
||||||
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>>
|
||||||
|
|
||||||
|
val activeDownloadsCount : Flow<Int>
|
||||||
|
val queuedDownloadsCount : Flow<Int>
|
||||||
|
val cancelledDownloadsCount : Flow<Int>
|
||||||
|
val erroredDownloadsCount : Flow<Int>
|
||||||
|
val savedDownloadsCount : Flow<Int>
|
||||||
|
|
||||||
private var bestVideoFormat : Format
|
private var bestVideoFormat : Format
|
||||||
private var bestAudioFormat : Format
|
private var bestAudioFormat : Format
|
||||||
private var defaultVideoFormats : MutableList<Format>
|
private var defaultVideoFormats : MutableList<Format>
|
||||||
|
|
@ -94,6 +86,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
private var audioCodec: String?
|
private var audioCodec: String?
|
||||||
private val dao: DownloadDao
|
private val dao: DownloadDao
|
||||||
private val historyRepository: HistoryRepository
|
private val historyRepository: HistoryRepository
|
||||||
|
private val resultRepository: ResultRepository
|
||||||
|
|
||||||
data class AlreadyExistsUIState(
|
data class AlreadyExistsUIState(
|
||||||
var historyItems: MutableList<Long>,
|
var historyItems: MutableList<Long>,
|
||||||
|
|
@ -120,15 +113,21 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
dao = dbManager.downloadDao
|
dao = dbManager.downloadDao
|
||||||
repository = DownloadRepository(dao)
|
repository = DownloadRepository(dao)
|
||||||
historyRepository = HistoryRepository(dbManager.historyDao)
|
historyRepository = HistoryRepository(dbManager.historyDao)
|
||||||
|
resultRepository = ResultRepository(dbManager.resultDao, application)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
infoUtil = InfoUtil(application)
|
infoUtil = InfoUtil(application)
|
||||||
|
|
||||||
|
activeDownloadsCount = repository.activeDownloadsCount
|
||||||
|
queuedDownloadsCount = repository.queuedDownloadsCount
|
||||||
|
cancelledDownloadsCount = repository.cancelledDownloadsCount
|
||||||
|
erroredDownloadsCount = repository.erroredDownloadsCount
|
||||||
|
savedDownloadsCount = repository.savedDownloadsCount
|
||||||
|
|
||||||
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
|
processingDownloads = repository.processingDownloads
|
||||||
activeDownloadsCount = repository.activeDownloadsCount
|
|
||||||
savedDownloads = repository.savedDownloads.flow
|
savedDownloads = repository.savedDownloads.flow
|
||||||
cancelledDownloads = repository.cancelledDownloads.flow
|
cancelledDownloads = repository.cancelledDownloads.flow
|
||||||
erroredDownloads = repository.erroredDownloads.flow
|
erroredDownloads = repository.erroredDownloads.flow
|
||||||
|
|
@ -663,17 +662,17 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
|
|
||||||
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun resetScheduleTimeForItemsAndStartDownload(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||||
DownloadUtil.startDownloadWorker(emptyList(), application)
|
repository.startDownloadWorker(emptyList(), application)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{
|
suspend fun putAtTopOfQueue(ids: List<Long>) = CoroutineScope(Dispatchers.IO).launch{
|
||||||
dbManager.downloadDao.putAtTopOfTheQueue(id)
|
dbManager.downloadDao.putAtTopOfTheQueue(ids)
|
||||||
DownloadUtil.startDownloadWorker(emptyList(), application)
|
repository.startDownloadWorker(emptyList(), application)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
dbManager.downloadDao.reQueueDownloadItems(items)
|
dbManager.downloadDao.reQueueDownloadItems(items)
|
||||||
DownloadUtil.startDownloadWorker(emptyList(), application)
|
repository.startDownloadWorker(emptyList(), application)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun queueDownloads(items: List<DownloadItem>, ign : Boolean = false) : Pair<List<Long>, List<Long>> {
|
suspend fun queueDownloads(items: List<DownloadItem>, ign : Boolean = false) : Pair<List<Long>, List<Long>> {
|
||||||
|
|
@ -761,19 +760,27 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
|
|
||||||
if (queuedItems.isNotEmpty()){
|
if (queuedItems.isNotEmpty()){
|
||||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime()){
|
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime()){
|
||||||
DownloadUtil.startDownloadWorker(queuedItems, context)
|
repository.startDownloadWorker(queuedItems, context)
|
||||||
|
|
||||||
if(!useScheduler){
|
if(!useScheduler){
|
||||||
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||||
try{
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
runCatching {
|
||||||
}catch (ignored: Exception){}
|
resultRepository.updateDownloadItem(it)?.apply {
|
||||||
|
repository.updateWithoutUpsert(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
||||||
try{
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
runCatching {
|
||||||
}catch (ignored: Exception){}
|
resultRepository.updateDownloadItem(it)?.apply {
|
||||||
|
repository.updateWithoutUpsert(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -819,7 +826,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
items.forEachIndexed { index, i ->
|
items.forEachIndexed { index, i ->
|
||||||
i.format = selectedFormats[index].format
|
i.format = selectedFormats[index].format
|
||||||
if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
if (i.type == Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
|
||||||
updateDownload(i)
|
repository.update(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.map { it.format.filesize }
|
return items.map { it.format.filesize }
|
||||||
|
|
@ -829,7 +836,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
val items = repository.getProcessingDownloads()
|
val items = repository.getProcessingDownloads()
|
||||||
items.forEach { i ->
|
items.forEach { i ->
|
||||||
i.format = format
|
i.format = format
|
||||||
updateDownload(i)
|
repository.update(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -837,7 +844,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
val items = repository.getProcessingDownloads()
|
val items = repository.getProcessingDownloads()
|
||||||
items.forEach { i ->
|
items.forEach { i ->
|
||||||
i.downloadPath = path
|
i.downloadPath = path
|
||||||
updateDownload(i)
|
repository.update(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -866,7 +873,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
dbManager.resultDao.update(this)
|
dbManager.resultDao.update(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDownload(i)
|
repository.update(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -874,7 +881,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
dao.getProcessingDownloadsList().apply {
|
dao.getProcessingDownloadsList().apply {
|
||||||
this.forEach {
|
this.forEach {
|
||||||
it.status = DownloadRepository.Status.Saved.toString()
|
it.status = DownloadRepository.Status.Saved.toString()
|
||||||
updateDownload(it)
|
repository.update(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val ids = this.map { it.id }
|
val ids = this.map { it.id }
|
||||||
|
|
@ -900,7 +907,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
repository.getProcessingDownloads().apply {
|
repository.getProcessingDownloads().apply {
|
||||||
val new = switchDownloadType(this, newType)
|
val new = switchDownloadType(this, newType)
|
||||||
new.forEach {
|
new.forEach {
|
||||||
updateDownload(it)
|
repository.update(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -933,28 +940,5 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
||||||
return dao.getURLsByStatus(list.map { it.toString() })
|
return dao.getURLsByStatus(list.map { it.toString() })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDownloadItem(
|
|
||||||
downloadItem: DownloadItem,
|
|
||||||
infoUtil: InfoUtil,
|
|
||||||
dao: DownloadDao,
|
|
||||||
resultDao: ResultDao
|
|
||||||
) : Boolean {
|
|
||||||
var wasQuickDownloaded = false
|
|
||||||
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
|
||||||
runCatching {
|
|
||||||
val info = infoUtil.getMissingInfo(downloadItem.url)
|
|
||||||
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
|
||||||
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
|
|
||||||
downloadItem.duration = info?.duration.toString()
|
|
||||||
downloadItem.website = info?.website.toString()
|
|
||||||
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
|
|
||||||
runBlocking {
|
|
||||||
wasQuickDownloaded = resultDao.getCountInt() == 0
|
|
||||||
repository.updateWithoutUpsert(downloadItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wasQuickDownloaded
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -8,9 +8,9 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
||||||
import android.app.AlarmManager
|
import android.app.AlarmManager
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
|
@ -12,12 +11,9 @@ import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||||
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
||||||
import com.deniscerri.ytdlnis.util.DownloadUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.closestValue
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
|
|
@ -63,13 +59,13 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(item: ObserveSourcesItem) = viewModelScope.launch(Dispatchers.IO) {
|
fun delete(item: ObserveSourcesItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
runCatching { DownloadUtil.cancelObservationTaskByID(application, item.id) }
|
runCatching { repository.cancelObservationTaskByID(application, item.id) }
|
||||||
repository.delete(item)
|
repository.delete(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
getAll().forEach {
|
getAll().forEach {
|
||||||
runCatching { DownloadUtil.cancelObservationTaskByID(application, it.id) }
|
runCatching { repository.cancelObservationTaskByID(application, it.id) }
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.deleteAll()
|
repository.deleteAll()
|
||||||
|
|
@ -94,7 +90,7 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
||||||
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||||
|
|
||||||
|
|
||||||
DownloadUtil.cancelObservationTaskByID(application, id)
|
repository.cancelObservationTaskByID(application, id)
|
||||||
|
|
||||||
val intent = Intent(application, ObserveAlarmReceiver::class.java)
|
val intent = Intent(application, ObserveAlarmReceiver::class.java)
|
||||||
intent.putExtra("id", id)
|
intent.putExtra("id", id)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import android.app.ActivityManager.RunningAppProcessInfo
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Patterns
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
|
|
@ -21,14 +20,14 @@ import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDLException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import java.util.concurrent.CancellationException
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
|
|
||||||
class ResultViewModel(private val application: Application) : AndroidViewModel(application) {
|
class ResultViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||||
|
|
@ -53,6 +52,18 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
errorMessage = null,
|
errorMessage = null,
|
||||||
actions = null
|
actions = null
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
val updatingData: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
var updateResultData: MutableStateFlow<List<ResultItem?>?> = MutableStateFlow(null)
|
||||||
|
private var updateResultDataJob : Job? = null
|
||||||
|
|
||||||
|
val updatingFormats: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(null)
|
||||||
|
private var updateFormatsResultDataJob: Job? = null
|
||||||
|
|
||||||
|
var parsingQueries: Job? = null
|
||||||
|
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
@ -86,41 +97,45 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
deleteAll()
|
deleteAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
suspend fun parseQueries(inputQueries: List<String>) : List<ResultItem?> {
|
|
||||||
if (inputQueries.size > 1){
|
|
||||||
repository.itemCount.value = inputQueries.size
|
|
||||||
}
|
|
||||||
val resetResults = inputQueries.size == 1
|
|
||||||
|
|
||||||
uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
|
fun cancelParsingQueries(){
|
||||||
return withContext(Dispatchers.IO){
|
parsingQueries?.cancel(CancellationException())
|
||||||
val res = mutableListOf<ResultItem?>()
|
uiState.update {it.copy(processing = false)}
|
||||||
inputQueries.forEach { inputQuery ->
|
}
|
||||||
val type = getQueryType(inputQuery)
|
|
||||||
try {
|
|
||||||
when (type) {
|
|
||||||
"Search" -> res.addAll(repository.search(inputQuery, resetResults))
|
|
||||||
"YT_Video" -> res.addAll(repository.getYoutubeVideo(inputQuery, resetResults))
|
|
||||||
"YT_Playlist" -> res.addAll(repository.getPlaylist(inputQuery, resetResults))
|
|
||||||
else -> res.addAll(repository.getDefault(inputQuery, resetResults))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (updateResultDataJob?.isCancelled == false){
|
|
||||||
uiState.update {it.copy(
|
|
||||||
processing = false,
|
|
||||||
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
|
||||||
actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
|
|
||||||
)}
|
|
||||||
Log.e(tag, e.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
suspend fun parseQueries(inputQueries: List<String>, onResult: (list: List<ResultItem?>) -> Unit) {
|
||||||
|
if (parsingQueries == null || parsingQueries?.isCancelled == true || parsingQueries?.isCompleted == true) {
|
||||||
|
parsingQueries = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
if (inputQueries.size > 1){
|
||||||
|
repository.itemCount.value = inputQueries.size
|
||||||
}
|
}
|
||||||
|
val resetResults = inputQueries.size == 1
|
||||||
|
|
||||||
|
uiState.update {it.copy(processing = true, errorMessage = null, actions = null)}
|
||||||
|
val res = mutableListOf<ResultItem?>()
|
||||||
|
inputQueries.forEach { inputQuery ->
|
||||||
|
try {
|
||||||
|
res.addAll(repository.getResultsFromSource(inputQuery, resetResults))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (updateResultDataJob?.isCancelled == false || e is YoutubeDLException){
|
||||||
|
uiState.update {it.copy(
|
||||||
|
processing = false,
|
||||||
|
errorMessage = Pair(R.string.no_results, e.message.toString()),
|
||||||
|
actions = mutableListOf(Pair(R.string.copy_log, ResultAction.COPY_LOG))
|
||||||
|
)}
|
||||||
|
Log.e(tag, e.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isForegrounded() && inputQueries.size > 1){
|
||||||
|
notificationUtil.showQueriesFinished()
|
||||||
|
}
|
||||||
|
uiState.update {it.copy(processing = false)}
|
||||||
|
onResult(res)
|
||||||
}
|
}
|
||||||
if (!isForegrounded() && inputQueries.size > 1){
|
}else{
|
||||||
notificationUtil.showQueriesFinished()
|
onResult(listOf())
|
||||||
}
|
|
||||||
uiState.update {it.copy(processing = false)}
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,20 +148,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
}.getOrElse { true }
|
}.getOrElse { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getQueryType(inputQuery: String) : String {
|
|
||||||
var type = "Search"
|
|
||||||
val p = Pattern.compile("(^(https?)://(www.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
|
||||||
val m = p.matcher(inputQuery)
|
|
||||||
if (m.find()) {
|
|
||||||
type = "YT_Video"
|
|
||||||
if (inputQuery.contains("playlist?list=")) {
|
|
||||||
type = "YT_Playlist"
|
|
||||||
}
|
|
||||||
} else if (Patterns.WEB_URL.matcher(inputQuery).matches()) {
|
|
||||||
type = "Default"
|
|
||||||
}
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
|
|
||||||
fun insert(items: ArrayList<ResultItem?>) = viewModelScope.launch(Dispatchers.IO){
|
fun insert(items: ArrayList<ResultItem?>) = viewModelScope.launch(Dispatchers.IO){
|
||||||
items.forEach {
|
items.forEach {
|
||||||
|
|
@ -196,21 +198,19 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val updatingData: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
var updateResultData: MutableStateFlow<List<ResultItem?>?> = MutableStateFlow(null)
|
|
||||||
private var updateResultDataJob : Job? = null
|
|
||||||
|
|
||||||
val updatingFormats: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(null)
|
|
||||||
private var updateFormatsResultDataJob: Job? = null
|
|
||||||
|
|
||||||
suspend fun updateItemData(res: ResultItem){
|
suspend fun updateItemData(res: ResultItem){
|
||||||
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
|
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
|
||||||
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
|
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
updatingData.emit(true)
|
updatingData.emit(true)
|
||||||
val result = parseQueries(listOf(res.url))
|
updatingData.value = true
|
||||||
updatingData.emit(false)
|
parseQueries(listOf(res.url)){ result ->
|
||||||
updateResultData.emit(result)
|
viewModelScope.launch(Dispatchers.IO){
|
||||||
|
updatingData.emit(false)
|
||||||
|
updatingData.value = false
|
||||||
|
updateResultData.emit(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateResultDataJob?.start()
|
updateResultDataJob?.start()
|
||||||
|
|
@ -218,6 +218,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
if (it != null){
|
if (it != null){
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
updatingData.emit(false)
|
updatingData.emit(false)
|
||||||
|
updatingData.value = false
|
||||||
updateResultData.emit(mutableListOf())
|
updateResultData.emit(mutableListOf())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,13 +227,14 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cancelUpdateItemData(){
|
suspend fun cancelUpdateItemData(){
|
||||||
updateResultDataJob?.cancel()
|
updateResultDataJob?.cancel(CancellationException())
|
||||||
updatingData.emit(false)
|
updatingData.emit(false)
|
||||||
|
updatingData.value = false
|
||||||
updateResultData.emit(null)
|
updateResultData.emit(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun cancelUpdateFormatsItemData(){
|
suspend fun cancelUpdateFormatsItemData(){
|
||||||
updateFormatsResultDataJob?.cancel()
|
updateFormatsResultDataJob?.cancel(CancellationException())
|
||||||
updatingFormats.emit(false)
|
updatingFormats.emit(false)
|
||||||
updateFormatsResultData.emit(null)
|
updateFormatsResultData.emit(null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import androidx.work.Data
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
|
||||||
import com.deniscerri.ytdlnis.work.ObserveSourceWorker
|
import com.deniscerri.ytdlnis.work.ObserveSourceWorker
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(c: Context, intent: Intent) {
|
override fun onReceive(c: Context, intent: Intent) {
|
||||||
|
val result = goAsync()
|
||||||
val id = intent.getIntExtra("itemID", 0)
|
val id = intent.getIntExtra("itemID", 0)
|
||||||
if (id != 0) {
|
if (id != 0) {
|
||||||
runCatching {
|
runCatching {
|
||||||
|
|
@ -20,13 +22,17 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||||
val notificationUtil = NotificationUtil(c)
|
val notificationUtil = NotificationUtil(c)
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
notificationUtil.createResumeDownload(id, title)
|
|
||||||
val dbManager = DBManager.getInstance(c)
|
val dbManager = DBManager.getInstance(c)
|
||||||
CoroutineScope(Dispatchers.IO).launch{
|
CoroutineScope(Dispatchers.IO).launch{
|
||||||
runCatching {
|
try {
|
||||||
val item = dbManager.downloadDao.getDownloadById(id.toLong())
|
val item = dbManager.downloadDao.getDownloadById(id.toLong())
|
||||||
item.status = DownloadRepository.Status.ActivePaused.toString()
|
item.status = DownloadRepository.Status.ActivePaused.toString()
|
||||||
dbManager.downloadDao.update(item)
|
dbManager.downloadDao.update(item)
|
||||||
|
}finally {
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
notificationUtil.createResumeDownload(id, title)
|
||||||
|
result.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver.downloadAlarmReceivers
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.deniscerri.ytdlnis.ui
|
package com.deniscerri.ytdlnis.ui
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
|
|
@ -15,14 +14,12 @@ import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.View.*
|
import android.view.View.*
|
||||||
import android.view.animation.Interpolator
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
|
@ -45,7 +42,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
|
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.SearchSuggestionsAdapter
|
import com.deniscerri.ytdlnis.ui.adapter.SearchSuggestionsAdapter
|
||||||
import com.deniscerri.ytdlnis.util.Extensions
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
|
|
@ -220,7 +216,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
if (inputQueries != null) {
|
if (inputQueries != null) {
|
||||||
resultViewModel.deleteAll()
|
resultViewModel.deleteAll()
|
||||||
lifecycleScope.launch(Dispatchers.IO){
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
resultViewModel.parseQueries(inputQueries!!)
|
resultViewModel.parseQueries(inputQueries!!){}
|
||||||
inputQueries = null
|
inputQueries = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -321,11 +317,25 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
|
|
||||||
requireView().post {
|
requireView().post {
|
||||||
checkClipboard().apply {
|
checkClipboard().apply {
|
||||||
clipboardFab?.isVisible = this != null
|
this?.apply {
|
||||||
clipboardFab?.setOnClickListener {
|
clipboardFab?.isVisible = this.isNotEmpty()
|
||||||
searchView!!.setText(this)
|
clipboardFab?.setOnClickListener {
|
||||||
clipboardFab?.isVisible = false
|
if (this.size == 1){
|
||||||
initSearch(searchView!!)
|
searchView?.setText(this.first())
|
||||||
|
clipboardFab?.isVisible = false
|
||||||
|
initSearch(searchView!!)
|
||||||
|
}else{
|
||||||
|
searchBar?.performClick()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
this@apply.forEach {
|
||||||
|
onSearchSuggestionAdd(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -435,6 +445,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
searchBar!!.setOnMenuItemClickListener { m: MenuItem ->
|
searchBar!!.setOnMenuItemClickListener { m: MenuItem ->
|
||||||
when (m.itemId) {
|
when (m.itemId) {
|
||||||
R.id.delete_results -> {
|
R.id.delete_results -> {
|
||||||
|
resultViewModel.cancelParsingQueries()
|
||||||
resultViewModel.getTrending()
|
resultViewModel.getTrending()
|
||||||
selectedObjects = ArrayList()
|
selectedObjects = ArrayList()
|
||||||
searchBar!!.setText("")
|
searchBar!!.setText("")
|
||||||
|
|
@ -489,7 +500,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
|
|
||||||
val url = checkClipboard()
|
val url = checkClipboard()
|
||||||
url?.apply {
|
url?.apply {
|
||||||
combinedList.add(0, SearchSuggestionItem(this, SearchSuggestionType.CLIPBOARD))
|
combinedList.add(0, SearchSuggestionItem(this.joinToString("\n"), SearchSuggestionType.CLIPBOARD))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -614,10 +625,10 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
}
|
}
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
resultViewModel.parseQueries(queryList)
|
resultViewModel.parseQueries(queryList){}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
resultViewModel.parseQueries(queryList)
|
resultViewModel.parseQueries(queryList){}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -830,12 +841,11 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun checkClipboard(): String?{
|
private fun checkClipboard(): List<String>?{
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
val clip = clipboard.primaryClip!!.getItemAt(0).text
|
val clip = clipboard.primaryClip!!.getItemAt(0).text
|
||||||
val matchesURL = Patterns.WEB_URL.matcher(clip.toString()).matches()
|
return clip.split("\r\n").filter { Patterns.WEB_URL.matcher(it).matches() }
|
||||||
return if (matchesURL) clip.toString() else null
|
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -850,9 +860,17 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchSuggestionClick(text: String) {
|
override fun onSearchSuggestionClick(text: String) {
|
||||||
clipboardFab?.isVisible = false
|
val res = text.split("\n")
|
||||||
searchView!!.setText(text)
|
if (res.size == 1){
|
||||||
initSearch(searchView!!)
|
clipboardFab?.isVisible = false
|
||||||
|
searchView!!.setText(text)
|
||||||
|
initSearch(searchView!!)
|
||||||
|
}else{
|
||||||
|
res.forEach {
|
||||||
|
onSearchSuggestionAdd(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchSuggestionAdd(text: String) {
|
override fun onSearchSuggestionAdd(text: String) {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
|
@ -22,9 +21,7 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.util.Extensions
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.dp
|
import com.deniscerri.ytdlnis.util.Extensions.dp
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
@ -63,7 +60,6 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
card.popup()
|
|
||||||
card.tag = "${item!!.id}##card"
|
card.tag = "${item!!.id}##card"
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
||||||
|
|
@ -98,7 +94,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
// Author ----------------------------------
|
// Author ----------------------------------
|
||||||
val author = card.findViewById<TextView>(R.id.author)
|
val author = card.findViewById<TextView>(R.id.author)
|
||||||
var info = item.author
|
var info = item.author
|
||||||
if (item.duration.isNotEmpty()) {
|
if (item.duration.isNotEmpty() && item.duration != "-1") {
|
||||||
if (item.author.isNotEmpty()) info += " • "
|
if (item.author.isNotEmpty()) info += " • "
|
||||||
info += item.duration
|
info += item.duration
|
||||||
}
|
}
|
||||||
|
|
@ -153,6 +149,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
val value = animation.animatedValue as Int
|
val value = animation.animatedValue as Int
|
||||||
pauseButton.cornerRadius = value
|
pauseButton.cornerRadius = value
|
||||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||||
|
pauseButton.contentDescription = activity.getString(R.string.pause)
|
||||||
pauseButton.isEnabled = true
|
pauseButton.isEnabled = true
|
||||||
pauseButton.tag = ActiveDownloadAction.Pause
|
pauseButton.tag = ActiveDownloadAction.Pause
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +168,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
val value = animation.animatedValue as Int
|
val value = animation.animatedValue as Int
|
||||||
pauseButton.cornerRadius = value
|
pauseButton.cornerRadius = value
|
||||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||||
|
pauseButton.contentDescription = activity.getString(R.string.start)
|
||||||
pauseButton.tag = ActiveDownloadAction.Resume
|
pauseButton.tag = ActiveDownloadAction.Resume
|
||||||
pauseButton.isEnabled = true
|
pauseButton.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +178,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
DownloadRepository.Status.PausedReQueued -> {
|
DownloadRepository.Status.PausedReQueued -> {
|
||||||
progressBar.isIndeterminate = true
|
progressBar.isIndeterminate = true
|
||||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.ic_refresh)
|
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.ic_refresh)
|
||||||
|
pauseButton.contentDescription = activity.getString(R.string.please_wait)
|
||||||
pauseButton.tag = null
|
pauseButton.tag = null
|
||||||
pauseButton.isEnabled = false
|
pauseButton.isEnabled = false
|
||||||
cancelButton.visibility = View.GONE
|
cancelButton.visibility = View.GONE
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import java.lang.StringBuilder
|
|
||||||
|
|
||||||
class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadMinifiedAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadMinifiedAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||||
DIFF_CALLBACK
|
DIFF_CALLBACK
|
||||||
|
|
@ -122,11 +121,13 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
||||||
if (item.status == DownloadRepository.Status.ActivePaused.toString()){
|
if (item.status == DownloadRepository.Status.ActivePaused.toString()){
|
||||||
progressBar.isIndeterminate = false
|
progressBar.isIndeterminate = false
|
||||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||||
|
pauseButton.contentDescription = activity.getString(R.string.start)
|
||||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
|
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
|
||||||
cancelButton.visibility = View.VISIBLE
|
cancelButton.visibility = View.VISIBLE
|
||||||
}else{
|
}else{
|
||||||
progressBar.isIndeterminate = true
|
progressBar.isIndeterminate = true
|
||||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||||
|
pauseButton.contentDescription = activity.getString(R.string.pause)
|
||||||
cancelButton.visibility = View.GONE
|
cancelButton.visibility = View.GONE
|
||||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause
|
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
@ -13,16 +12,10 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<Pair<DownloadItem, Long?>, AlreadyExistsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<Pair<DownloadItem, Long?>, AlreadyExistsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||||
DIFF_CALLBACK
|
DIFF_CALLBACK
|
||||||
|
|
|
||||||
|
|
@ -130,12 +130,15 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
when(item.type) {
|
when(item.type) {
|
||||||
DownloadViewModel.Type.audio -> {
|
DownloadViewModel.Type.audio -> {
|
||||||
btn.setIconResource(R.drawable.ic_music)
|
btn.setIconResource(R.drawable.ic_music)
|
||||||
|
btn.contentDescription = activity.getString(R.string.audio)
|
||||||
}
|
}
|
||||||
DownloadViewModel.Type.video -> {
|
DownloadViewModel.Type.video -> {
|
||||||
btn.setIconResource(R.drawable.ic_video)
|
btn.setIconResource(R.drawable.ic_video)
|
||||||
|
btn.contentDescription = activity.getString(R.string.video)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
btn.setIconResource(R.drawable.ic_terminal)
|
btn.setIconResource(R.drawable.ic_terminal)
|
||||||
|
btn.contentDescription = activity.getString(R.string.command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,18 @@ class DownloadLogsAdapter(onItemClickListener: OnItemClickListener, activity: Ac
|
||||||
|
|
||||||
val downloadTypeIcon = card.findViewById<MaterialButton>(R.id.download_type)
|
val downloadTypeIcon = card.findViewById<MaterialButton>(R.id.download_type)
|
||||||
when(item.downloadType){
|
when(item.downloadType){
|
||||||
DownloadViewModel.Type.audio -> downloadTypeIcon.setIconResource(R.drawable.ic_music)
|
DownloadViewModel.Type.audio -> {
|
||||||
DownloadViewModel.Type.video -> downloadTypeIcon.setIconResource(R.drawable.ic_video)
|
downloadTypeIcon.setIconResource(R.drawable.ic_music)
|
||||||
DownloadViewModel.Type.command -> downloadTypeIcon.setIconResource(R.drawable.ic_terminal)
|
downloadTypeIcon.contentDescription = activity.getString(R.string.audio)
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.video -> {
|
||||||
|
downloadTypeIcon.setIconResource(R.drawable.ic_video)
|
||||||
|
downloadTypeIcon.contentDescription = activity.getString(R.string.video)
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.command -> {
|
||||||
|
downloadTypeIcon.setIconResource(R.drawable.ic_terminal)
|
||||||
|
downloadTypeIcon.contentDescription = activity.getString(R.string.command)
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,21 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
if (actionButton.hasOnClickListeners()) actionButton.setOnClickListener(null)
|
if (actionButton.hasOnClickListeners()) actionButton.setOnClickListener(null)
|
||||||
|
|
||||||
when(item.status){
|
when(item.status){
|
||||||
DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh)
|
DownloadRepository.Status.Cancelled.toString() -> {
|
||||||
DownloadRepository.Status.Saved.toString() -> actionButton.setIconResource(R.drawable.ic_downloads)
|
actionButton.setIconResource(R.drawable.ic_refresh)
|
||||||
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
|
actionButton.contentDescription = activity.getString(R.string.download)
|
||||||
|
}
|
||||||
|
DownloadRepository.Status.Saved.toString() -> {
|
||||||
|
actionButton.setIconResource(R.drawable.ic_downloads)
|
||||||
|
actionButton.contentDescription = activity.getString(R.string.download)
|
||||||
|
}
|
||||||
|
DownloadRepository.Status.Queued.toString() -> {
|
||||||
|
actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
|
||||||
|
actionButton.contentDescription = activity.getString(R.string.Remove)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
|
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
|
||||||
|
actionButton.contentDescription = activity.getString(R.string.logs)
|
||||||
if (item.logID == null){
|
if (item.logID == null){
|
||||||
actionButton.visibility = View.GONE
|
actionButton.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
||||||
author.text = item.author
|
author.text = item.author
|
||||||
|
|
||||||
val length = card.findViewById<TextView>(R.id.length)
|
val length = card.findViewById<TextView>(R.id.length)
|
||||||
length.text = if(item.downloadPath.size == 1) item.duration else "[${item.downloadPath.size}]"
|
length.text = if(item.downloadPath.size == 1) item.duration else ""
|
||||||
|
|
||||||
|
|
||||||
// TIME DOWNLOADED ----------------------------------
|
// TIME DOWNLOADED ----------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package com.deniscerri.ytdlnis.ui.adapter
|
package com.deniscerri.ytdlnis.ui.adapter
|
||||||
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -19,7 +17,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.util.Extensions
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
@ -72,7 +69,6 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
||||||
} else {
|
} else {
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
|
||||||
}else{
|
}else{
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
package com.deniscerri.ytdlnis.ui.adapter
|
package com.deniscerri.ytdlnis.ui.adapter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -25,7 +23,6 @@ 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.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.squareup.picasso.Picasso
|
|
||||||
import java.text.DateFormatSymbols
|
import java.text.DateFormatSymbols
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
@ -139,7 +136,6 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
info.isVisible = true
|
info.isVisible = true
|
||||||
checkMissing.isVisible = true
|
|
||||||
|
|
||||||
btn.setImageResource(R.drawable.ic_search)
|
btn.setImageResource(R.drawable.ic_search)
|
||||||
btn.setOnClickListener {
|
btn.setOnClickListener {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.ui.adapter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.compose.ui.util.fastJoinToString
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
|
@ -14,7 +13,6 @@ import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@ import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,6 @@ import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
|
||||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
|
|
@ -36,7 +34,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
//TODO MAKE CONFIGURE LIKE NORMAL SHEET
|
|
||||||
class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private val currentDownloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
|
class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private val currentDownloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
|
||||||
private lateinit var tabLayout: TabLayout
|
private lateinit var tabLayout: TabLayout
|
||||||
private lateinit var viewPager2: ViewPager2
|
private lateinit var viewPager2: ViewPager2
|
||||||
|
|
@ -77,6 +74,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
|
|
||||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||||
|
viewPager2.isUserInputEnabled = sharedPreferences.getBoolean("swipe_gestures_download_card", true)
|
||||||
|
|
||||||
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
|
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
|
||||||
isNestedScrollingEnabled = false
|
isNestedScrollingEnabled = false
|
||||||
|
|
@ -231,6 +229,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||||
|
f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
|
@ -23,7 +22,6 @@ import androidx.core.view.setPadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.util.Log
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
|
@ -37,14 +35,13 @@ import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -83,7 +80,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
downloadItem = withContext(Dispatchers.IO) {
|
downloadItem = withContext(Dispatchers.IO) {
|
||||||
if (currentDownloadItem != null){
|
if (currentDownloadItem != null){
|
||||||
|
|
@ -304,12 +300,22 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateDataClicked = {
|
cutDisabledClicked = {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
val isUpdatingData = ViewModelProvider(requireActivity())[ResultViewModel::class.java].updatingData.value
|
||||||
resultItem?.apply {
|
if(isUpdatingData){
|
||||||
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
val snack = Snackbar.make(view, context.getString(R.string.please_wait), Snackbar.LENGTH_SHORT)
|
||||||
rsVM.updateItemData(this)
|
snack.show()
|
||||||
|
}else{
|
||||||
|
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
|
||||||
|
snack.setAction(R.string.update){
|
||||||
|
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||||
|
resultItem?.apply {
|
||||||
|
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||||
|
rsVM.updateItemData(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
snack.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
extraCommandsClicked = {
|
extraCommandsClicked = {
|
||||||
|
|
@ -328,6 +334,16 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
||||||
}catch (e : Exception){
|
}catch (e : Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
fun updateSelectedAudioFormat(formatID: String){
|
||||||
|
resultItem?.formats?.find { it.format_id == formatID }?.apply {
|
||||||
|
downloadItem.format = this
|
||||||
|
val formatCard = requireView().findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||||
|
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, listOf())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
|
@ -19,7 +20,11 @@ import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.fragment.app.commitNow
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
|
@ -139,6 +144,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||||
updateItem = view.findViewById(R.id.update_item)
|
updateItem = view.findViewById(R.id.update_item)
|
||||||
|
viewPager2.isUserInputEnabled = sharedPreferences.getBoolean("swipe_gestures_download_card", true)
|
||||||
|
|
||||||
|
|
||||||
//loading shimmers
|
//loading shimmers
|
||||||
|
|
@ -307,12 +313,31 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
download.isEnabled = false
|
download.isEnabled = false
|
||||||
val item: DownloadItem = getDownloadItem()
|
val item: DownloadItem = getDownloadItem()
|
||||||
item.downloadStartTime = it.timeInMillis
|
item.downloadStartTime = it.timeInMillis
|
||||||
runBlocking {
|
if (item.videoPreferences.alsoDownloadAsAudio){
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
val itemsToQueue = mutableListOf<DownloadItem>()
|
||||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
itemsToQueue.add(item)
|
||||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
|
||||||
|
getAlsoAudioDownloadItem{ audioDownloadItem ->
|
||||||
|
audioDownloadItem.downloadStartTime = it.timeInMillis
|
||||||
|
itemsToQueue.add(audioDownloadItem)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(itemsToQueue)
|
||||||
|
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
||||||
|
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.downloadStartTime)
|
||||||
|
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
download!!.setOnClickListener {
|
download!!.setOnClickListener {
|
||||||
|
|
@ -324,10 +349,24 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
scheduleBtn.isEnabled = false
|
scheduleBtn.isEnabled = false
|
||||||
download.isEnabled = false
|
download.isEnabled = false
|
||||||
val item: DownloadItem = getDownloadItem()
|
val item: DownloadItem = getDownloadItem()
|
||||||
runBlocking {
|
if (item.videoPreferences.alsoDownloadAsAudio){
|
||||||
downloadViewModel.queueDownloads(listOf(item))
|
val itemsToQueue = mutableListOf<DownloadItem>()
|
||||||
|
itemsToQueue.add(item)
|
||||||
|
|
||||||
|
getAlsoAudioDownloadItem {
|
||||||
|
itemsToQueue.add(it)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(itemsToQueue)
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -563,7 +602,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
|
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem {
|
||||||
return when(selectedTabPosition){
|
return when(selectedTabPosition){
|
||||||
0 -> {
|
0 -> {
|
||||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
|
|
@ -580,6 +619,33 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getAlsoAudioDownloadItem(finished: (it: DownloadItem) -> Unit) {
|
||||||
|
try {
|
||||||
|
val ff = (fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment)
|
||||||
|
ff.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
|
||||||
|
finished(ff.downloadItem)
|
||||||
|
}catch (e: Exception){
|
||||||
|
val fragmentLifecycleCallback = object:
|
||||||
|
FragmentManager.FragmentLifecycleCallbacks() {
|
||||||
|
|
||||||
|
override fun onFragmentStarted(fm: FragmentManager, f: Fragment) {
|
||||||
|
fragmentManager?.unregisterFragmentLifecycleCallbacks(this)
|
||||||
|
val ff = (f as DownloadAudioFragment)
|
||||||
|
ff.requireView().post {
|
||||||
|
ff.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
|
||||||
|
finished(ff.downloadItem)
|
||||||
|
}
|
||||||
|
super.onFragmentStarted(fm, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fragmentManager?.registerFragmentLifecycleCallbacks(fragmentLifecycleCallback, true)
|
||||||
|
viewPager2.setCurrentItem(0, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWhenSwitching(){
|
private fun updateWhenSwitching(){
|
||||||
val prevDownloadItem = getDownloadItem(
|
val prevDownloadItem = getDownloadItem(
|
||||||
if (viewPager2.currentItem == 1) 0 else 1
|
if (viewPager2.currentItem == 1) 0 else 1
|
||||||
|
|
@ -591,6 +657,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||||
|
f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,15 @@ import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard
|
import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard
|
||||||
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.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import android.content.res.Configuration
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -30,15 +28,10 @@ import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.work.Data
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
|
|
@ -48,8 +41,6 @@ import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
|
||||||
import com.google.android.material.bottomappbar.BottomAppBar
|
import com.google.android.material.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
|
@ -63,7 +54,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
@ -80,6 +70,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
private lateinit var behavior: BottomSheetBehavior<View>
|
private lateinit var behavior: BottomSheetBehavior<View>
|
||||||
private lateinit var bottomAppBar: BottomAppBar
|
private lateinit var bottomAppBar: BottomAppBar
|
||||||
private lateinit var filesize : TextView
|
private lateinit var filesize : TextView
|
||||||
|
private lateinit var count : TextView
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -135,6 +126,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
||||||
filesize = view.findViewById(R.id.filesize)
|
filesize = view.findViewById(R.id.filesize)
|
||||||
|
count = view.findViewById(R.id.count)
|
||||||
|
|
||||||
|
|
||||||
scheduleBtn.setOnClickListener{
|
scheduleBtn.setOnClickListener{
|
||||||
|
|
@ -406,7 +398,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
bottomSheet.dismiss()
|
bottomSheet.dismiss()
|
||||||
},
|
},
|
||||||
cutClicked = {},
|
cutClicked = {},
|
||||||
updateDataClicked = {},
|
cutDisabledClicked = {},
|
||||||
extraCommandsClicked = {
|
extraCommandsClicked = {
|
||||||
val callback = object : ExtraCommandsListener {
|
val callback = object : ExtraCommandsListener {
|
||||||
override fun onChangeExtraCommand(c: String) {
|
override fun onChangeExtraCommand(c: String) {
|
||||||
|
|
@ -476,7 +468,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
bottomSheet.dismiss()
|
bottomSheet.dismiss()
|
||||||
},
|
},
|
||||||
cutClicked = {},
|
cutClicked = {},
|
||||||
updateDataClicked = {},
|
cutDisabledClicked = {},
|
||||||
filenameTemplateSet = { checked ->
|
filenameTemplateSet = { checked ->
|
||||||
items.forEach { it.customFileNameTemplate = checked }
|
items.forEach { it.customFileNameTemplate = checked }
|
||||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||||
|
|
@ -497,6 +489,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
items.forEach { it.videoPreferences.removeAudio = checked }
|
items.forEach { it.videoPreferences.removeAudio = checked }
|
||||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||||
},
|
},
|
||||||
|
alsoDownloadAsAudioClicked = {},
|
||||||
extraCommandsClicked = {
|
extraCommandsClicked = {
|
||||||
val callback = object : ExtraCommandsListener {
|
val callback = object : ExtraCommandsListener {
|
||||||
override fun onChangeExtraCommand(c: String) {
|
override fun onChangeExtraCommand(c: String) {
|
||||||
|
|
@ -536,6 +529,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
downloadViewModel.processingDownloads.collectLatest {
|
downloadViewModel.processingDownloads.collectLatest {
|
||||||
|
count.text = "${it.size} ${getString(R.string.selected)}"
|
||||||
listAdapter.submitList(it)
|
listAdapter.submitList(it)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
updateFileSize(it.map { it2 -> it2.format.filesize })
|
updateFileSize(it.map { it2 -> it2.format.filesize })
|
||||||
|
|
@ -575,7 +569,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFileSize(items: List<Long>){
|
private fun updateFileSize(items: List<Long>){
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
|
import com.google.android.material.textfield.TextInputLayout.END_ICON_CUSTOM
|
||||||
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
import com.google.android.material.textfield.TextInputLayout.END_ICON_NONE
|
||||||
|
|
@ -322,12 +323,22 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateDataClicked = {
|
cutDisabledClicked = {
|
||||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
val isUpdatingData = ViewModelProvider(requireActivity())[ResultViewModel::class.java].updatingData.value
|
||||||
resultItem?.apply {
|
if(isUpdatingData){
|
||||||
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
val snack = Snackbar.make(view, context.getString(R.string.please_wait), Snackbar.LENGTH_SHORT)
|
||||||
rsVM.updateItemData(this)
|
snack.show()
|
||||||
|
}else{
|
||||||
|
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
|
||||||
|
snack.setAction(R.string.update){
|
||||||
|
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||||
|
resultItem?.apply {
|
||||||
|
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||||
|
rsVM.updateItemData(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
snack.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filenameTemplateSet = {
|
filenameTemplateSet = {
|
||||||
|
|
@ -346,6 +357,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
||||||
downloadItem.videoPreferences.removeAudio = it
|
downloadItem.videoPreferences.removeAudio = it
|
||||||
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, if (it) listOf() else downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
|
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, if (it) listOf() else downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
|
||||||
},
|
},
|
||||||
|
alsoDownloadAsAudioClicked = {
|
||||||
|
downloadItem.videoPreferences.alsoDownloadAsAudio = it
|
||||||
|
},
|
||||||
extraCommandsClicked = {
|
extraCommandsClicked = {
|
||||||
val callback = object : ExtraCommandsListener {
|
val callback = object : ExtraCommandsListener {
|
||||||
override fun onChangeExtraCommand(c: String) {
|
override fun onChangeExtraCommand(c: String) {
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,8 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
|
|
@ -30,8 +28,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
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.ChipGroup
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import android.content.SharedPreferences
|
||||||
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.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -16,23 +14,20 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.Button
|
import android.widget.LinearLayout
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.compose.ui.text.capitalize
|
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.addTextChangedListener
|
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
|
|
@ -49,7 +44,6 @@ import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.checkbox.MaterialCheckBox
|
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.materialswitch.MaterialSwitch
|
import com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
|
@ -58,12 +52,9 @@ import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.w3c.dom.Text
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.time.format.TextStyle
|
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
@ -102,6 +93,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
private lateinit var endsOnTime: TextInputLayout
|
private lateinit var endsOnTime: TextInputLayout
|
||||||
private lateinit var endsAfterNr: TextInputLayout
|
private lateinit var endsAfterNr: TextInputLayout
|
||||||
private lateinit var retryMissingDownloads: MaterialSwitch
|
private lateinit var retryMissingDownloads: MaterialSwitch
|
||||||
|
private lateinit var getOnlyNewUploads: MaterialSwitch
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -114,13 +106,13 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
infoUtil = InfoUtil(requireContext())
|
infoUtil = InfoUtil(requireContext())
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
type = arguments?.getSerializable("type") as Type
|
|
||||||
currentItem = if (Build.VERSION.SDK_INT >= 33){
|
currentItem = if (Build.VERSION.SDK_INT >= 33){
|
||||||
arguments?.getParcelable("item", ObserveSourcesItem::class.java)
|
arguments?.getParcelable("item", ObserveSourcesItem::class.java)
|
||||||
}else{
|
}else{
|
||||||
arguments?.getParcelable<ObserveSourcesItem>("item")
|
arguments?.getParcelable<ObserveSourcesItem>("item")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type = currentItem?.downloadItemTemplate?.type ?: arguments?.getSerializable("type") as Type
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
|
@ -259,6 +251,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
endsAfter = view.findViewById(R.id.after)
|
endsAfter = view.findViewById(R.id.after)
|
||||||
endsAfterNr = view.findViewById(R.id.after_nr)
|
endsAfterNr = view.findViewById(R.id.after_nr)
|
||||||
retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads)
|
retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads)
|
||||||
|
getOnlyNewUploads = view.findViewById(R.id.get_new_uploads)
|
||||||
okButton = view.findViewById(R.id.okButton)
|
okButton = view.findViewById(R.id.okButton)
|
||||||
|
|
||||||
title.editText!!.setText(currentItem?.name ?: "")
|
title.editText!!.setText(currentItem?.name ?: "")
|
||||||
|
|
@ -451,6 +444,9 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
endsAfter.performClick()
|
endsAfter.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
retryMissingDownloads.isChecked = currentItem?.retryMissingDownloads ?: false
|
||||||
|
getOnlyNewUploads.isChecked = currentItem?.getOnlyNewUploads ?: false
|
||||||
|
|
||||||
if (currentItem != null) okButton.text = getString(R.string.update)
|
if (currentItem != null) okButton.text = getString(R.string.update)
|
||||||
okButton.setOnClickListener {
|
okButton.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
@ -483,6 +479,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
||||||
ends,
|
ends,
|
||||||
endsAfterCount,
|
endsAfterCount,
|
||||||
0,
|
0,
|
||||||
|
getOnlyNewUploads.isChecked,
|
||||||
retryMissingDownloads.isChecked,
|
retryMissingDownloads.isChecked,
|
||||||
mutableListOf()
|
mutableListOf()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.widget.Button
|
||||||
import android.widget.*
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.map
|
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
|
@ -39,13 +39,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadMinifiedAdapter
|
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadMinifiedAdapter
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.setFullScreen
|
import com.deniscerri.ytdlnis.util.Extensions.setFullScreen
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
|
@ -71,7 +71,8 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdapter.OnItemClickListener, ActiveDownloadMinifiedAdapter.OnItemClickListener {
|
class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdapter.OnItemClickListener, ActiveDownloadMinifiedAdapter.OnItemClickListener {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
import android.app.ActionBar.LayoutParams
|
import android.app.ActionBar.LayoutParams
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -19,13 +18,11 @@ import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.PlaylistAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareActivity
|
import com.deniscerri.ytdlnis.ui.adapter.PlaylistAdapter
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,12 @@ import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import androidx.work.WorkQuery
|
import androidx.work.WorkQuery
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloads
|
package com.deniscerri.ytdlnis.ui.downloads
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadAudioFragment
|
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadCommandFragment
|
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadVideoFragment
|
|
||||||
|
|
||||||
class DownloadListFragmentAdapter (
|
class DownloadListFragmentAdapter (
|
||||||
fragmentManager : FragmentManager,
|
fragmentManager : FragmentManager,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
package com.deniscerri.ytdlnis.ui.downloads
|
package com.deniscerri.ytdlnis.ui.downloads
|
||||||
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.icu.text.Transliterator.Position
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.view.get
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.navigation.ui.setupWithNavController
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
|
@ -20,14 +20,17 @@ import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.createBadge
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
import com.google.android.material.badge.BadgeDrawable
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
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.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
|
@ -40,6 +43,7 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
private lateinit var viewPager2: ViewPager2
|
private lateinit var viewPager2: ViewPager2
|
||||||
private lateinit var fragmentAdapter : DownloadListFragmentAdapter
|
private lateinit var fragmentAdapter : DownloadListFragmentAdapter
|
||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
|
@ -47,13 +51,14 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
mainActivity = activity as MainActivity
|
mainActivity = activity as MainActivity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
return inflater.inflate(R.layout.fragment_download_queue_main_screen, container, false)
|
return inflater.inflate(R.layout.fragment_download_queue_main_screen, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
workManager = WorkManager.getInstance(requireContext())
|
workManager = WorkManager.getInstance(requireContext())
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||||
|
|
||||||
topAppBar = view.findViewById(R.id.logs_toolbar)
|
topAppBar = view.findViewById(R.id.logs_toolbar)
|
||||||
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
||||||
|
|
@ -113,6 +118,50 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
viewPager2.setCurrentItem(3, false)
|
viewPager2.setCurrentItem(3, false)
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean("show_count_downloads", false)){
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||||
|
tabLayout.getTabAt(0)?.apply {
|
||||||
|
if (it == 0) removeBadge()
|
||||||
|
else createBadge(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.queuedDownloadsCount.collectLatest {
|
||||||
|
tabLayout.getTabAt(1)?.apply {
|
||||||
|
if (it == 0) removeBadge()
|
||||||
|
else createBadge(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.cancelledDownloadsCount.collectLatest {
|
||||||
|
tabLayout.getTabAt(2)?.apply {
|
||||||
|
if (it == 0) removeBadge()
|
||||||
|
else createBadge(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.erroredDownloadsCount.collectLatest {
|
||||||
|
tabLayout.getTabAt(3)?.apply {
|
||||||
|
if (it == 0) removeBadge()
|
||||||
|
else createBadge(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
downloadViewModel.savedDownloadsCount.collectLatest {
|
||||||
|
tabLayout.getTabAt(4)?.apply {
|
||||||
|
if (it == 0) removeBadge()
|
||||||
|
else createBadge(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMenu() {
|
private fun initMenu() {
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.compose.ui.text.capitalize
|
|
||||||
import androidx.compose.ui.text.intl.Locale
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.forEach
|
import androidx.core.view.forEach
|
||||||
|
|
@ -43,16 +41,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.HistoryAdapter
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
|
import com.deniscerri.ytdlnis.ui.adapter.HistoryAdapter
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,15 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
@ -201,7 +200,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems, adapter.inverted, now)
|
downloadViewModel.checkAllQueuedItemsAreScheduledAfterNow(adapter.checkedItems, adapter.inverted, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
actionMode!!.menu.getItem(1).isVisible = selectedObjects == 1 && position > 0
|
actionMode!!.menu.getItem(1).isVisible = position > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -314,22 +313,20 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
}
|
}
|
||||||
R.id.up -> {
|
R.id.up -> {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
if (adapter.getSelectedObjectsCount(totalSize) == 1){
|
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
||||||
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
|
|
||||||
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
adapter.checkedItems.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter.clearCheckedItems()
|
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.putAtTopOfQueue(selectedObjects[0])
|
downloadViewModel.getItemIDsNotPresentIn(adapter.checkedItems, listOf(
|
||||||
|
DownloadRepository.Status.Queued, DownloadRepository.Status.QueuedPaused))
|
||||||
}
|
}
|
||||||
actionMode?.finish()
|
}else{
|
||||||
|
adapter.checkedItems.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adapter.clearCheckedItems()
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.putAtTopOfQueue(selectedObjects)
|
||||||
|
}
|
||||||
|
actionMode?.finish()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,12 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.TemplatesAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
|
||||||
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType
|
|
||||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||||
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.CommandTemplateRepository.CommandTemplateSortType
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.TemplatesAdapter
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more
|
package com.deniscerri.ytdlnis.ui.more
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context.INPUT_METHOD_SERVICE
|
import android.content.Context.INPUT_METHOD_SERVICE
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -63,6 +64,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
return inflater.inflate(R.layout.fragment_cookies, container, false)
|
return inflater.inflate(R.layout.fragment_cookies, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
topAppBar = view.findViewById(R.id.logs_toolbar)
|
topAppBar = view.findViewById(R.id.logs_toolbar)
|
||||||
|
|
@ -164,7 +166,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
|
|
||||||
private fun showDialog(url: String){
|
private fun showDialog(url: String){
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
var item = withContext(Dispatchers.IO){
|
val item = withContext(Dispatchers.IO){
|
||||||
cookiesViewModel.getByURL(url)
|
cookiesViewModel.getByURL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,11 +180,15 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
||||||
editText.setText(text)
|
editText.setText(text)
|
||||||
editText.setSelection(editText.text.length)
|
editText.setSelection(editText.text.length)
|
||||||
builder.setView(inputLayout)
|
builder.setView(inputLayout)
|
||||||
builder.setNeutralButton(
|
|
||||||
getString(android.R.string.copy)
|
item?.apply {
|
||||||
) { dialog: DialogInterface?, which: Int ->
|
builder.setNeutralButton(
|
||||||
UiUtil.copyToClipboard(item.content, requireActivity())
|
getString(android.R.string.copy)
|
||||||
|
) { dialog: DialogInterface?, which: Int ->
|
||||||
|
UiUtil.copyToClipboard(item.content, requireActivity())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(
|
builder.setPositiveButton(
|
||||||
getString(R.string.get_cookies)
|
getString(R.string.get_cookies)
|
||||||
) { dialog: DialogInterface?, which: Int ->
|
) { dialog: DialogInterface?, which: Int ->
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,24 @@ import android.annotation.SuppressLint
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
import androidx.work.WorkInfo
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkQuery
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
|
|
@ -33,8 +34,8 @@ import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -73,13 +74,8 @@ class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickList
|
||||||
)
|
)
|
||||||
val newSource = view.findViewById<Chip>(R.id.newSource)
|
val newSource = view.findViewById<Chip>(R.id.newSource)
|
||||||
recyclerView = view.findViewById(R.id.source_recyclerView)
|
recyclerView = view.findViewById(R.id.source_recyclerView)
|
||||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
recyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
recyclerView.adapter = listAdapter
|
recyclerView.adapter = listAdapter
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
|
||||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("cookies")){
|
|
||||||
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
|
||||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
|
||||||
}
|
|
||||||
|
|
||||||
observeSourcesViewModel = ViewModelProvider(this)[ObserveSourcesViewModel::class.java]
|
observeSourcesViewModel = ViewModelProvider(this)[ObserveSourcesViewModel::class.java]
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
|
|
@ -163,63 +159,4 @@ class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickList
|
||||||
observeSourcesViewModel.delete(item)
|
observeSourcesViewModel.delete(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
|
||||||
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT ) {
|
|
||||||
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
|
||||||
): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
|
||||||
val position = viewHolder.bindingAdapterPosition
|
|
||||||
val url = viewHolder.itemView.tag.toString()
|
|
||||||
when (direction) {
|
|
||||||
ItemTouchHelper.LEFT -> {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val deletedItem = withContext(Dispatchers.IO){
|
|
||||||
observeSourcesViewModel.getByURL(url)
|
|
||||||
}
|
|
||||||
listAdapter.notifyItemChanged(position)
|
|
||||||
onDelete(deletedItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChildDraw(
|
|
||||||
c: Canvas,
|
|
||||||
recyclerView: RecyclerView,
|
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
|
||||||
dX: Float,
|
|
||||||
dY: Float,
|
|
||||||
actionState: Int,
|
|
||||||
isCurrentlyActive: Boolean
|
|
||||||
) {
|
|
||||||
RecyclerViewSwipeDecorator.Builder(
|
|
||||||
context,
|
|
||||||
c,
|
|
||||||
recyclerView,
|
|
||||||
viewHolder,
|
|
||||||
dX,
|
|
||||||
dY,
|
|
||||||
actionState,
|
|
||||||
isCurrentlyActive
|
|
||||||
)
|
|
||||||
.addSwipeLeftBackgroundColor(Color.RED)
|
|
||||||
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
|
|
||||||
.create()
|
|
||||||
.decorate()
|
|
||||||
super.onChildDraw(
|
|
||||||
c,
|
|
||||||
recyclerView,
|
|
||||||
viewHolder,
|
|
||||||
dX,
|
|
||||||
dY,
|
|
||||||
actionState,
|
|
||||||
isCurrentlyActive
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import com.google.accompanist.web.AccompanistWebChromeClient
|
||||||
import com.google.accompanist.web.AccompanistWebViewClient
|
import com.google.accompanist.web.AccompanistWebViewClient
|
||||||
import com.google.accompanist.web.WebView
|
import com.google.accompanist.web.WebView
|
||||||
import com.google.accompanist.web.rememberWebViewState
|
import com.google.accompanist.web.rememberWebViewState
|
||||||
|
import com.google.android.exoplayer2.util.Log
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
@ -57,48 +58,58 @@ class WebViewActivity : BaseActivity() {
|
||||||
cookieManager = CookieManager.getInstance()
|
cookieManager = CookieManager.getInstance()
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
webViewClient = object : AccompanistWebViewClient() {
|
lifecycleScope.launch{
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
webViewClient = object : AccompanistWebViewClient() {
|
||||||
super.onPageFinished(view, url)
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
kotlin.runCatching {
|
super.onPageFinished(view, url)
|
||||||
toolbar.title = view?.title ?: ""
|
kotlin.runCatching {
|
||||||
cookies = CookieManager.getInstance().getCookie(view?.url)
|
toolbar.title = view?.title ?: ""
|
||||||
}
|
cookies = CookieManager.getInstance().getCookie(view?.url)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieManager = CookieManager.getInstance()
|
|
||||||
|
|
||||||
toolbar.setNavigationOnClickListener {
|
|
||||||
cookieManager.flush()
|
|
||||||
onBackPressedDispatcher.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
generateBtn.setOnClickListener {
|
|
||||||
cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
|
|
||||||
kotlin.runCatching {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
cookiesViewModel.insert(
|
|
||||||
CookieItem(
|
|
||||||
0,
|
|
||||||
url,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
cookiesViewModel.updateCookiesFile()
|
|
||||||
}
|
}
|
||||||
}.onFailure {
|
|
||||||
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onBackPressedDispatcher.onBackPressed()
|
|
||||||
|
cookieManager = CookieManager.getInstance()
|
||||||
|
|
||||||
|
toolbar.setNavigationOnClickListener {
|
||||||
|
cookieManager.flush()
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
generateBtn.setOnClickListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
val a = cookieManager.getCookie(url).replace(";", "\n")
|
||||||
|
Log.e("TESTING", a)
|
||||||
|
cookiesViewModel.getCookiesFromDB(url).getOrNull()?.let {
|
||||||
|
kotlin.runCatching {
|
||||||
|
cookiesViewModel.insert(
|
||||||
|
CookieItem(
|
||||||
|
0,
|
||||||
|
url,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cookiesViewModel.updateCookiesFile()
|
||||||
|
}.onFailure {
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
Toast.makeText(this@WebViewActivity, "Something went wrong", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main){
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webViewCompose.apply {
|
||||||
|
setContent { WebViewView() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
webViewCompose.apply {
|
|
||||||
setContent { WebViewView() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import android.widget.LinearLayout
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.core.view.get
|
import androidx.core.view.get
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
|
@ -168,10 +169,12 @@ class DownloadLogFragment : Fragment() {
|
||||||
requireActivity().runOnUiThread{
|
requireActivity().runOnUiThread{
|
||||||
if (logItem != null){
|
if (logItem != null){
|
||||||
if (logItem.content.isNotBlank()) {
|
if (logItem.content.isNotBlank()) {
|
||||||
content.text = logItem.content
|
content.setText(logItem.content, TextView.BufferType.SPANNABLE)
|
||||||
|
}
|
||||||
|
if (!bottomAppBar.menu.children.first { it.itemId == R.id.scroll_down }.isVisible){
|
||||||
|
content.scrollTo(0, content.height)
|
||||||
|
contentScrollView.fullScroll(View.FOCUS_DOWN)
|
||||||
}
|
}
|
||||||
content.scrollTo(0, content.height)
|
|
||||||
contentScrollView.fullScroll(View.FOCUS_DOWN)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.DownloadLogsAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.DownloadLogsAdapter
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.preference.EditTextPreference
|
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,15 @@ import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
import com.deniscerri.ytdlnis.database.models.TemplateShortcut
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.ObserveSourcesViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
|
@ -70,6 +71,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
private lateinit var downloadViewModel: DownloadViewModel
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
private lateinit var cookieViewModel: CookieViewModel
|
private lateinit var cookieViewModel: CookieViewModel
|
||||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||||
|
private lateinit var observeSourcesViewModel: ObserveSourcesViewModel
|
||||||
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
|
@ -127,6 +129,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||||
|
observeSourcesViewModel = ViewModelProvider(this)[ObserveSourcesViewModel::class.java]
|
||||||
|
|
||||||
backup = findPreference("backup")
|
backup = findPreference("backup")
|
||||||
restore = findPreference("restore")
|
restore = findPreference("restore")
|
||||||
|
|
@ -175,6 +178,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
"templates" -> json.add("templates", backupCommandTemplates() )
|
"templates" -> json.add("templates", backupCommandTemplates() )
|
||||||
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
||||||
"searchHistory" -> json.add("search_history", backupSearchHistory() )
|
"searchHistory" -> json.add("search_history", backupSearchHistory() )
|
||||||
|
"observeSources" -> json.add("observe_sources", backupObserveSources() )
|
||||||
}
|
}
|
||||||
}.onFailure {err ->
|
}.onFailure {err ->
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
|
|
@ -374,6 +378,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
return JsonArray()
|
return JsonArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun backupObserveSources() : JsonArray {
|
||||||
|
runCatching {
|
||||||
|
val observeSourcesItems = withContext(Dispatchers.IO) {
|
||||||
|
observeSourcesViewModel.getAll()
|
||||||
|
}
|
||||||
|
val arr = JsonArray()
|
||||||
|
observeSourcesItems.forEach {
|
||||||
|
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
return JsonArray()
|
||||||
|
}
|
||||||
|
|
||||||
private var appRestoreResultLauncher = registerForActivityResult(
|
private var appRestoreResultLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result ->
|
) { result ->
|
||||||
|
|
@ -400,204 +418,263 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val json = Gson().fromJson(total.toString(), JsonObject::class.java)
|
val json = Gson().fromJson(total.toString(), JsonObject::class.java)
|
||||||
val finalMessage = StringBuilder("${getString(R.string.restore_complete)}\n")
|
val finalMessage = StringBuilder("")
|
||||||
|
val errorMessage = StringBuilder("")
|
||||||
|
|
||||||
//preference restore
|
//preference restore
|
||||||
if(json.has("settings")){
|
kotlin.runCatching {
|
||||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
if(json.has("settings")){
|
||||||
clear()
|
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
||||||
val prefs = json.getAsJsonArray("settings")
|
clear()
|
||||||
prefs.forEach {
|
val prefs = json.getAsJsonArray("settings")
|
||||||
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
prefs.forEach {
|
||||||
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
||||||
"String" -> {
|
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
||||||
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
"String" -> {
|
||||||
putString(key, value)
|
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
||||||
}
|
putString(key, value)
|
||||||
"Boolean" -> {
|
}
|
||||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
"Boolean" -> {
|
||||||
Log.e("REST", value.toString())
|
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
||||||
Log.e("REST", key)
|
Log.e("REST", value.toString())
|
||||||
putBoolean(key, value)
|
Log.e("REST", key)
|
||||||
}
|
putBoolean(key, value)
|
||||||
"Int" -> {
|
}
|
||||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
"Int" -> {
|
||||||
putInt(key, value)
|
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
||||||
}
|
putInt(key, value)
|
||||||
"HashSet" -> {
|
}
|
||||||
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
"HashSet" -> {
|
||||||
putStringSet(key, value.toHashSet())
|
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
||||||
|
putStringSet(key, value.toHashSet())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finalMessage.append("${getString(R.string.settings)}: ${prefs.count()}\n")
|
||||||
}
|
}
|
||||||
finalMessage.append("${getString(R.string.settings)}: ${prefs.count()}\n")
|
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
|
errorMessage.append("\n PREFERENCE RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
//history restore
|
//history restore
|
||||||
if(json.has("downloads")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("downloads")
|
if(json.has("downloads")){
|
||||||
items.forEach {
|
val items = json.getAsJsonArray("downloads")
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java)
|
||||||
withContext(Dispatchers.IO){
|
item.id = 0L
|
||||||
historyViewModel.insert(item)
|
withContext(Dispatchers.IO){
|
||||||
|
historyViewModel.insert(item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
finalMessage.append("${getString(R.string.downloads)}: ${items.count()}\n")
|
||||||
}
|
}
|
||||||
finalMessage.append("${getString(R.string.downloads)}: ${items.count()}\n")
|
}.onFailure {
|
||||||
|
errorMessage.append("\n HISTORY RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
//queued downloads restore
|
//queued downloads restore
|
||||||
if(json.has("queued")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("queued")
|
if(json.has("queued")){
|
||||||
val queued = mutableListOf<DownloadItem>()
|
val items = json.getAsJsonArray("queued")
|
||||||
items.forEach {
|
val queued = mutableListOf<DownloadItem>()
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||||
queued.add(item)
|
item.id = 0L
|
||||||
}
|
queued.add(item)
|
||||||
if (queued.isNotEmpty()) {
|
}
|
||||||
finalMessage.append("${getString(R.string.in_queue)}: ${queued.count()}\n")
|
if (queued.isNotEmpty()) {
|
||||||
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(queued)}
|
finalMessage.append("${getString(R.string.in_queue)}: ${queued.count()}\n")
|
||||||
|
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(queued)}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
|
errorMessage.append("\n QUEUED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
//cancelled downloads restore
|
//cancelled downloads restore
|
||||||
if(json.has("cancelled")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("cancelled")
|
if(json.has("cancelled")){
|
||||||
val cancelled = mutableListOf<DownloadItem>()
|
val items = json.getAsJsonArray("cancelled")
|
||||||
items.forEach {
|
val cancelled = mutableListOf<DownloadItem>()
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||||
cancelled.add(item)
|
item.id = 0L
|
||||||
|
cancelled.add(item)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelled.asReversed().forEach { f ->
|
cancelled.asReversed().forEach { f ->
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.insert(f)
|
downloadViewModel.insert(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cancelled.isNotEmpty()){
|
||||||
|
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
if(cancelled.isNotEmpty()){
|
errorMessage.append("\n CANCELLED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||||
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//erorred downloads restore
|
//erorred downloads restore
|
||||||
if(json.has("errored")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("errored")
|
if(json.has("errored")){
|
||||||
val errored = mutableListOf<DownloadItem>()
|
val items = json.getAsJsonArray("errored")
|
||||||
items.forEach {
|
val errored = mutableListOf<DownloadItem>()
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||||
errored.add(item)
|
item.id = 0L
|
||||||
|
errored.add(item)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errored.asReversed().forEach { f ->
|
errored.asReversed().forEach { f ->
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.insert(f)
|
downloadViewModel.insert(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(errored.isNotEmpty()){
|
||||||
|
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
if(errored.isNotEmpty()){
|
errorMessage.append("\n ERRORED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||||
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//erorred downloads restore
|
//saved downloads restore
|
||||||
if(json.has("saved")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("saved")
|
if(json.has("saved")){
|
||||||
val saved = mutableListOf<DownloadItem>()
|
val items = json.getAsJsonArray("saved")
|
||||||
items.forEach {
|
val saved = mutableListOf<DownloadItem>()
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||||
saved.add(item)
|
item.id = 0L
|
||||||
|
saved.add(item)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saved.asReversed().forEach { f ->
|
saved.asReversed().forEach { f ->
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
downloadViewModel.insert(f)
|
downloadViewModel.insert(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(saved.isNotEmpty()){
|
||||||
|
finalMessage.append("${getString(R.string.saved)}: ${saved.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
if(saved.isNotEmpty()){
|
errorMessage.append("\n SAVED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||||
finalMessage.append("${getString(R.string.saved)}: ${saved.count()}\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//cookies restore
|
//cookies restore
|
||||||
if(json.has("cookies")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("cookies")
|
if(json.has("cookies")){
|
||||||
items.forEach {
|
val items = json.getAsJsonArray("cookies")
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java)
|
||||||
withContext(Dispatchers.IO){
|
item.id = 0L
|
||||||
cookieViewModel.insert(item)
|
withContext(Dispatchers.IO){
|
||||||
|
cookieViewModel.insert(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(items.count() > 0){
|
||||||
|
finalMessage.append("${getString(R.string.cookies)}: ${items.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(items.count() > 0){
|
}.onFailure {
|
||||||
finalMessage.append("${getString(R.string.cookies)}: ${items.count()}\n")
|
errorMessage.append("\n COOKIES RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//command template restore
|
//command template restore
|
||||||
if(json.has("templates")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("templates")
|
if(json.has("templates")){
|
||||||
val templates = mutableListOf<CommandTemplate>()
|
val items = json.getAsJsonArray("templates")
|
||||||
items.forEach {
|
val templates = mutableListOf<CommandTemplate>()
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
|
||||||
templates.add(item)
|
item.id = 0L
|
||||||
|
templates.add(item)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
templates.asReversed().forEach { t ->
|
templates.asReversed().forEach { t ->
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
commandTemplateViewModel.insert(t)
|
commandTemplateViewModel.insert(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(items.count() > 0){
|
||||||
|
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
if(items.count() > 0){
|
errorMessage.append("\n COMMAND TEMPLATE RESTORE FAILED: \n ${it.message}")
|
||||||
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//shortcuts restore
|
//shortcuts restore
|
||||||
if(json.has("shortcuts")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("shortcuts")
|
if(json.has("shortcuts")){
|
||||||
items.forEach {
|
val items = json.getAsJsonArray("shortcuts")
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), TemplateShortcut::class.java)
|
items.forEach {
|
||||||
item.id = 0L
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), TemplateShortcut::class.java)
|
||||||
withContext(Dispatchers.IO){
|
item.id = 0L
|
||||||
commandTemplateViewModel.insertShortcut(item)
|
withContext(Dispatchers.IO){
|
||||||
|
commandTemplateViewModel.insertShortcut(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(items.count() > 0){
|
||||||
|
finalMessage.append("${getString(R.string.shortcuts)}: ${items.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(items.count() > 0){
|
}.onFailure {
|
||||||
finalMessage.append("${getString(R.string.shortcuts)}: ${items.count()}\n")
|
errorMessage.append("\n COMMAND SHORTCUTS RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//search history restore
|
//search history restore
|
||||||
if(json.has("search_history")){
|
kotlin.runCatching {
|
||||||
val items = json.getAsJsonArray("search_history")
|
if(json.has("search_history")){
|
||||||
items.forEach {
|
val items = json.getAsJsonArray("search_history")
|
||||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), SearchHistoryItem::class.java)
|
items.forEach {
|
||||||
withContext(Dispatchers.IO){
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), SearchHistoryItem::class.java)
|
||||||
resultViewModel.addSearchQueryToHistory(item.query)
|
withContext(Dispatchers.IO){
|
||||||
|
resultViewModel.addSearchQueryToHistory(item.query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(items.count() > 0){
|
||||||
|
finalMessage.append("${getString(R.string.search_history)}: ${items.count()}\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(items.count() > 0){
|
}.onFailure {
|
||||||
finalMessage.append("${getString(R.string.search_history)}: ${items.count()}\n")
|
errorMessage.append("\n SEARCH HISTORY RESTORE FAILED: \n ${it.message}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//observe sources restore
|
||||||
|
kotlin.runCatching {
|
||||||
|
if(json.has("observe_sources")){
|
||||||
|
val items = json.getAsJsonArray("observe_sources")
|
||||||
|
items.forEach {
|
||||||
|
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), ObserveSourcesItem::class.java)
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
item.id = 0L
|
||||||
|
observeSourcesViewModel.insert(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
errorMessage.append("\n SEARCH HISTORY RESTORE FAILED: \n ${it.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalMessage.isEmpty()) throw Exception("")
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
builder.setTitle(getString(R.string.restore))
|
builder.setTitle(getString(R.string.restore))
|
||||||
builder.setMessage(finalMessage)
|
builder.setMessage("${getString(R.string.restore_complete)}\n $finalMessage \n $errorMessage")
|
||||||
builder.setPositiveButton(
|
builder.setPositiveButton(
|
||||||
getString(R.string.restart)
|
getString(R.string.restart)
|
||||||
) { _: DialogInterface?, _: Int ->
|
) { _: DialogInterface?, _: Int ->
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
|
@ -21,6 +22,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
||||||
override val title: Int = R.string.updating
|
override val title: Int = R.string.updating
|
||||||
private var updateYTDL: Preference? = null
|
private var updateYTDL: Preference? = null
|
||||||
private var ytdlVersion: Preference? = null
|
private var ytdlVersion: Preference? = null
|
||||||
|
private var ytdlSource: Preference? = null
|
||||||
private var updateUtil: UpdateUtil? = null
|
private var updateUtil: UpdateUtil? = null
|
||||||
private var version: Preference? = null
|
private var version: Preference? = null
|
||||||
|
|
||||||
|
|
@ -34,52 +36,22 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
||||||
val editor = preferences.edit()
|
val editor = preferences.edit()
|
||||||
updateYTDL = findPreference("update_ytdl")
|
updateYTDL = findPreference("update_ytdl")
|
||||||
ytdlVersion = findPreference("ytdl-version")
|
ytdlVersion = findPreference("ytdl-version")
|
||||||
|
ytdlSource = findPreference("ytdlp_source")
|
||||||
|
|
||||||
YoutubeDL.getInstance().version(context)?.let {
|
YoutubeDL.getInstance().version(context)?.let {
|
||||||
editor.putString("ytdl-version", it)
|
editor.putString("ytdl-version", it)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
ytdlVersion!!.summary = it
|
ytdlVersion!!.summary = it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ytdlSource?.setOnPreferenceChangeListener { preference, newValue ->
|
||||||
|
initYTDLUpdate(editor)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
updateYTDL!!.onPreferenceClickListener =
|
updateYTDL!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
lifecycleScope.launch {
|
initYTDLUpdate(editor)
|
||||||
Snackbar.make(requireView(),
|
|
||||||
requireContext().getString(R.string.ytdl_updating_started),
|
|
||||||
Snackbar.LENGTH_LONG).show()
|
|
||||||
runCatching {
|
|
||||||
when (updateUtil!!.updateYoutubeDL()) {
|
|
||||||
YoutubeDL.UpdateStatus.DONE -> {
|
|
||||||
Snackbar.make(requireView(),
|
|
||||||
requireContext().getString(R.string.ytld_update_success),
|
|
||||||
Snackbar.LENGTH_LONG).show()
|
|
||||||
|
|
||||||
YoutubeDL.getInstance().version(context)?.let {
|
|
||||||
editor.putString("ytdl-version", it)
|
|
||||||
editor.apply()
|
|
||||||
ytdlVersion!!.summary = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(),
|
|
||||||
requireContext().getString(R.string.you_are_in_latest_version),
|
|
||||||
Snackbar.LENGTH_LONG).show()
|
|
||||||
else -> {
|
|
||||||
Snackbar.make(requireView(),
|
|
||||||
requireContext().getString(R.string.errored),
|
|
||||||
Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onFailure {
|
|
||||||
val msg = it.message ?: requireContext().getString(R.string.errored)
|
|
||||||
val snackBar = Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG)
|
|
||||||
snackBar.setAction(android.R.string.copy){
|
|
||||||
UiUtil.copyToClipboard(msg, requireActivity())
|
|
||||||
}
|
|
||||||
val snackbarView: View = snackBar.view
|
|
||||||
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
|
||||||
snackTextView.maxLines = 9999999
|
|
||||||
snackBar.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,5 +91,44 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initYTDLUpdate(editor: SharedPreferences.Editor) = lifecycleScope.launch {
|
||||||
|
Snackbar.make(requireView(),
|
||||||
|
requireContext().getString(R.string.ytdl_updating_started),
|
||||||
|
Snackbar.LENGTH_LONG).show()
|
||||||
|
runCatching {
|
||||||
|
when (updateUtil!!.updateYoutubeDL()) {
|
||||||
|
YoutubeDL.UpdateStatus.DONE -> {
|
||||||
|
Snackbar.make(requireView(),
|
||||||
|
requireContext().getString(R.string.ytld_update_success),
|
||||||
|
Snackbar.LENGTH_LONG).show()
|
||||||
|
|
||||||
|
YoutubeDL.getInstance().version(context)?.let {
|
||||||
|
editor.putString("ytdl-version", it)
|
||||||
|
editor.apply()
|
||||||
|
ytdlVersion!!.summary = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
YoutubeDL.UpdateStatus.ALREADY_UP_TO_DATE -> Snackbar.make(requireView(),
|
||||||
|
requireContext().getString(R.string.you_are_in_latest_version),
|
||||||
|
Snackbar.LENGTH_LONG).show()
|
||||||
|
else -> {
|
||||||
|
Snackbar.make(requireView(),
|
||||||
|
requireContext().getString(R.string.errored),
|
||||||
|
Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
val msg = it.message ?: requireContext().getString(R.string.errored)
|
||||||
|
val snackBar = Snackbar.make(requireView(), msg, Snackbar.LENGTH_LONG)
|
||||||
|
snackBar.setAction(R.string.copy_log){
|
||||||
|
UiUtil.copyToClipboard(msg, requireActivity())
|
||||||
|
}
|
||||||
|
val snackbarView: View = snackBar.view
|
||||||
|
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
|
||||||
|
snackTextView.maxLines = 9999999
|
||||||
|
snackBar.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -17,9 +17,9 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.TerminalDownloadsAdapter
|
|
||||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.adapter.TerminalDownloadsAdapter
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,11 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||||
|
import com.deniscerri.ytdlnis.util.Extensions.setCustomTextSize
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UiUtil
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.setCustomTextSize
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.google.android.material.bottomappbar.BottomAppBar
|
import com.google.android.material.bottomappbar.BottomAppBar
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
|
@ -228,6 +228,8 @@ class TerminalFragment : Fragment() {
|
||||||
input!!.requestFocus()
|
input!!.requestFocus()
|
||||||
input!!.setSelection(input!!.text.length)
|
input!!.setSelection(input!!.text.length)
|
||||||
output?.text = bundle?.getString("output") ?: output?.text
|
output?.text = bundle?.getString("output") ?: output?.text
|
||||||
|
output?.isVisible = output?.text.toString().isNotEmpty()
|
||||||
|
|
||||||
if (bundle?.getBoolean("run") == true){
|
if (bundle?.getBoolean("run") == true){
|
||||||
showCancelFab()
|
showCancelFab()
|
||||||
}
|
}
|
||||||
|
|
@ -326,6 +328,7 @@ class TerminalFragment : Fragment() {
|
||||||
requireActivity().runOnUiThread{
|
requireActivity().runOnUiThread{
|
||||||
if (it != null){
|
if (it != null){
|
||||||
if (!it.log.isNullOrBlank()) {
|
if (!it.log.isNullOrBlank()) {
|
||||||
|
output?.isVisible = true
|
||||||
output?.text = it.log
|
output?.text = it.log
|
||||||
}
|
}
|
||||||
output?.scrollTo(0, output!!.height)
|
output?.scrollTo(0, output!!.height)
|
||||||
|
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
package com.deniscerri.ytdlnis.util
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.AlarmManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.os.Looper
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.work.Constraints
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
|
||||||
import androidx.work.NetworkType
|
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
|
||||||
import androidx.work.WorkInfo
|
|
||||||
import androidx.work.WorkManager
|
|
||||||
import androidx.work.await
|
|
||||||
import com.deniscerri.ytdlnis.R
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
|
||||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
|
||||||
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.closestValue
|
|
||||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
|
||||||
import java.time.Month
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
object DownloadUtil {
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
suspend fun startDownloadWorker(queuedItems: List<DownloadItem>, context: Context) {
|
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
|
||||||
val workManager = WorkManager.getInstance(context)
|
|
||||||
|
|
||||||
val currentWork = workManager.getWorkInfosByTag("download").await()
|
|
||||||
if (currentWork.size == 0 || currentWork.none{ it.state == WorkInfo.State.RUNNING } || (queuedItems.isNotEmpty() && queuedItems[0].downloadStartTime != 0L)){
|
|
||||||
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
var delay = 0L
|
|
||||||
if (queuedItems.isNotEmpty()){
|
|
||||||
delay = if (queuedItems[0].downloadStartTime != 0L){
|
|
||||||
queuedItems[0].downloadStartTime.minus(currentTime)
|
|
||||||
} else 0
|
|
||||||
if (delay <= 60000L) delay = 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val workConstraints = Constraints.Builder()
|
|
||||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
|
||||||
|
|
||||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
|
||||||
.addTag("download")
|
|
||||||
.setConstraints(workConstraints.build())
|
|
||||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
|
||||||
|
|
||||||
queuedItems.forEach {
|
|
||||||
workRequest.addTag(it.id.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
workManager.enqueueUniqueWork(
|
|
||||||
System.currentTimeMillis().toString(),
|
|
||||||
ExistingWorkPolicy.REPLACE,
|
|
||||||
workRequest.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
|
||||||
if (!allowMeteredNetworks && isCurrentNetworkMetered){
|
|
||||||
Looper.prepare().run {
|
|
||||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancelObservationTaskByID(context: Context, id: Long){
|
|
||||||
val alarmManager = context.getSystemService(AlarmManager::class.java)
|
|
||||||
alarmManager.cancel(
|
|
||||||
PendingIntent.getBroadcast(
|
|
||||||
context,
|
|
||||||
id.toInt(),
|
|
||||||
Intent(context, ObserveAlarmReceiver::class.java),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -22,16 +22,13 @@ import android.view.animation.Interpolator
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Px
|
import androidx.annotation.Px
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.withStarted
|
import androidx.lifecycle.withStarted
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.deniscerri.ytdlnis.R
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.neo.highlight.core.Highlight
|
import com.neo.highlight.core.Highlight
|
||||||
import com.neo.highlight.util.listener.HighlightTextWatcher
|
import com.neo.highlight.util.listener.HighlightTextWatcher
|
||||||
import com.neo.highlight.util.scheme.ColorScheme
|
import com.neo.highlight.util.scheme.ColorScheme
|
||||||
|
|
@ -205,8 +202,6 @@ object Extensions {
|
||||||
format.substring(3) else if (this < 36000) format = format.substring(1)
|
format.substring(3) else if (this < 36000) format = format.substring(1)
|
||||||
return format
|
return format
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun View.popup(){
|
fun View.popup(){
|
||||||
val animator = ValueAnimator.ofFloat( 0.75f, 1f)
|
val animator = ValueAnimator.ofFloat( 0.75f, 1f)
|
||||||
animator.addUpdateListener { animation: ValueAnimator ->
|
animator.addUpdateListener { animation: ValueAnimator ->
|
||||||
|
|
@ -219,6 +214,25 @@ object Extensions {
|
||||||
animator.start()
|
animator.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun TabLayout.Tab.createBadge(nr: Int){
|
||||||
|
this.orCreateBadge.apply {
|
||||||
|
number = nr
|
||||||
|
verticalOffset = 5
|
||||||
|
horizontalOffset = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.appendLineToLog(line: String): String {
|
||||||
|
val lines = this.split("\n")
|
||||||
|
//clean dublicate progress + add newline
|
||||||
|
var newLine = line
|
||||||
|
if (newLine.contains("[download")) {
|
||||||
|
newLine = "[download]" + line.split("[download]").last()
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
||||||
|
}
|
||||||
|
|
||||||
fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) }
|
fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) }
|
||||||
|
|
||||||
class CustomInterpolator : Interpolator {
|
class CustomInterpolator : Interpolator {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import java.io.InputStreamReader
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun search(query: String): ArrayList<ResultItem?> {
|
fun search(query: String): ArrayList<ResultItem> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
when(sharedPreferences.getString("search_engine", "ytsearch")){
|
when(sharedPreferences.getString("search_engine", "ytsearch")){
|
||||||
"ytsearch" -> searchFromPiped(query)
|
"ytsearch" -> searchFromPiped(query)
|
||||||
|
|
@ -66,8 +67,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
fun searchFromPiped(query: String): ArrayList<ResultItem> {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem>()
|
||||||
val data = genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCODE}")
|
val data = genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCODE}")
|
||||||
val dataArray = data.getJSONArray("items")
|
val dataArray = data.getJSONArray("items")
|
||||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||||
|
|
@ -85,8 +86,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun searchFromPipedMusic(query: String): ArrayList<ResultItem?> {
|
fun searchFromPipedMusic(query: String): ArrayList<ResultItem> {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem>()
|
||||||
val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs®ion=${countryCODE}")
|
val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs®ion=${countryCODE}")
|
||||||
val dataArray = data.getJSONArray("items")
|
val dataArray = data.getJSONArray("items")
|
||||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||||
|
|
@ -106,7 +107,7 @@ class InfoUtil(private val context: Context) {
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
|
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
|
||||||
try{
|
try{
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem>()
|
||||||
// -------------- PIPED API FUNCTION -------------------
|
// -------------- PIPED API FUNCTION -------------------
|
||||||
var url = ""
|
var url = ""
|
||||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||||
|
|
@ -120,10 +121,11 @@ class InfoUtil(private val context: Context) {
|
||||||
for (i in 0 until dataArray.length()){
|
for (i in 0 until dataArray.length()){
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val obj = dataArray.getJSONObject(i)
|
val obj = dataArray.getJSONObject(i)
|
||||||
val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))
|
createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))?.apply {
|
||||||
itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||||
itm?.playlistURL = "https://www.youtube.com/playlist?list=$id"
|
playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||||
items.add(itm)
|
items.add(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nextpage == "null") nextpage = ""
|
if (nextpage == "null") nextpage = ""
|
||||||
|
|
@ -137,17 +139,25 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getYoutubeVideo(url: String): List<ResultItem?> {
|
fun getYoutubeVideo(url: String): List<ResultItem>? {
|
||||||
val theURL = url.replace("\\?list.*".toRegex(), "")
|
val theURL = url.replace("\\?list.*".toRegex(), "")
|
||||||
return try {
|
try{
|
||||||
val id = getIDFromYoutubeURL(theURL)
|
val id = getIDFromYoutubeURL(theURL)
|
||||||
val res = genericRequest("$pipedURL/streams/$id")
|
val res = genericRequest("$pipedURL/streams/$id")
|
||||||
if (res.length() == 0) getFromYTDL(theURL) else listOf(createVideoFromPipedJSON(res, theURL))
|
if (res.length() == 0) {
|
||||||
|
return getFromYTDL(theURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
createVideoFromPipedJSON(res, theURL)?.apply {
|
||||||
|
return listOf(this)
|
||||||
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
val v = getFromYTDL(theURL)
|
val v = getFromYTDL(theURL)
|
||||||
v.forEach { it!!.url = theURL }
|
v.forEach { it.url = theURL }
|
||||||
v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createVideofromJSON(obj: JSONObject): ResultItem? {
|
private fun createVideofromJSON(obj: JSONObject): ResultItem? {
|
||||||
|
|
@ -359,7 +369,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getTrending(): ArrayList<ResultItem?> {
|
fun getTrending(): ArrayList<ResultItem> {
|
||||||
if (sharedPreferences.getString("api_key", "")!!.isNotBlank()){
|
if (sharedPreferences.getString("api_key", "")!!.isNotBlank()){
|
||||||
return getTrendingFromYoutubeAPI()
|
return getTrendingFromYoutubeAPI()
|
||||||
}
|
}
|
||||||
|
|
@ -367,8 +377,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getTrendingFromYoutubeAPI(): ArrayList<ResultItem?> {
|
fun getTrendingFromYoutubeAPI(): ArrayList<ResultItem> {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem>()
|
||||||
val key = sharedPreferences.getString("api_key", "")!!
|
val key = sharedPreferences.getString("api_key", "")!!
|
||||||
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10®ionCode=$countryCODE&maxResults=25&key=$key"
|
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10®ionCode=$countryCODE&maxResults=25&key=$key"
|
||||||
//short data
|
//short data
|
||||||
|
|
@ -397,8 +407,8 @@ class InfoUtil(private val context: Context) {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTrendingFromPiped(): ArrayList<ResultItem?> {
|
private fun getTrendingFromPiped(): ArrayList<ResultItem> {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem>()
|
||||||
val url = "$pipedURL/trending?region=$countryCODE"
|
val url = "$pipedURL/trending?region=$countryCODE"
|
||||||
val res = genericArrayRequest(url)
|
val res = genericArrayRequest(url)
|
||||||
for (i in 0 until res.length()) {
|
for (i in 0 until res.length()) {
|
||||||
|
|
@ -438,7 +448,7 @@ class InfoUtil(private val context: Context) {
|
||||||
return try {
|
return try {
|
||||||
val id = getIDFromYoutubeURL(url)
|
val id = getIDFromYoutubeURL(url)
|
||||||
val res = genericRequest("$pipedURL/streams/$id")
|
val res = genericRequest("$pipedURL/streams/$id")
|
||||||
if (res.length() == 0) getFromYTDL(url)[0]!!
|
if (res.length() == 0) getFromYTDL(url)[0]
|
||||||
val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id")
|
val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id")
|
||||||
item!!.formats
|
item!!.formats
|
||||||
}catch(e: Exception) {
|
}catch(e: Exception) {
|
||||||
|
|
@ -571,8 +581,8 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
fun getFromYTDL(query: String, singleItem: Boolean = false): ArrayList<ResultItem> {
|
||||||
val items = arrayListOf<ResultItem?>()
|
val items = arrayListOf<ResultItem>()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||||
|
|
||||||
val request : YoutubeDLRequest
|
val request : YoutubeDLRequest
|
||||||
|
|
@ -596,7 +606,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("--flat-playlist")
|
request.addOption("--flat-playlist")
|
||||||
request.addOption("-j")
|
request.addOption(if (singleItem) "-J" else "-j")
|
||||||
request.addOption("--skip-download")
|
request.addOption("--skip-download")
|
||||||
request.addOption("-R", "1")
|
request.addOption("-R", "1")
|
||||||
request.addOption("--compat-options", "manifest-filesize-approx")
|
request.addOption("--compat-options", "manifest-filesize-approx")
|
||||||
|
|
@ -694,14 +704,15 @@ class InfoUtil(private val context: Context) {
|
||||||
val url = if (jsonObject.has("url") && results.size > 1){
|
val url = if (jsonObject.has("url") && results.size > 1){
|
||||||
jsonObject.getString("url")
|
jsonObject.getString("url")
|
||||||
}else{
|
}else{
|
||||||
if (Patterns.WEB_URL.matcher(query).matches()){
|
if (Patterns.WEB_URL.matcher(query).matches() && playlistURL?.isEmpty() == true){
|
||||||
query
|
query
|
||||||
}else{
|
}else{
|
||||||
|
jsonObject.getStringByAny("webpage_url", "original_url", "url", )
|
||||||
jsonObject.getString("webpage_url")
|
jsonObject.getString("webpage_url")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(ResultItem(0,
|
val res = ResultItem(0,
|
||||||
url,
|
url,
|
||||||
title,
|
title,
|
||||||
author,
|
author,
|
||||||
|
|
@ -715,7 +726,8 @@ class InfoUtil(private val context: Context) {
|
||||||
playlistURL,
|
playlistURL,
|
||||||
playlistIndex
|
playlistIndex
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
items.add(res)
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
@ -772,79 +784,6 @@ class InfoUtil(private val context: Context) {
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMissingInfo(url: String): ResultItem? {
|
|
||||||
val request = YoutubeDLRequest(url)
|
|
||||||
request.addOption("--flat-playlist")
|
|
||||||
request.addOption("-J")
|
|
||||||
request.addOption("--skip-download")
|
|
||||||
request.addOption("-R", "1")
|
|
||||||
request.addOption("--socket-timeout", "5")
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("force_ipv4", false)){
|
|
||||||
request.addOption("-4")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("use_cookies", false)){
|
|
||||||
FileUtil.getCookieFile(context){
|
|
||||||
request.addOption("--cookies", it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val useHeader = sharedPreferences.getBoolean("use_header", false)
|
|
||||||
val header = sharedPreferences.getString("useragent_header", "")
|
|
||||||
if (useHeader && !header.isNullOrBlank()){
|
|
||||||
request.addOption("--add-header","User-Agent:${header}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val proxy = sharedPreferences.getString("proxy", "")
|
|
||||||
if (proxy!!.isNotBlank()){
|
|
||||||
request.addOption("--proxy", proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
|
|
||||||
val jsonObject = JSONObject(youtubeDLResponse.out)
|
|
||||||
|
|
||||||
val title = jsonObject.getStringByAny("track", "alt_title", "title", "webpage_url_basename")
|
|
||||||
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
|
|
||||||
|
|
||||||
var duration = jsonObject.getIntByAny("duration").toString()
|
|
||||||
if (duration != "-1"){
|
|
||||||
duration = jsonObject.getInt("duration").toStringDuration(Locale.US)
|
|
||||||
}
|
|
||||||
|
|
||||||
var thumb: String? = ""
|
|
||||||
if (jsonObject.has("thumbnail")) {
|
|
||||||
thumb = jsonObject.getString("thumbnail")
|
|
||||||
} else if (jsonObject.has("thumbnails")) {
|
|
||||||
val thumbs = jsonObject.getJSONArray("thumbnails")
|
|
||||||
if (thumbs.length() > 0){
|
|
||||||
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isPlaylist = jsonObject.has("playlist_count")
|
|
||||||
return ResultItem(
|
|
||||||
0,
|
|
||||||
url,
|
|
||||||
if (isPlaylist) "" else title,
|
|
||||||
author,
|
|
||||||
duration,
|
|
||||||
thumb!!,
|
|
||||||
jsonObject.getString("extractor"),
|
|
||||||
if (isPlaylist) jsonObject.getString("title") else "",
|
|
||||||
arrayListOf(),
|
|
||||||
"",
|
|
||||||
arrayListOf(),
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
System.currentTimeMillis()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getSearchSuggestions(query: String): ArrayList<String> {
|
fun getSearchSuggestions(query: String): ArrayList<String> {
|
||||||
val url = "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&client=firefox&q=$query"
|
val url = "https://suggestqueries.google.com/complete/search?client=youtube&ds=yt&client=firefox&q=$query"
|
||||||
|
|
@ -1064,7 +1003,7 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
if (downloadItem.SaveThumb) {
|
if (downloadItem.SaveThumb) {
|
||||||
request.addOption("--write-thumbnail")
|
request.addOption("--write-thumbnail")
|
||||||
request.addOption("--convert-thumbnails", "jpg")
|
request.addOption("--convert-thumbnails", sharedPreferences.getString("thumbnail_format", "jpg")!!)
|
||||||
}
|
}
|
||||||
if (!sharedPreferences.getBoolean("mtime", false)){
|
if (!sharedPreferences.getBoolean("mtime", false)){
|
||||||
request.addOption("--no-mtime")
|
request.addOption("--no-mtime")
|
||||||
|
|
@ -1093,15 +1032,42 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(downloadItem.title.isNotBlank()){
|
|
||||||
request.addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title))
|
|
||||||
|
try {
|
||||||
|
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##replaceMetadata.txt")
|
||||||
|
YoutubeDLRequest("").apply {
|
||||||
|
|
||||||
|
if (downloadItem.playlistTitle.isNotBlank()){
|
||||||
|
addCommands(listOf("--replace-in-metadata", "playlist", ".+", downloadItem.playlistTitle))
|
||||||
|
runCatching {
|
||||||
|
if (downloadItem.playlistIndex != null){
|
||||||
|
addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(downloadItem.title.isNotBlank()){
|
||||||
|
addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (downloadItem.author.isNotBlank()){
|
||||||
|
addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author))
|
||||||
|
}
|
||||||
|
|
||||||
|
val configData = parseYTDLRequestString(this).replace(" \" \"", "")
|
||||||
|
config.writeText(configData)
|
||||||
|
request.addOption("--config", config.absolutePath)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
request.addOption("--parse-metadata", "%(channel,uploader,creator,artist|null)s:%(uploader)s")
|
request.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$")
|
||||||
request.addOption("--parse-metadata", "%(uploader)s:%(album_artist)s")
|
|
||||||
if (downloadItem.author.isNotBlank()){
|
if (embedMetadata){
|
||||||
request.addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author))
|
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
|
||||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.downloadSections.isNotBlank()){
|
if (downloadItem.downloadSections.isNotBlank()){
|
||||||
|
|
@ -1122,7 +1088,7 @@ class InfoUtil(private val context: Context) {
|
||||||
"%(section_title&{} |)s$filenameTemplate"
|
"%(section_title&{} |)s$filenameTemplate"
|
||||||
}
|
}
|
||||||
if (downloadItem.downloadSections.split(";").size > 1){
|
if (downloadItem.downloadSections.split(";").size > 1){
|
||||||
filenameTemplate = "%(autonumber)d. %(section_start>%H∶%M∶%S)s $filenameTemplate"
|
filenameTemplate = "%(autonumber)d. $filenameTemplate [%(section_start>%H∶%M∶%S)s]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1133,7 +1099,7 @@ class InfoUtil(private val context: Context) {
|
||||||
if (downloadItem.extraCommands.isNotBlank()){
|
if (downloadItem.extraCommands.isNotBlank()){
|
||||||
val cache = File(FileUtil.getCachePath(context))
|
val cache = File(FileUtil.getCachePath(context))
|
||||||
cache.mkdirs()
|
cache.mkdirs()
|
||||||
val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}.txt")
|
val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt")
|
||||||
conf.createNewFile()
|
conf.createNewFile()
|
||||||
conf.writeText(downloadItem.extraCommands)
|
conf.writeText(downloadItem.extraCommands)
|
||||||
request.addOption(
|
request.addOption(
|
||||||
|
|
@ -1146,16 +1112,9 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--write-description")
|
request.addOption("--write-description")
|
||||||
}
|
}
|
||||||
|
|
||||||
filenameTemplate = filenameTemplate.replace("%(uploader)s", "%(uploader,channel)s")
|
filenameTemplate = filenameTemplate
|
||||||
}
|
.replace("%(uploader)s", "%(uploader,channel).30B")
|
||||||
|
.replace("%(title)s", "%(title).170B")
|
||||||
if (downloadItem.playlistTitle.isNotBlank()){
|
|
||||||
request.addCommands(listOf("--replace-in-metadata", "playlist", ".+", downloadItem.playlistTitle))
|
|
||||||
runCatching {
|
|
||||||
if (downloadItem.playlistIndex != null){
|
|
||||||
request.addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean("download_archive", false)){
|
if (sharedPreferences.getBoolean("download_archive", false)){
|
||||||
|
|
@ -1216,9 +1175,9 @@ 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", "%(album_artist,meta_album_artist,uploader)s:%(album_artist)s")
|
||||||
request.addOption("--parse-metadata", "%(release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
|
request.addOption("--parse-metadata", "%(release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
|
||||||
request.addOption("--parse-metadata", "video:%(uploader)s:%(album_artist)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,playlist,title)s:%(meta_album)s")
|
||||||
|
|
@ -1519,7 +1478,7 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
class PlaylistTuple internal constructor(
|
class PlaylistTuple internal constructor(
|
||||||
var nextPageToken: String,
|
var nextPageToken: String,
|
||||||
var videos: ArrayList<ResultItem?>
|
var videos: ArrayList<ResultItem>
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,15 @@ import android.app.PendingIntent
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.navigation.NavDeepLinkBuilder
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
import com.deniscerri.ytdlnis.receiver.CancelWorkReceiver
|
||||||
|
|
@ -27,7 +23,6 @@ import com.deniscerri.ytdlnis.receiver.PauseDownloadNotificationReceiver
|
||||||
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
import com.deniscerri.ytdlnis.receiver.ResumeActivity
|
||||||
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
import com.deniscerri.ytdlnis.receiver.ShareFileActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationUtil(var context: Context) {
|
class NotificationUtil(var context: Context) {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,11 @@ import android.content.pm.PackageManager
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -10,6 +9,8 @@ import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
|
|
@ -25,20 +26,16 @@ import android.widget.Button
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ScrollView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.compose.ui.text.toUpperCase
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.children
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setMargins
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
|
|
@ -61,7 +58,6 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.adapter.AlreadyExistsAdapter
|
import com.deniscerri.ytdlnis.ui.adapter.AlreadyExistsAdapter
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
|
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.dp
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
||||||
|
|
@ -94,7 +90,6 @@ import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -496,6 +491,7 @@ object UiUtil {
|
||||||
|
|
||||||
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
||||||
val currentDate = Calendar.getInstance()
|
val currentDate = Calendar.getInstance()
|
||||||
|
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
|
||||||
val date = Calendar.getInstance()
|
val date = Calendar.getInstance()
|
||||||
|
|
||||||
val datePicker = MaterialDatePicker.Builder.datePicker()
|
val datePicker = MaterialDatePicker.Builder.datePicker()
|
||||||
|
|
@ -530,6 +526,7 @@ object UiUtil {
|
||||||
|
|
||||||
fun showTimePicker(fragmentManager: FragmentManager , onSubmit : (chosenTime: Calendar) -> Unit ){
|
fun showTimePicker(fragmentManager: FragmentManager , onSubmit : (chosenTime: Calendar) -> Unit ){
|
||||||
val currentDate = Calendar.getInstance()
|
val currentDate = Calendar.getInstance()
|
||||||
|
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
|
||||||
val date = Calendar.getInstance()
|
val date = Calendar.getInstance()
|
||||||
|
|
||||||
val timepicker = MaterialTimePicker.Builder()
|
val timepicker = MaterialTimePicker.Builder()
|
||||||
|
|
@ -1055,16 +1052,22 @@ object UiUtil {
|
||||||
val chips = mutableListOf<Chip>()
|
val chips = mutableListOf<Chip>()
|
||||||
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
|
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
|
||||||
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip
|
||||||
tmp.text = s
|
tmp.text = Locale(s).displayLanguage
|
||||||
|
tmp.tag = s
|
||||||
tmp.id = index
|
tmp.id = index
|
||||||
|
|
||||||
tmp.setOnClickListener {
|
tmp.setOnClickListener {
|
||||||
val c = it as Chip
|
val c = it as Chip
|
||||||
if(!c.isChecked){
|
if(!c.isChecked){
|
||||||
editText.setText(editText.text.toString().replace(c.text.toString(), ""))
|
editText.setText(editText.text.toString().replace(c.tag.toString(), "").removeSuffix(","))
|
||||||
editText.setSelection(editText.text.length)
|
editText.setSelection(editText.text.length)
|
||||||
}else{
|
}else{
|
||||||
editText.append(c.text)
|
if (editText.text.isBlank()){
|
||||||
|
editText.setText(c.tag.toString())
|
||||||
|
editText.setSelection(editText.text.length)
|
||||||
|
}else{
|
||||||
|
editText.append(",${c.tag}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1183,13 +1186,14 @@ object UiUtil {
|
||||||
saveThumbnailClicked: (Boolean) -> Unit,
|
saveThumbnailClicked: (Boolean) -> Unit,
|
||||||
sponsorBlockItemsSet: (values: Array<String>, checkedItems: List<Boolean>) -> Unit,
|
sponsorBlockItemsSet: (values: Array<String>, checkedItems: List<Boolean>) -> Unit,
|
||||||
cutClicked: (VideoCutListener) -> Unit,
|
cutClicked: (VideoCutListener) -> Unit,
|
||||||
|
cutDisabledClicked: () -> Unit,
|
||||||
filenameTemplateSet: (String) -> Unit,
|
filenameTemplateSet: (String) -> Unit,
|
||||||
saveSubtitlesClicked: (Boolean) -> Unit,
|
saveSubtitlesClicked: (Boolean) -> Unit,
|
||||||
saveAutoSubtitlesClicked: (Boolean) -> Unit,
|
saveAutoSubtitlesClicked: (Boolean) -> Unit,
|
||||||
subtitleLanguagesSet: (String) -> Unit,
|
subtitleLanguagesSet: (String) -> Unit,
|
||||||
removeAudioClicked: (Boolean) -> Unit,
|
removeAudioClicked: (Boolean) -> Unit,
|
||||||
extraCommandsClicked: () -> Unit,
|
alsoDownloadAsAudioClicked: (Boolean) -> Unit,
|
||||||
updateDataClicked: () -> Unit
|
extraCommandsClicked: () -> Unit
|
||||||
){
|
){
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
||||||
|
|
@ -1322,11 +1326,7 @@ object UiUtil {
|
||||||
}else{
|
}else{
|
||||||
cut.alpha = 0.3f
|
cut.alpha = 0.3f
|
||||||
cut.setOnClickListener {
|
cut.setOnClickListener {
|
||||||
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
|
cutDisabledClicked()
|
||||||
snack.setAction(R.string.update){
|
|
||||||
updateDataClicked()
|
|
||||||
}
|
|
||||||
snack.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1372,10 +1372,41 @@ object UiUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
|
if (items.size == 1 && items.first().id == 0L){
|
||||||
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
|
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
|
||||||
removeAudio.setOnCheckedChangeListener { _, _ ->
|
adjustAudio.setOnClickListener {
|
||||||
removeAudioClicked(removeAudio.isChecked)
|
val adjustAudioView = context.layoutInflater.inflate(R.layout.audio_download_preferences_dialog, null)
|
||||||
|
adjustAudioView.findViewById<MaterialSwitch>(R.id.remove_audio).apply {
|
||||||
|
isChecked = items.first().videoPreferences.removeAudio
|
||||||
|
setOnCheckedChangeListener { _, b ->
|
||||||
|
removeAudioClicked(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustAudioView.findViewById<MaterialSwitch>(R.id.also_download_audio).apply {
|
||||||
|
isChecked = items.first().videoPreferences.alsoDownloadAsAudio
|
||||||
|
setOnCheckedChangeListener { _, b ->
|
||||||
|
alsoDownloadAsAudioClicked(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val adjustAudioDialog = MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(context.getString(R.string.adjust_audio))
|
||||||
|
.setView(adjustAudioView)
|
||||||
|
.setIcon(R.drawable.ic_music)
|
||||||
|
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
|
||||||
|
|
||||||
|
adjustAudioDialog.show()
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
|
||||||
|
adjustAudio.isVisible = false
|
||||||
|
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
|
||||||
|
removeAudio.isVisible = true
|
||||||
|
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
|
||||||
|
removeAudio.setOnCheckedChangeListener { _, _ ->
|
||||||
|
removeAudioClicked(removeAudio.isChecked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
val extraCommands = view.findViewById<Chip>(R.id.extra_commands)
|
||||||
|
|
@ -1399,8 +1430,8 @@ object UiUtil {
|
||||||
filenameTemplateSet: (String) -> Unit,
|
filenameTemplateSet: (String) -> Unit,
|
||||||
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
|
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
|
||||||
cutClicked: (VideoCutListener) -> Unit,
|
cutClicked: (VideoCutListener) -> Unit,
|
||||||
extraCommandsClicked: () -> Unit,
|
cutDisabledClicked: () -> Unit,
|
||||||
updateDataClicked: () -> Unit
|
extraCommandsClicked: () -> Unit
|
||||||
){
|
){
|
||||||
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
|
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
|
||||||
val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
|
val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
|
||||||
|
|
@ -1513,11 +1544,7 @@ object UiUtil {
|
||||||
}else{
|
}else{
|
||||||
cut.alpha = 0.3f
|
cut.alpha = 0.3f
|
||||||
cut.setOnClickListener {
|
cut.setOnClickListener {
|
||||||
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
|
cutDisabledClicked()
|
||||||
snack.setAction(R.string.update){
|
|
||||||
updateDataClicked()
|
|
||||||
}
|
|
||||||
snack.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.BuildConfig
|
import com.deniscerri.ytdlnis.BuildConfig
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.GithubRelease
|
import com.deniscerri.ytdlnis.database.models.GithubRelease
|
||||||
|
import com.deniscerri.ytdlnis.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
|
||||||
|
|
@ -175,7 +176,7 @@ class UpdateUtil(var context: Context) {
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
|
|
||||||
layoutParams.setMargins(20, 20, 20, 0)
|
layoutParams.setMargins(5, 5, 5, 0)
|
||||||
scrollView.layoutParams = layoutParams
|
scrollView.layoutParams = layoutParams
|
||||||
scrollView.addView(linearLayout)
|
scrollView.addView(linearLayout)
|
||||||
val releases = getGithubReleases()
|
val releases = getGithubReleases()
|
||||||
|
|
@ -212,7 +213,9 @@ class UpdateUtil(var context: Context) {
|
||||||
|
|
||||||
val assetGroup = findViewById<ChipGroup>(R.id.assets)
|
val assetGroup = findViewById<ChipGroup>(R.id.assets)
|
||||||
it.assets.forEachIndexed { idx, c ->
|
it.assets.forEachIndexed { idx, c ->
|
||||||
val tmp = activity.layoutInflater.inflate(R.layout.suggestion_chip, assetGroup, false) as Chip
|
val tmp = activity.layoutInflater.inflate(R.layout.filter_chip, assetGroup, false) as Chip
|
||||||
|
tmp.isCheckable = false
|
||||||
|
tmp.layoutParams = layoutParams
|
||||||
tmp.text = c.name
|
tmp.text = c.name
|
||||||
tmp.id = idx
|
tmp.id = idx
|
||||||
tmp.setOnClickListener {
|
tmp.setOnClickListener {
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,15 @@ import androidx.work.WorkManager
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import androidx.work.await
|
import androidx.work.await
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
|
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
|
||||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
|
@ -36,6 +35,7 @@ import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
@ -59,6 +59,7 @@ class DownloadWorker(
|
||||||
val historyDao = dbManager.historyDao
|
val historyDao = dbManager.historyDao
|
||||||
val resultDao = dbManager.resultDao
|
val resultDao = dbManager.resultDao
|
||||||
val logRepo = LogRepository(dbManager.logDao)
|
val logRepo = LogRepository(dbManager.logDao)
|
||||||
|
val resultRepo = ResultRepository(dbManager.resultDao, context)
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
val alarmScheduler = AlarmScheduler(context)
|
val alarmScheduler = AlarmScheduler(context)
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
@ -67,6 +68,8 @@ class DownloadWorker(
|
||||||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||||
|
|
||||||
|
val createResultItem = inputData.getBoolean("createResultItem", true)
|
||||||
|
|
||||||
val confTmp = Configuration(context.resources.configuration)
|
val confTmp = Configuration(context.resources.configuration)
|
||||||
val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-")
|
val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-")
|
||||||
confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1]))
|
confTmp.setLocale(if (currLang.size == 1) Locale(currLang[0]) else Locale(currLang[0], currLang[1]))
|
||||||
|
|
@ -111,8 +114,11 @@ class DownloadWorker(
|
||||||
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||||
downloadItem.status = DownloadRepository.Status.Active.toString()
|
downloadItem.status = DownloadRepository.Status.Active.toString()
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
delay(1500)
|
||||||
//update item if its incomplete
|
//update item if its incomplete
|
||||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
||||||
|
dao.updateWithoutUpsert(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cacheDir = FileUtil.getCachePath(context)
|
val cacheDir = FileUtil.getCachePath(context)
|
||||||
|
|
@ -164,7 +170,10 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
||||||
|
dao.updateWithoutUpsert(this)
|
||||||
|
}
|
||||||
|
val wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||||
runBlocking {
|
runBlocking {
|
||||||
var finalPaths : List<String>?
|
var finalPaths : List<String>?
|
||||||
|
|
||||||
|
|
@ -178,12 +187,6 @@ class DownloadWorker(
|
||||||
.map { it.removeSuffix("'") }
|
.map { it.removeSuffix("'") }
|
||||||
.sortedBy { File(it).lastModified() }
|
.sortedBy { File(it).lastModified() }
|
||||||
.toList()
|
.toList()
|
||||||
// finalPaths = File(FileUtil.formatPath(downloadLocation))
|
|
||||||
// .walkTopDown()
|
|
||||||
// .filter { it.isFile && p.any { f -> f.contains(it.nameWithoutExtension) }}
|
|
||||||
// .sortedByDescending { it.length() }
|
|
||||||
// .map { it.absolutePath }
|
|
||||||
// .toList()
|
|
||||||
FileUtil.scanMedia(finalPaths, context)
|
FileUtil.scanMedia(finalPaths, context)
|
||||||
if (finalPaths.isEmpty()){
|
if (finalPaths.isEmpty()){
|
||||||
finalPaths = listOf(context.getString(R.string.unfound_file))
|
finalPaths = listOf(context.getString(R.string.unfound_file))
|
||||||
|
|
@ -212,7 +215,14 @@ class DownloadWorker(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finalPaths = finalPaths?.filter { !it.matches("(\\.description)|(\\.srt)|(\\.ass)|(\\.lrc)|(\\.vtt)|(\\.txt)|(\\.jpg)|(\\.png)\$".toRegex()) }
|
|
||||||
|
val nonMediaExtensions = mutableListOf<String>().apply {
|
||||||
|
addAll(context.getStringArray(R.array.thumbnail_containers_values))
|
||||||
|
addAll(context.getStringArray(R.array.sub_formats_values).filter { it.isNotBlank() })
|
||||||
|
add("description")
|
||||||
|
add("txt")
|
||||||
|
}
|
||||||
|
finalPaths = finalPaths?.filter { path -> !nonMediaExtensions.any { path.endsWith(it) } }
|
||||||
FileUtil.deleteConfigFiles(request)
|
FileUtil.deleteConfigFiles(request)
|
||||||
|
|
||||||
//put download in history
|
//put download in history
|
||||||
|
|
@ -260,7 +270,7 @@ class DownloadWorker(
|
||||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
|
||||||
)
|
)
|
||||||
|
|
||||||
if (wasQuickDownloaded){
|
if (wasQuickDownloaded && createResultItem){
|
||||||
runCatching {
|
runCatching {
|
||||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
|
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
|
@ -338,32 +348,6 @@ class DownloadWorker(
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDownloadItem(
|
|
||||||
downloadItem: DownloadItem,
|
|
||||||
infoUtil: InfoUtil,
|
|
||||||
dao: DownloadDao,
|
|
||||||
resultDao: ResultDao
|
|
||||||
) : Boolean {
|
|
||||||
var wasQuickDownloaded = false
|
|
||||||
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
|
||||||
runCatching {
|
|
||||||
if (isStopped) YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
|
|
||||||
setProgressAsync(workDataOf("progress" to 0, "output" to context.getString(R.string.updating_download_data), "id" to downloadItem.id))
|
|
||||||
val info = infoUtil.getMissingInfo(downloadItem.url)
|
|
||||||
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
|
||||||
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
|
|
||||||
downloadItem.duration = info?.duration.toString()
|
|
||||||
downloadItem.website = info?.website.toString()
|
|
||||||
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
|
|
||||||
runBlocking {
|
|
||||||
wasQuickDownloaded = resultDao.getCountInt() == 0
|
|
||||||
dao.update(downloadItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wasQuickDownloaded
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,23 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
import androidx.work.ForegroundInfo
|
import androidx.work.ForegroundInfo
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
|
||||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
|
||||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
|
||||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||||
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
||||||
import com.deniscerri.ytdlnis.util.DownloadUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.Extensions.closestValue
|
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
|
|
@ -41,6 +37,7 @@ class ObserveSourceWorker(
|
||||||
if (sourceID == 0L) return Result.failure()
|
if (sourceID == 0L) return Result.failure()
|
||||||
val dbManager = DBManager.getInstance(context)
|
val dbManager = DBManager.getInstance(context)
|
||||||
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
|
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
|
||||||
|
val resultsRepo = ResultRepository(dbManager.resultDao, App.instance)
|
||||||
val historyRepo = HistoryRepository(dbManager.historyDao)
|
val historyRepo = HistoryRepository(dbManager.historyDao)
|
||||||
val downloadRepo = DownloadRepository(dbManager.downloadDao)
|
val downloadRepo = DownloadRepository(dbManager.downloadDao)
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
@ -48,7 +45,7 @@ class ObserveSourceWorker(
|
||||||
|
|
||||||
val item = repo.getByID(sourceID)
|
val item = repo.getByID(sourceID)
|
||||||
if (item.status == ObserveSourcesRepository.SourceStatus.STOPPED){
|
if (item.status == ObserveSourcesRepository.SourceStatus.STOPPED){
|
||||||
DownloadUtil.cancelObservationTaskByID(context, item.id)
|
repo.cancelObservationTaskByID(context, item.id)
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,36 +54,50 @@ class ObserveSourceWorker(
|
||||||
val foregroundInfo = ForegroundInfo(workerID, notification)
|
val foregroundInfo = ForegroundInfo(workerID, notification)
|
||||||
setForegroundAsync(foregroundInfo)
|
setForegroundAsync(foregroundInfo)
|
||||||
|
|
||||||
item.runCount = item.runCount + 1
|
val res = runCatching { infoUtil.getFromYTDL(item.url).toList() }.getOrElse { listOf() }
|
||||||
|
.filter { result ->
|
||||||
var res = runCatching { infoUtil.getFromYTDL(item.url).toList() }.getOrElse { listOf() }
|
//if first run and get new items only is preferred then dont get anything on first run
|
||||||
res = if (!item.retryMissingDownloads){
|
if (item.getOnlyNewUploads && item.runCount == 0){
|
||||||
res.filter { result ->
|
item.alreadyProcessedLinks.add(result.url)
|
||||||
//all items that are not present in history
|
false
|
||||||
!historyRepo.getAllByURL(result!!.url).filter { hi ->
|
}else{
|
||||||
hi.downloadPath.any { path -> FileUtil.exists(path) }
|
true
|
||||||
}.map { it.url }.contains(result.url)
|
}
|
||||||
}
|
}
|
||||||
}else{
|
.filter { result ->
|
||||||
//all items that are not already processed
|
|
||||||
res.filter { !item.alreadyProcessedLinks.contains(it!!.url) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val history = historyRepo.getAllByURL(result.url)
|
||||||
|
if (!item.retryMissingDownloads){
|
||||||
|
//all items that are not present in history
|
||||||
|
history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||||
|
}else{
|
||||||
|
//all items that are not already processed
|
||||||
|
if(item.alreadyProcessedLinks.isEmpty()){
|
||||||
|
!history.map { it.url }.contains(result.url) && !item.alreadyProcessedLinks.contains(result.url)
|
||||||
|
}else{
|
||||||
|
!item.alreadyProcessedLinks.contains(result.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
val items = mutableListOf<DownloadItem>()
|
val items = mutableListOf<DownloadItem>()
|
||||||
|
|
||||||
res.forEach {
|
res.forEach {
|
||||||
val string = Gson().toJson(item.downloadItemTemplate, DownloadItem::class.java)
|
val string = Gson().toJson(item.downloadItemTemplate, DownloadItem::class.java)
|
||||||
val downloadItem = Gson().fromJson(string, DownloadItem::class.java)
|
val downloadItem = Gson().fromJson(string, DownloadItem::class.java)
|
||||||
downloadItem.url = it!!.url
|
downloadItem.url = it.url
|
||||||
|
downloadItem.title = it.title
|
||||||
|
downloadItem.author = it.author
|
||||||
|
downloadItem.thumb = it.thumb
|
||||||
|
downloadItem.status = DownloadRepository.Status.Queued.toString()
|
||||||
|
downloadItem.playlistTitle = it.playlistTitle
|
||||||
|
downloadItem.playlistURL = it.playlistURL
|
||||||
|
downloadItem.playlistIndex = it.playlistIndex
|
||||||
downloadItem.id = 0L
|
downloadItem.id = 0L
|
||||||
items.add(downloadItem)
|
items.add(downloadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO){
|
|
||||||
repo.update(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (items.isNotEmpty()){
|
if (items.isNotEmpty()){
|
||||||
|
|
||||||
|
|
@ -108,69 +119,73 @@ class ObserveSourceWorker(
|
||||||
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
items.forEach {
|
||||||
items.forEach {
|
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||||
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
d.id = 0
|
||||||
d.id = 0
|
d.logID = null
|
||||||
d.logID = null
|
d.customFileNameTemplate = it.customFileNameTemplate
|
||||||
d.customFileNameTemplate = it.customFileNameTemplate
|
d.status = DownloadRepository.Status.Queued.toString()
|
||||||
d.status = DownloadRepository.Status.Queued.toString()
|
d.toString() == it.toString()
|
||||||
d.toString() == it.toString()
|
}
|
||||||
|
if (existingDownload != null) {
|
||||||
|
it.id = existingDownload.id
|
||||||
|
existing.add(it)
|
||||||
|
}else{
|
||||||
|
//check if downloaded and file exists
|
||||||
|
val history = withContext(Dispatchers.IO){
|
||||||
|
historyRepo.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
||||||
}
|
}
|
||||||
if (existingDownload != null) {
|
|
||||||
it.id = existingDownload.id
|
val existingHistory = history.firstOrNull {
|
||||||
|
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingHistory != null){
|
||||||
|
it.id = existingHistory.id
|
||||||
existing.add(it)
|
existing.add(it)
|
||||||
}else{
|
}else{
|
||||||
//check if downloaded and file exists
|
if (it.id == 0L){
|
||||||
val history = withContext(Dispatchers.IO){
|
it.id = downloadRepo.insert(it)
|
||||||
historyRepo.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
|
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||||
|
downloadRepo.update(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val existingHistory = history.firstOrNull {
|
queuedItems.add(it)
|
||||||
h -> h.command.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "") == parsedCurrentCommand.replace("(-P \"(.*?)\")|(--trim-filenames \"(.*?)\")".toRegex(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingHistory != null){
|
|
||||||
it.id = existingHistory.id
|
|
||||||
existing.add(it)
|
|
||||||
}else{
|
|
||||||
if (it.id == 0L){
|
|
||||||
it.id = downloadRepo.insert(it)
|
|
||||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
|
||||||
downloadRepo.update(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
queuedItems.add(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
|
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
|
||||||
DownloadUtil.startDownloadWorker(queuedItems, context)
|
downloadRepo.startDownloadWorker(queuedItems, context, Data.Builder().putBoolean("createResultItem", false))
|
||||||
|
|
||||||
if(!useScheduler){
|
if(!useScheduler){
|
||||||
queuedItems.filter { it.downloadStartTime != 0L || (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
queuedItems.filter { it.downloadStartTime != 0L || (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||||
try{
|
runCatching {
|
||||||
updateDownloadItem(it, infoUtil, downloadRepo, dbManager.resultDao)
|
resultsRepo.updateDownloadItem(it)?.apply {
|
||||||
}catch (ignored: Exception){}
|
downloadRepo.updateWithoutUpsert(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
||||||
try{
|
runCatching {
|
||||||
updateDownloadItem(it, infoUtil, downloadRepo, dbManager.resultDao)
|
resultsRepo.updateDownloadItem(it)?.apply {
|
||||||
}catch (ignored: Exception){}
|
downloadRepo.updateWithoutUpsert(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.alreadyProcessedLinks.removeAll(items.map { it.url })
|
||||||
item.alreadyProcessedLinks.addAll(items.map { it.url })
|
item.alreadyProcessedLinks.addAll(items.map { it.url })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.runCount = item.runCount + 1
|
||||||
|
|
||||||
if (item.runCount > item.endsAfterCount && item.endsAfterCount > 0){
|
if (item.runCount > item.endsAfterCount && item.endsAfterCount > 0){
|
||||||
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
|
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
|
|
@ -185,10 +200,14 @@ class ObserveSourceWorker(
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
repo.update(item)
|
repo.update(item)
|
||||||
}
|
}
|
||||||
DownloadUtil.cancelObservationTaskByID(context, item.id)
|
repo.cancelObservationTaskByID(context, item.id)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO){
|
||||||
|
repo.update(item)
|
||||||
|
}
|
||||||
|
|
||||||
scheduleForNextTime(item)
|
scheduleForNextTime(item)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
@ -232,28 +251,4 @@ class ObserveSourceWorker(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDownloadItem(
|
|
||||||
downloadItem: DownloadItem,
|
|
||||||
infoUtil: InfoUtil,
|
|
||||||
repository: DownloadRepository,
|
|
||||||
resultDao: ResultDao
|
|
||||||
) : Boolean {
|
|
||||||
var wasQuickDownloaded = false
|
|
||||||
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
|
||||||
runCatching {
|
|
||||||
val info = infoUtil.getMissingInfo(downloadItem.url)
|
|
||||||
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
|
||||||
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
|
|
||||||
downloadItem.duration = info?.duration.toString()
|
|
||||||
downloadItem.website = info?.website.toString()
|
|
||||||
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
|
|
||||||
runBlocking {
|
|
||||||
wasQuickDownloaded = resultDao.getCountInt() == 0
|
|
||||||
repository.updateWithoutUpsert(downloadItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wasQuickDownloaded
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +20,6 @@ import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity
|
import com.deniscerri.ytdlnis.ui.more.terminal.TerminalActivity
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||||
|
|
@ -93,7 +92,7 @@ class TerminalDownloadWorker(
|
||||||
0,
|
0,
|
||||||
"Terminal Task",
|
"Terminal Task",
|
||||||
"Terminal Task\n" +
|
"Terminal Task\n" +
|
||||||
"Command: ${command}\n\n",
|
"Command: \n ${command}\n\n",
|
||||||
Format(),
|
Format(),
|
||||||
DownloadViewModel.Type.command,
|
DownloadViewModel.Type.command,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ package com.deniscerri.ytdlnis.work
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ForegroundInfo
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<vector android:height="24dp"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:width="24dp"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillColor="?android:colorAccent" android:pathData="M19.5,10c0.17,0 0.33,0.03 0.5,0.05L20,1L1,20h13v-3c0,-0.89 0.39,-1.68 1,-2.23v-0.27c0,-2.48 2.02,-4.5 4.5,-4.5zM22,16v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S17,13.12 17,14.5L17,16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zM21,16h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5L21,16z"/>
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,600Q120,567 143.5,543.5Q167,520 200,520L600,520L600,360L680,360L680,520L760,520Q793,520 816.5,543.5Q840,567 840,600L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,600Q760,600 760,600Q760,600 760,600L200,600Q200,600 200,600Q200,600 200,600L200,760Q200,760 200,760Q200,760 200,760ZM280,720Q297,720 308.5,708.5Q320,697 320,680Q320,663 308.5,651.5Q297,640 280,640Q263,640 251.5,651.5Q240,663 240,680Q240,697 251.5,708.5Q263,720 280,720ZM420,720Q437,720 448.5,708.5Q460,697 460,680Q460,663 448.5,651.5Q437,640 420,640Q403,640 391.5,651.5Q380,663 380,680Q380,697 391.5,708.5Q403,720 420,720ZM560,720Q577,720 588.5,708.5Q600,697 600,680Q600,663 588.5,651.5Q577,640 560,640Q543,640 531.5,651.5Q520,663 520,680Q520,697 531.5,708.5Q543,720 560,720ZM570,330L512,272Q538,248 570,234Q602,220 640,220Q678,220 710,234Q742,248 768,272L710,330Q696,316 678.5,308Q661,300 640,300Q619,300 601.5,308Q584,316 570,330ZM470,230L414,174Q458,130 516,105Q574,80 640,80Q706,80 764,105Q822,130 866,174L810,230Q777,197 733.5,178.5Q690,160 640,160Q590,160 546.5,178.5Q503,197 470,230ZM200,760Q200,760 200,760Q200,760 200,760L200,600Q200,600 200,600Q200,600 200,600L200,600Q200,600 200,600Q200,600 200,600L200,760Q200,760 200,760Q200,760 200,760L200,760Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
5
app/src/main/res/drawable/baseline_numbers_24.xml
Normal file
5
app/src/main/res/drawable/baseline_numbers_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M20.5,10L21,8h-4l1,-4h-2l-1,4h-4l1,-4h-2L9,8H5l-0.5,2h4l-1,4h-4L3,16h4l-1,4h2l1,-4h4l-1,4h2l1,-4h4l0.5,-2h-4l1,-4H20.5zM13.5,14h-4l1,-4h4L13.5,14z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<vector android:height="24dp"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:width="24dp"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillColor="?android:colorAccent" android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.89,2 1.99,2H15v-8h5V8L14,2zM13,9V3.5L18.5,9H13zM17,21.66V16h5.66v2h-2.24l2.95,2.95l-1.41,1.41L19,19.41l0,2.24H17z"/>
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent"
|
||||||
|
android:autoMirrored="true">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M280,680L560,680L560,600L280,600L280,680ZM280,520L680,520L680,440L280,440L280,520ZM280,360L680,360L680,280L280,280L280,360ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
<vector android:height="24dp"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:width="24dp"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillColor="?android:colorAccent" android:pathData="M18,6.41l-1.41,-1.41l-4.59,4.58l-4.59,-4.58l-1.41,1.41l6,6z"/>
|
android:viewportWidth="960"
|
||||||
<path android:fillColor="?android:colorAccent" android:pathData="M18,13l-1.41,-1.41l-4.59,4.58l-4.59,-4.58l-1.41,1.41l6,6z"/>
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M120,800L120,720L840,720L840,800L120,800ZM120,240L120,160L840,160L840,240L120,240ZM200,640Q167,640 143.5,616.5Q120,593 120,560L120,400Q120,367 143.5,343.5Q167,320 200,320L760,320Q793,320 816.5,343.5Q840,367 840,400L840,560Q840,593 816.5,616.5Q793,640 760,640L200,640Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
10
app/src/main/res/drawable/ic_dns.xml
Normal file
10
app/src/main/res/drawable/ic_dns.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M300,240Q275,240 257.5,257.5Q240,275 240,300Q240,325 257.5,342.5Q275,360 300,360Q325,360 342.5,342.5Q360,325 360,300Q360,275 342.5,257.5Q325,240 300,240ZM300,640Q275,640 257.5,657.5Q240,675 240,700Q240,725 257.5,742.5Q275,760 300,760Q325,760 342.5,742.5Q360,725 360,700Q360,675 342.5,657.5Q325,640 300,640ZM160,120L800,120Q817,120 828.5,131.5Q840,143 840,160L840,440Q840,457 828.5,468.5Q817,480 800,480L160,480Q143,480 131.5,468.5Q120,457 120,440L120,160Q120,143 131.5,131.5Q143,120 160,120ZM160,520L800,520Q817,520 828.5,531.5Q840,543 840,560L840,840Q840,857 828.5,868.5Q817,880 800,880L160,880Q143,880 131.5,868.5Q120,857 120,840L120,560Q120,543 131.5,531.5Q143,520 160,520Z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_end.xml
Normal file
10
app/src/main/res/drawable/ic_end.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M440,700L440,520L80,520L80,440L440,440L440,260L880,260L880,700L440,700Z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_experiment.xml
Normal file
10
app/src/main/res/drawable/ic_experiment.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M200,840Q149,840 127.5,794.5Q106,749 138,710L360,440L360,200L320,200Q303,200 291.5,188.5Q280,177 280,160Q280,143 291.5,131.5Q303,120 320,120L640,120Q657,120 668.5,131.5Q680,143 680,160Q680,177 668.5,188.5Q657,200 640,200L600,200L600,440L822,710Q854,749 832.5,794.5Q811,840 760,840L200,840ZM280,720L680,720L544,560L416,560L280,720Z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<vector android:height="24dp"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:width="24dp"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:height="24dp"
|
||||||
<path android:fillColor="?android:colorAccent" android:pathData="M12.34,2.02C6.59,1.82 2,6.42 2,12c0,5.52 4.48,10 10,10c3.71,0 6.93,-2.02 8.66,-5.02C13.15,16.73 8.57,8.55 12.34,2.02z"/>
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M312,720Q261,720 214.5,702Q168,684 131,649Q83,604 61.5,542.5Q40,481 40,415Q40,337 78,288.5Q116,240 189,240Q203,240 215.5,242.5Q228,245 241,250L480,339L719,250Q732,245 744.5,242.5Q757,240 771,240Q844,240 882,288.5Q920,337 920,415Q920,481 898.5,542.5Q877,604 829,649Q792,684 745.5,702Q699,720 648,720Q582,720 536,690Q490,660 490,660L470,660Q470,660 424,690Q378,720 312,720ZM349,560Q378,560 395,546.5Q412,533 412,510Q412,471 360,435.5Q308,400 251,400Q222,400 205,413.5Q188,427 188,450Q188,489 240,524.5Q292,560 349,560ZM611,560Q668,560 720,524.5Q772,489 772,450Q772,426 755.5,413Q739,400 709,400Q652,400 600,435.5Q548,471 548,510Q548,533 564.5,546.5Q581,560 611,560Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
10
app/src/main/res/drawable/ic_ipv4.xml
Normal file
10
app/src/main/res/drawable/ic_ipv4.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M600,600L680,600L680,200L600,200L600,360L520,360L520,200L440,200L440,440L600,440L600,600ZM320,720Q287,720 263.5,696.5Q240,673 240,640L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L320,720ZM160,880Q127,880 103.5,856.5Q80,833 80,800L80,240L160,240L160,800Q160,800 160,800Q160,800 160,800L720,800L720,880L160,880Z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_license.xml
Normal file
10
app/src/main/res/drawable/ic_license.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M480,520Q430,520 395,485Q360,450 360,400Q360,350 395,315Q430,280 480,280Q530,280 565,315Q600,350 600,400Q600,450 565,485Q530,520 480,520ZM240,920L240,611Q202,569 181,515Q160,461 160,400Q160,266 253,173Q346,80 480,80Q614,80 707,173Q800,266 800,400Q800,461 779,515Q758,569 720,611L720,920L480,840L240,920ZM480,640Q580,640 650,570Q720,500 720,400Q720,300 650,230Q580,160 480,160Q380,160 310,230Q240,300 240,400Q240,500 310,570Q380,640 480,640ZM320,801L480,760L640,801L640,677Q605,697 564.5,708.5Q524,720 480,720Q436,720 395.5,708.5Q355,697 320,677L320,801ZM480,739L480,739Q480,739 480,739Q480,739 480,739Q480,739 480,739Q480,739 480,739L480,739L480,739L480,739Z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_limitrate.xml
Normal file
10
app/src/main/res/drawable/ic_limitrate.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorAccent">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M84,444L0,360Q92,266 215,213Q338,160 480,160Q504,160 528,161.5Q552,163 576,166L516,282Q504,280 496.5,280Q489,280 480,280Q364,280 262.5,323.5Q161,367 84,444ZM254,614L170,528Q226,472 298.5,439.5Q371,407 456,402L392,532Q353,544 318,564.5Q283,585 254,614ZM452,794Q422,783 405.5,752.5Q389,722 404,692L644,204Q648,196 655.5,193.5Q663,191 672,194Q680,197 684,204Q688,211 686,220L556,746Q548,779 516,792Q484,805 452,794ZM706,614Q700,608 693,602Q686,596 678,590L710,464Q731,479 751.5,494.5Q772,510 790,528L706,614ZM876,444Q846,414 811.5,389Q777,364 738,344L766,224Q820,250 869,284Q918,318 960,360L876,444Z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_music_formatcard.xml
Normal file
5
app/src/main/res/drawable/ic_music_formatcard.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="19dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue