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 versionMajor = 1
|
||||
def versionMinor = 7
|
||||
def versionPatch = 2
|
||||
def versionPatch = 3
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
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" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<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.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
|
|
|||
|
|
@ -13,16 +13,13 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.widget.CheckBox
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.forEach
|
||||
|
|
|
|||
|
|
@ -1,14 +1,30 @@
|
|||
package com.deniscerri.ytdlnis.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.*
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.work.impl.Migration_11_12
|
||||
import androidx.work.impl.Migration_12_13
|
||||
import com.deniscerri.ytdlnis.database.dao.*
|
||||
import com.deniscerri.ytdlnis.database.models.*
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.dao.CookieDao
|
||||
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)
|
||||
@Database(
|
||||
|
|
@ -24,7 +40,7 @@ import com.deniscerri.ytdlnis.database.models.*
|
|||
TerminalItem::class,
|
||||
ObserveSourcesItem::class
|
||||
],
|
||||
version = 14,
|
||||
version = 15,
|
||||
autoMigrations = [
|
||||
AutoMigration (from = 1, to = 2),
|
||||
AutoMigration (from = 2, to = 3),
|
||||
|
|
@ -39,6 +55,7 @@ import com.deniscerri.ytdlnis.database.models.*
|
|||
AutoMigration (from = 11, to = 12),
|
||||
AutoMigration (from = 12, to = 13),
|
||||
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
||||
AutoMigration (from = 14, to = 15),
|
||||
]
|
||||
)
|
||||
abstract class DBManager : RoomDatabase(){
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
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.TemplateShortcut
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
@ -10,7 +14,7 @@ interface CookieDao {
|
|||
fun getAllCookies() : List<CookieItem>
|
||||
|
||||
@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")
|
||||
fun getAllCookiesFlow() : Flow<List<CookieItem>>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
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.DownloadItemSimple
|
||||
import com.deniscerri.ytdlnis.database.models.Format
|
||||
|
|
@ -19,14 +26,14 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in ('Active', 'ActivePaused', 'PausedReQueued')")
|
||||
fun getActiveDownloadsCountFlow() : Flow<Int>
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)")
|
||||
fun getDownloadsCountFlow(statuses: List<String>) : Flow<Int>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||
fun getDownloadsCountByStatusFlow(status : List<String>) : Flow<Int>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:status)")
|
||||
fun getDownloadsCountByStatus(status : List<String>) : Int
|
||||
@Query("SELECT COUNT(*) FROM downloads WHERE status in (:statuses)")
|
||||
fun getDownloadsCountByStatus(statuses : List<String>) : Int
|
||||
|
||||
|
||||
@Query("""
|
||||
|
|
@ -79,6 +86,9 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Queued','QueuedPaused') ORDER BY downloadStartTime, id")
|
||||
fun getQueuedDownloadsListIDs() : List<Long>
|
||||
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||
fun getCancelledDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
|
@ -207,28 +217,30 @@ interface DownloadDao {
|
|||
fun getURLsByStatus(status: List<String>) : List<String>
|
||||
|
||||
@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)")
|
||||
fun reQueueDownloadItems(list: List<Long>)
|
||||
suspend fun reQueueDownloadItems(list: List<Long>)
|
||||
|
||||
@Query("Update downloads SET status='Saved' WHERE status='Processing'")
|
||||
fun updateProcessingtoSavedStatus()
|
||||
|
||||
@Transaction
|
||||
fun putAtTopOfTheQueue(id: Long){
|
||||
val downloads = getQueuedDownloadsList()
|
||||
val newID = downloads.first().id
|
||||
suspend fun putAtTopOfTheQueue(existingIDs: List<Long>){
|
||||
val downloads = getQueuedDownloadsListIDs()
|
||||
val newIDs = downloads.sortedBy { it }.take(existingIDs.size)
|
||||
|
||||
updateDownloadID(id, -id)
|
||||
resetScheduleTimeForItems(listOf(-id))
|
||||
|
||||
downloads.reversed().dropWhile { it.id == id }.forEach {
|
||||
updateDownloadID(it.id, it.id + 1)
|
||||
resetScheduleTimeForItems(existingIDs)
|
||||
existingIDs.forEach { updateDownloadID(it, -it) }
|
||||
downloads.filter { !existingIDs.contains(it) }.reversed().forEach {
|
||||
updateDownloadID(it, it + existingIDs.size)
|
||||
}
|
||||
|
||||
existingIDs.forEachIndexed { idx, it ->
|
||||
updateDownloadID(-it, newIDs[idx])
|
||||
}
|
||||
updateDownloadID(-id, newID)
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.deniscerri.ytdlnis.database.dao
|
||||
|
||||
import androidx.room.*
|
||||
import com.deniscerri.ytdlnis.database.models.CookieItem
|
||||
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.ObserveSourcesItem
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
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 kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
|
@ -27,6 +32,11 @@ interface ResultDao {
|
|||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
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)
|
||||
suspend fun update(item: ResultItem)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
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
|
||||
|
||||
@Dao
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
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.util.Extensions.appendLineToLog
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
|
|
@ -28,16 +32,7 @@ interface TerminalDao {
|
|||
fun updateLog(line: String, id: Long){
|
||||
val t = getTerminalById(id) ?: return
|
||||
val log = t.log ?: ""
|
||||
val lines = log.split("\n")
|
||||
//clean dublicate progress + add newline
|
||||
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)
|
||||
updateTerminalLog(log.appendLineToLog(line), id)
|
||||
}
|
||||
|
||||
@Insert
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import androidx.room.PrimaryKey
|
|||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Month
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
@Entity(tableName = "observeSources")
|
||||
@Parcelize
|
||||
|
|
@ -32,6 +30,8 @@ data class ObserveSourcesItem(
|
|||
var endsAfterCount: Int,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var runCount: Int,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var getOnlyNewUploads: Boolean,
|
||||
var retryMissingDownloads: Boolean,
|
||||
var alreadyProcessedLinks : MutableList<String>
|
||||
) : Parcelable
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
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(
|
||||
var text: String,
|
||||
val type: SearchSuggestionType,
|
||||
|
|
|
|||
|
|
@ -13,5 +13,6 @@ data class VideoPreferences (
|
|||
var writeAutoSubs: Boolean = false,
|
||||
var subsLanguages: String = "en.*,.*-orig",
|
||||
var audioFormatIDs : ArrayList<String> = arrayListOf(),
|
||||
var removeAudio: Boolean = false
|
||||
var removeAudio: Boolean = false,
|
||||
var alsoDownloadAsAudio: Boolean = false
|
||||
) : Parcelable
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class CookieRepository(private val cookieDao: CookieDao) {
|
|||
return cookieDao.getAllCookies()
|
||||
}
|
||||
|
||||
fun getByURL(url: String) : CookieItem {
|
||||
fun getByURL(url: String) : CookieItem? {
|
||||
return cookieDao.getByURL(url)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
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.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.R
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||
|
|
@ -36,12 +53,20 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
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 {
|
||||
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 {
|
||||
return downloadDao.insert(item)
|
||||
}
|
||||
|
|
@ -162,4 +187,53 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
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
|
||||
|
||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||
import com.deniscerri.ytdlnis.database.dao.HistoryDao
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||
|
||||
class HistoryRepository(private val historyDao: HistoryDao) {
|
||||
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.models.LogItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.appendLineToLog
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.regex.MatchResult
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class LogRepository(private val logDao: LogDao) {
|
||||
val items : Flow<List<LogItem>> = logDao.getAllLogsFlow()
|
||||
|
|
@ -43,16 +42,7 @@ class LogRepository(private val logDao: LogDao) {
|
|||
runCatching {
|
||||
val item = getItem(id) ?: return
|
||||
val log = item.content ?: ""
|
||||
val lines = log.split("\n")
|
||||
//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 = log.appendLineToLog(line)
|
||||
//item.content += "\n$line"
|
||||
logDao.update(item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
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.database.dao.ObserveSourcesDao
|
||||
import com.deniscerri.ytdlnis.database.models.ObserveSourcesItem
|
||||
import com.deniscerri.ytdlnis.receiver.ObserveAlarmReceiver
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao) {
|
||||
|
|
@ -57,4 +62,16 @@ class ObserveSourcesRepository(private val observeSourcesDao: ObserveSourcesDao)
|
|||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Patterns
|
||||
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.util.InfoUtil
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
|
||||
private val tag: String = "ResultRepository"
|
||||
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
|
||||
var itemCount = MutableStateFlow(-1)
|
||||
|
||||
enum class SourceType {
|
||||
YOUTUBE_VIDEO, YOUTUBE_PLAYLIST, SEARCH_QUERY, YT_DLP
|
||||
}
|
||||
|
||||
suspend fun insert(it: ResultItem){
|
||||
resultDao.insert(it)
|
||||
}
|
||||
|
|
@ -28,46 +35,52 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
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)
|
||||
if (resetResults) deleteAll()
|
||||
val res = infoUtil.search(inputQuery)
|
||||
itemCount.value = res.size
|
||||
val ids = resultDao.insertMultiple(res)
|
||||
ids.forEachIndexed { index, id ->
|
||||
res[index]?.id = id
|
||||
if (addToResults){
|
||||
val ids = resultDao.insertMultiple(res)
|
||||
ids.forEachIndexed { index, id ->
|
||||
res[index].id = id
|
||||
}
|
||||
}
|
||||
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 v = infoUtil.getYoutubeVideo(inputQuery)
|
||||
val v = infoUtil.getYoutubeVideo(inputQuery) ?: return arrayListOf()
|
||||
if (resetResults) {
|
||||
deleteAll()
|
||||
itemCount.value = v.size
|
||||
}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)
|
||||
ids.forEachIndexed { index, id ->
|
||||
v[index]?.id = id
|
||||
if (addToResults){
|
||||
val ids = resultDao.insertMultiple(v)
|
||||
ids.forEachIndexed { index, id ->
|
||||
v[index].id = id
|
||||
}
|
||||
}
|
||||
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() }
|
||||
.toTypedArray()[1].split("&").first()
|
||||
var nextPageToken = ""
|
||||
if (resetResults) deleteAll()
|
||||
val infoUtil = InfoUtil(context)
|
||||
val items : ArrayList<ResultItem?> = arrayListOf()
|
||||
val items = arrayListOf<ResultItem>()
|
||||
do {
|
||||
val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0]?.playlistTitle ?: "" else "")
|
||||
val ids = resultDao.insertMultiple(tmp.videos.toList())
|
||||
ids.forEachIndexed { index, id ->
|
||||
tmp.videos[index]?.id = id
|
||||
val tmp = infoUtil.getPlaylist(query, nextPageToken, if (items.isNotEmpty()) items[0].playlistTitle else "")
|
||||
if (addToResults){
|
||||
val ids = resultDao.insertMultiple(tmp.videos.toList())
|
||||
ids.forEachIndexed { index, id ->
|
||||
tmp.videos[index].id = id
|
||||
}
|
||||
}
|
||||
items.addAll(tmp.videos)
|
||||
val tmpToken = tmp.nextPageToken
|
||||
|
|
@ -79,19 +92,23 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
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 items = infoUtil.getFromYTDL(inputQuery)
|
||||
val items = infoUtil.getFromYTDL(inputQuery, singleItem)
|
||||
if (resetResults) {
|
||||
deleteAll()
|
||||
itemCount.value = items.size
|
||||
}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 ->
|
||||
items[index]?.id = id
|
||||
|
||||
if (addToResults){
|
||||
val ids = resultDao.insertMultiple(items.toList())
|
||||
ids.forEachIndexed { index, id ->
|
||||
items[index].id = id
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
|
|
@ -111,4 +128,59 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
fun getItemByURL(url: String): ResultItem? {
|
||||
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.models.CookieItem
|
||||
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.util.FileUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.util.Date
|
||||
|
||||
class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
|
|
@ -44,7 +43,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
return repository.getAll()
|
||||
}
|
||||
|
||||
fun getByURL(url: String) : CookieItem {
|
||||
fun getByURL(url: String) : CookieItem? {
|
||||
return repository.getByURL(url)
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +74,7 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
}
|
||||
|
||||
@SuppressLint("SdCardPath")
|
||||
fun getCookiesFromDB() : Result<String> = kotlin.runCatching {
|
||||
fun getCookiesFromDB(url: String) : Result<String> = kotlin.runCatching {
|
||||
CookieManager.getInstance().run {
|
||||
if (!hasCookies()) throw Exception("There is no cookies in the database!")
|
||||
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" }
|
||||
?: throw Exception("Cookies File not found!")
|
||||
|
||||
SQLiteDatabase.openDatabase(
|
||||
val db = SQLiteDatabase.openDatabase(
|
||||
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(
|
||||
WebViewActivity.CookieItem(
|
||||
domain = host,
|
||||
|
|
@ -118,13 +122,13 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
)
|
||||
)
|
||||
}
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,36 +1,25 @@
|
|||
package com.deniscerri.ytdlnis.database.viewmodel
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Looper
|
||||
import android.util.DisplayMetrics
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.await
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
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.repository.DownloadRepository
|
||||
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.util.DownloadUtil
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.work.AlarmScheduler
|
||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -59,7 +47,6 @@ import kotlinx.coroutines.runBlocking
|
|||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
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 activeDownloads : Flow<List<DownloadItem>>
|
||||
val processingDownloads : Flow<List<DownloadItem>>
|
||||
val activeDownloadsCount : Flow<Int>
|
||||
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
|
||||
val erroredDownloads : 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 bestAudioFormat : Format
|
||||
private var defaultVideoFormats : MutableList<Format>
|
||||
|
|
@ -94,6 +86,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
private var audioCodec: String?
|
||||
private val dao: DownloadDao
|
||||
private val historyRepository: HistoryRepository
|
||||
private val resultRepository: ResultRepository
|
||||
|
||||
data class AlreadyExistsUIState(
|
||||
var historyItems: MutableList<Long>,
|
||||
|
|
@ -120,15 +113,21 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
dao = dbManager.downloadDao
|
||||
repository = DownloadRepository(dao)
|
||||
historyRepository = HistoryRepository(dbManager.historyDao)
|
||||
resultRepository = ResultRepository(dbManager.resultDao, application)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||
infoUtil = InfoUtil(application)
|
||||
|
||||
activeDownloadsCount = repository.activeDownloadsCount
|
||||
queuedDownloadsCount = repository.queuedDownloadsCount
|
||||
cancelledDownloadsCount = repository.cancelledDownloadsCount
|
||||
erroredDownloadsCount = repository.erroredDownloadsCount
|
||||
savedDownloadsCount = repository.savedDownloadsCount
|
||||
|
||||
allDownloads = repository.allDownloads.flow
|
||||
queuedDownloads = repository.queuedDownloads.flow
|
||||
activeDownloads = repository.activeDownloads
|
||||
processingDownloads = repository.processingDownloads
|
||||
activeDownloadsCount = repository.activeDownloadsCount
|
||||
savedDownloads = repository.savedDownloads.flow
|
||||
cancelledDownloads = repository.cancelledDownloads.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 {
|
||||
dbManager.downloadDao.resetScheduleTimeForItems(items)
|
||||
DownloadUtil.startDownloadWorker(emptyList(), application)
|
||||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun putAtTopOfQueue(id: Long) = CoroutineScope(Dispatchers.IO).launch{
|
||||
dbManager.downloadDao.putAtTopOfTheQueue(id)
|
||||
DownloadUtil.startDownloadWorker(emptyList(), application)
|
||||
suspend fun putAtTopOfQueue(ids: List<Long>) = CoroutineScope(Dispatchers.IO).launch{
|
||||
dbManager.downloadDao.putAtTopOfTheQueue(ids)
|
||||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun reQueueDownloadItems(items: List<Long>) = CoroutineScope(Dispatchers.IO).launch {
|
||||
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>> {
|
||||
|
|
@ -761,19 +760,27 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
if (queuedItems.isNotEmpty()){
|
||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime()){
|
||||
DownloadUtil.startDownloadWorker(queuedItems, context)
|
||||
repository.startDownloadWorker(queuedItems, context)
|
||||
|
||||
if(!useScheduler){
|
||||
queuedItems.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||
try{
|
||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
runCatching {
|
||||
resultRepository.updateDownloadItem(it)?.apply {
|
||||
repository.updateWithoutUpsert(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
||||
try{
|
||||
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
runCatching {
|
||||
resultRepository.updateDownloadItem(it)?.apply {
|
||||
repository.updateWithoutUpsert(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -819,7 +826,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
items.forEachIndexed { index, i ->
|
||||
i.format = selectedFormats[index].format
|
||||
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 }
|
||||
|
|
@ -829,7 +836,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val items = repository.getProcessingDownloads()
|
||||
items.forEach { i ->
|
||||
i.format = format
|
||||
updateDownload(i)
|
||||
repository.update(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -837,7 +844,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
val items = repository.getProcessingDownloads()
|
||||
items.forEach { i ->
|
||||
i.downloadPath = path
|
||||
updateDownload(i)
|
||||
repository.update(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -866,7 +873,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
dbManager.resultDao.update(this)
|
||||
}
|
||||
}
|
||||
updateDownload(i)
|
||||
repository.update(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -874,7 +881,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
dao.getProcessingDownloadsList().apply {
|
||||
this.forEach {
|
||||
it.status = DownloadRepository.Status.Saved.toString()
|
||||
updateDownload(it)
|
||||
repository.update(it)
|
||||
}
|
||||
|
||||
val ids = this.map { it.id }
|
||||
|
|
@ -900,7 +907,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.getProcessingDownloads().apply {
|
||||
val new = switchDownloadType(this, newType)
|
||||
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() })
|
||||
}
|
||||
|
||||
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.viewModelScope
|
||||
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.repository.HistoryRepository
|
||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||
import com.deniscerri.ytdlnis.database.repository.HistoryRepository.HistorySortType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.database.viewmodel
|
|||
import android.app.AlarmManager
|
||||
import android.app.Application
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
|
|
@ -12,12 +11,9 @@ import androidx.lifecycle.asLiveData
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
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.repository.ObserveSourcesRepository
|
||||
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.launch
|
||||
import java.time.Month
|
||||
|
|
@ -63,13 +59,13 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
}
|
||||
|
||||
fun delete(item: ObserveSourcesItem) = viewModelScope.launch(Dispatchers.IO) {
|
||||
runCatching { DownloadUtil.cancelObservationTaskByID(application, item.id) }
|
||||
runCatching { repository.cancelObservationTaskByID(application, item.id) }
|
||||
repository.delete(item)
|
||||
}
|
||||
|
||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
getAll().forEach {
|
||||
runCatching { DownloadUtil.cancelObservationTaskByID(application, it.id) }
|
||||
runCatching { repository.cancelObservationTaskByID(application, it.id) }
|
||||
}
|
||||
|
||||
repository.deleteAll()
|
||||
|
|
@ -94,7 +90,7 @@ class ObserveSourcesViewModel(private val application: Application) : AndroidVie
|
|||
c.set(Calendar.MINUTE, hourMin.get(Calendar.MINUTE))
|
||||
|
||||
|
||||
DownloadUtil.cancelObservationTaskByID(application, id)
|
||||
repository.cancelObservationTaskByID(application, id)
|
||||
|
||||
val intent = Intent(application, ObserveAlarmReceiver::class.java)
|
||||
intent.putExtra("id", id)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.app.ActivityManager.RunningAppProcessInfo
|
|||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
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.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDLException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.regex.Pattern
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
|
||||
class ResultViewModel(private val application: Application) : AndroidViewModel(application) {
|
||||
|
|
@ -53,6 +52,18 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
errorMessage = 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
|
||||
|
||||
init {
|
||||
|
|
@ -86,41 +97,45 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
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)}
|
||||
return withContext(Dispatchers.IO){
|
||||
val res = mutableListOf<ResultItem?>()
|
||||
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())
|
||||
}
|
||||
fun cancelParsingQueries(){
|
||||
parsingQueries?.cancel(CancellationException())
|
||||
uiState.update {it.copy(processing = false)}
|
||||
}
|
||||
|
||||
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){
|
||||
notificationUtil.showQueriesFinished()
|
||||
}
|
||||
uiState.update {it.copy(processing = false)}
|
||||
res
|
||||
}else{
|
||||
onResult(listOf())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,20 +148,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
}.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){
|
||||
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){
|
||||
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
|
||||
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
updatingData.emit(true)
|
||||
val result = parseQueries(listOf(res.url))
|
||||
updatingData.emit(false)
|
||||
updateResultData.emit(result)
|
||||
updatingData.value = true
|
||||
parseQueries(listOf(res.url)){ result ->
|
||||
viewModelScope.launch(Dispatchers.IO){
|
||||
updatingData.emit(false)
|
||||
updatingData.value = false
|
||||
updateResultData.emit(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateResultDataJob?.start()
|
||||
|
|
@ -218,6 +218,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
if (it != null){
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
updatingData.emit(false)
|
||||
updatingData.value = false
|
||||
updateResultData.emit(mutableListOf())
|
||||
}
|
||||
}
|
||||
|
|
@ -226,13 +227,14 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
|
|||
}
|
||||
|
||||
suspend fun cancelUpdateItemData(){
|
||||
updateResultDataJob?.cancel()
|
||||
updateResultDataJob?.cancel(CancellationException())
|
||||
updatingData.emit(false)
|
||||
updatingData.value = false
|
||||
updateResultData.emit(null)
|
||||
}
|
||||
|
||||
suspend fun cancelUpdateFormatsItemData(){
|
||||
updateFormatsResultDataJob?.cancel()
|
||||
updateFormatsResultDataJob?.cancel(CancellationException())
|
||||
updatingFormats.emit(false)
|
||||
updateFormatsResultData.emit(null)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.deniscerri.ytdlnis.receiver
|
|||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.work.DownloadWorker
|
||||
import com.deniscerri.ytdlnis.work.ObserveSourceWorker
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import com.yausername.youtubedl_android.YoutubeDL
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(c: Context, intent: Intent) {
|
||||
val result = goAsync()
|
||||
val id = intent.getIntExtra("itemID", 0)
|
||||
if (id != 0) {
|
||||
runCatching {
|
||||
|
|
@ -20,13 +22,17 @@ class PauseDownloadNotificationReceiver : BroadcastReceiver() {
|
|||
val notificationUtil = NotificationUtil(c)
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
notificationUtil.createResumeDownload(id, title)
|
||||
val dbManager = DBManager.getInstance(c)
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
runCatching {
|
||||
try {
|
||||
val item = dbManager.downloadDao.getDownloadById(id.toLong())
|
||||
item.status = DownloadRepository.Status.ActivePaused.toString()
|
||||
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.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.deniscerri.ytdlnis.ui
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
|
|
@ -15,14 +14,12 @@ import android.util.Log
|
|||
import android.util.Patterns
|
||||
import android.view.*
|
||||
import android.view.View.*
|
||||
import android.view.animation.Interpolator
|
||||
import android.widget.*
|
||||
import androidx.activity.addCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.forEach
|
||||
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.ui.adapter.HomeAdapter
|
||||
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.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||
|
|
@ -220,7 +216,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
if (inputQueries != null) {
|
||||
resultViewModel.deleteAll()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
resultViewModel.parseQueries(inputQueries!!)
|
||||
resultViewModel.parseQueries(inputQueries!!){}
|
||||
inputQueries = null
|
||||
}
|
||||
}
|
||||
|
|
@ -321,11 +317,25 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
|
||||
requireView().post {
|
||||
checkClipboard().apply {
|
||||
clipboardFab?.isVisible = this != null
|
||||
clipboardFab?.setOnClickListener {
|
||||
searchView!!.setText(this)
|
||||
clipboardFab?.isVisible = false
|
||||
initSearch(searchView!!)
|
||||
this?.apply {
|
||||
clipboardFab?.isVisible = this.isNotEmpty()
|
||||
clipboardFab?.setOnClickListener {
|
||||
if (this.size == 1){
|
||||
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 {
|
||||
|
|
@ -435,6 +445,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
searchBar!!.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
R.id.delete_results -> {
|
||||
resultViewModel.cancelParsingQueries()
|
||||
resultViewModel.getTrending()
|
||||
selectedObjects = ArrayList()
|
||||
searchBar!!.setText("")
|
||||
|
|
@ -489,7 +500,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
|
||||
val url = checkClipboard()
|
||||
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{
|
||||
resultViewModel.parseQueries(queryList)
|
||||
resultViewModel.parseQueries(queryList){}
|
||||
}
|
||||
}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 {
|
||||
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = clipboard.primaryClip!!.getItemAt(0).text
|
||||
val matchesURL = Patterns.WEB_URL.matcher(clip.toString()).matches()
|
||||
return if (matchesURL) clip.toString() else null
|
||||
return clip.split("\r\n").filter { Patterns.WEB_URL.matcher(it).matches() }
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
|
|
@ -850,9 +860,17 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
|
|||
}
|
||||
|
||||
override fun onSearchSuggestionClick(text: String) {
|
||||
clipboardFab?.isVisible = false
|
||||
searchView!!.setText(text)
|
||||
initSearch(searchView!!)
|
||||
val res = text.split("\n")
|
||||
if (res.size == 1){
|
||||
clipboardFab?.isVisible = false
|
||||
searchView!!.setText(text)
|
||||
initSearch(searchView!!)
|
||||
}else{
|
||||
res.forEach {
|
||||
onSearchSuggestionAdd(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onSearchSuggestionAdd(text: String) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
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.repository.DownloadRepository
|
||||
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.popup
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
|
@ -63,7 +60,6 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
val card = holder.cardView
|
||||
card.popup()
|
||||
card.tag = "${item!!.id}##card"
|
||||
val uiHandler = Handler(Looper.getMainLooper())
|
||||
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
||||
|
|
@ -98,7 +94,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
// Author ----------------------------------
|
||||
val author = card.findViewById<TextView>(R.id.author)
|
||||
var info = item.author
|
||||
if (item.duration.isNotEmpty()) {
|
||||
if (item.duration.isNotEmpty() && item.duration != "-1") {
|
||||
if (item.author.isNotEmpty()) info += " • "
|
||||
info += item.duration
|
||||
}
|
||||
|
|
@ -153,6 +149,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
val value = animation.animatedValue as Int
|
||||
pauseButton.cornerRadius = value
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.pause)
|
||||
pauseButton.isEnabled = true
|
||||
pauseButton.tag = ActiveDownloadAction.Pause
|
||||
}
|
||||
|
|
@ -171,6 +168,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
val value = animation.animatedValue as Int
|
||||
pauseButton.cornerRadius = value
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.start)
|
||||
pauseButton.tag = ActiveDownloadAction.Resume
|
||||
pauseButton.isEnabled = true
|
||||
}
|
||||
|
|
@ -180,6 +178,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
DownloadRepository.Status.PausedReQueued -> {
|
||||
progressBar.isIndeterminate = true
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.ic_refresh)
|
||||
pauseButton.contentDescription = activity.getString(R.string.please_wait)
|
||||
pauseButton.tag = null
|
||||
pauseButton.isEnabled = false
|
||||
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.progressindicator.LinearProgressIndicator
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.lang.StringBuilder
|
||||
|
||||
class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadMinifiedAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
DIFF_CALLBACK
|
||||
|
|
@ -122,11 +121,13 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
if (item.status == DownloadRepository.Status.ActivePaused.toString()){
|
||||
progressBar.isIndeterminate = false
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.start)
|
||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume
|
||||
cancelButton.visibility = View.VISIBLE
|
||||
}else{
|
||||
progressBar.isIndeterminate = true
|
||||
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
|
||||
pauseButton.contentDescription = activity.getString(R.string.pause)
|
||||
cancelButton.visibility = View.GONE
|
||||
pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
|
@ -13,16 +12,10 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
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.UiUtil
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AlreadyExistsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<Pair<DownloadItem, Long?>, AlreadyExistsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
|
||||
DIFF_CALLBACK
|
||||
|
|
|
|||
|
|
@ -130,12 +130,15 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
when(item.type) {
|
||||
DownloadViewModel.Type.audio -> {
|
||||
btn.setIconResource(R.drawable.ic_music)
|
||||
btn.contentDescription = activity.getString(R.string.audio)
|
||||
}
|
||||
DownloadViewModel.Type.video -> {
|
||||
btn.setIconResource(R.drawable.ic_video)
|
||||
btn.contentDescription = activity.getString(R.string.video)
|
||||
}
|
||||
else -> {
|
||||
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)
|
||||
when(item.downloadType){
|
||||
DownloadViewModel.Type.audio -> downloadTypeIcon.setIconResource(R.drawable.ic_music)
|
||||
DownloadViewModel.Type.video -> downloadTypeIcon.setIconResource(R.drawable.ic_video)
|
||||
DownloadViewModel.Type.command -> downloadTypeIcon.setIconResource(R.drawable.ic_terminal)
|
||||
DownloadViewModel.Type.audio -> {
|
||||
downloadTypeIcon.setIconResource(R.drawable.ic_music)
|
||||
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 -> {}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,11 +118,21 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
if (actionButton.hasOnClickListeners()) actionButton.setOnClickListener(null)
|
||||
|
||||
when(item.status){
|
||||
DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh)
|
||||
DownloadRepository.Status.Saved.toString() -> actionButton.setIconResource(R.drawable.ic_downloads)
|
||||
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
|
||||
DownloadRepository.Status.Cancelled.toString() -> {
|
||||
actionButton.setIconResource(R.drawable.ic_refresh)
|
||||
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 -> {
|
||||
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
|
||||
actionButton.contentDescription = activity.getString(R.string.logs)
|
||||
if (item.logID == null){
|
||||
actionButton.visibility = View.GONE
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
|||
author.text = item.author
|
||||
|
||||
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 ----------------------------------
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.deniscerri.ytdlnis.ui.adapter
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Color
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -19,7 +17,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.util.Extensions
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
|
@ -72,7 +69,6 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
|||
} else {
|
||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||
}
|
||||
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
||||
}else{
|
||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
package com.deniscerri.ytdlnis.ui.adapter
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
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.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.text.DateFormatSymbols
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
|
|
@ -139,7 +136,6 @@ class ObserveSourcesAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
}
|
||||
}else{
|
||||
info.isVisible = true
|
||||
checkMissing.isVisible = true
|
||||
|
||||
btn.setImageResource(R.drawable.ic_search)
|
||||
btn.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.ui.adapter
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.compose.ui.util.fastJoinToString
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
|
@ -14,7 +13,6 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.util.Extensions.popup
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ import androidx.preference.PreferenceManager
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
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.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
|
|
@ -36,7 +34,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
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() {
|
||||
private lateinit var tabLayout: TabLayout
|
||||
private lateinit var viewPager2: ViewPager2
|
||||
|
|
@ -77,6 +74,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
|
||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||
viewPager2.isUserInputEnabled = sharedPreferences.getBoolean("swipe_gestures_download_card", true)
|
||||
|
||||
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
|
||||
isNestedScrollingEnabled = false
|
||||
|
|
@ -231,6 +229,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
kotlin.runCatching {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ import androidx.preference.PreferenceManager
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||
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.toStringDuration
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import android.widget.ArrayAdapter
|
|||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.view.isVisible
|
||||
|
|
@ -23,7 +22,6 @@ import androidx.core.view.setPadding
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
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.UiUtil
|
||||
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.END_ICON_NONE
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
|
@ -83,7 +80,6 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
downloadItem = withContext(Dispatchers.IO) {
|
||||
if (currentDownloadItem != null){
|
||||
|
|
@ -304,12 +300,22 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||
}
|
||||
},
|
||||
updateDataClicked = {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
resultItem?.apply {
|
||||
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||
rsVM.updateItemData(this)
|
||||
cutDisabledClicked = {
|
||||
val isUpdatingData = ViewModelProvider(requireActivity())[ResultViewModel::class.java].updatingData.value
|
||||
if(isUpdatingData){
|
||||
val snack = Snackbar.make(view, context.getString(R.string.please_wait), Snackbar.LENGTH_SHORT)
|
||||
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 = {
|
||||
|
|
@ -328,6 +334,16 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
|
|||
}catch (e : Exception){
|
||||
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.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
|
|
@ -19,7 +20,11 @@ import android.widget.TextView
|
|||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.get
|
||||
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.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
|
|
@ -139,6 +144,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||
viewPager2 = view.findViewById(R.id.download_viewpager)
|
||||
updateItem = view.findViewById(R.id.update_item)
|
||||
viewPager2.isUserInputEnabled = sharedPreferences.getBoolean("swipe_gestures_download_card", true)
|
||||
|
||||
|
||||
//loading shimmers
|
||||
|
|
@ -307,12 +313,31 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
download.isEnabled = false
|
||||
val item: DownloadItem = getDownloadItem()
|
||||
item.downloadStartTime = it.timeInMillis
|
||||
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()
|
||||
if (item.videoPreferences.alsoDownloadAsAudio){
|
||||
val itemsToQueue = mutableListOf<DownloadItem>()
|
||||
itemsToQueue.add(item)
|
||||
|
||||
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 {
|
||||
|
|
@ -324,10 +349,24 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
scheduleBtn.isEnabled = false
|
||||
download.isEnabled = false
|
||||
val item: DownloadItem = getDownloadItem()
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
if (item.videoPreferences.alsoDownloadAsAudio){
|
||||
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){
|
||||
0 -> {
|
||||
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(){
|
||||
val prevDownloadItem = getDownloadItem(
|
||||
if (viewPager2.currentItem == 1) 0 else 1
|
||||
|
|
@ -591,6 +657,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
kotlin.runCatching {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
|
||||
f.updateSelectedAudioFormat(getDownloadItem(1).videoPreferences.audioFormatIDs.first())
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
|
|
|
|||
|
|
@ -30,17 +30,15 @@ import com.deniscerri.ytdlnis.database.models.Format
|
|||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
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.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
|
||||
import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import android.content.res.Configuration
|
|||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -30,15 +28,10 @@ import androidx.preference.PreferenceManager
|
|||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
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.DownloadViewModel
|
||||
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.InfoUtil
|
||||
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.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
|
@ -63,7 +54,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
|
@ -80,6 +70,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var bottomAppBar: BottomAppBar
|
||||
private lateinit var filesize : TextView
|
||||
private lateinit var count : TextView
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -135,6 +126,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
|
||||
val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
|
||||
filesize = view.findViewById(R.id.filesize)
|
||||
count = view.findViewById(R.id.count)
|
||||
|
||||
|
||||
scheduleBtn.setOnClickListener{
|
||||
|
|
@ -406,7 +398,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
updateDataClicked = {},
|
||||
cutDisabledClicked = {},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
|
|
@ -476,7 +468,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
bottomSheet.dismiss()
|
||||
},
|
||||
cutClicked = {},
|
||||
updateDataClicked = {},
|
||||
cutDisabledClicked = {},
|
||||
filenameTemplateSet = { checked ->
|
||||
items.forEach { it.customFileNameTemplate = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
|
|
@ -497,6 +489,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
items.forEach { it.videoPreferences.removeAudio = checked }
|
||||
CoroutineScope(Dispatchers.IO).launch { items.forEach { downloadViewModel.updateDownload(it) } }
|
||||
},
|
||||
alsoDownloadAsAudioClicked = {},
|
||||
extraCommandsClicked = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
|
|
@ -536,6 +529,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.processingDownloads.collectLatest {
|
||||
count.text = "${it.size} ${getString(R.string.selected)}"
|
||||
listAdapter.submitList(it)
|
||||
withContext(Dispatchers.Main){
|
||||
updateFileSize(it.map { it2 -> it2.format.filesize })
|
||||
|
|
@ -575,7 +569,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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.UiUtil
|
||||
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.END_ICON_CUSTOM
|
||||
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")
|
||||
}
|
||||
},
|
||||
updateDataClicked = {
|
||||
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
|
||||
resultItem?.apply {
|
||||
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
|
||||
rsVM.updateItemData(this)
|
||||
cutDisabledClicked = {
|
||||
val isUpdatingData = ViewModelProvider(requireActivity())[ResultViewModel::class.java].updatingData.value
|
||||
if(isUpdatingData){
|
||||
val snack = Snackbar.make(view, context.getString(R.string.please_wait), Snackbar.LENGTH_SHORT)
|
||||
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 = {
|
||||
|
|
@ -346,6 +357,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
|
|||
downloadItem.videoPreferences.removeAudio = it
|
||||
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 = {
|
||||
val callback = object : ExtraCommandsListener {
|
||||
override fun onChangeExtraCommand(c: String) {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
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.Type
|
||||
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.BottomSheetDialogFragment
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import android.content.SharedPreferences
|
|||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -16,23 +14,20 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.ui.text.capitalize
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
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.BottomSheetDialogFragment
|
||||
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.ChipGroup
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.w3c.dom.Text
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Month
|
||||
import java.time.format.TextStyle
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -102,6 +93,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
private lateinit var endsOnTime: TextInputLayout
|
||||
private lateinit var endsAfterNr: TextInputLayout
|
||||
private lateinit var retryMissingDownloads: MaterialSwitch
|
||||
private lateinit var getOnlyNewUploads: MaterialSwitch
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -114,13 +106,13 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
infoUtil = InfoUtil(requireContext())
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
type = arguments?.getSerializable("type") as Type
|
||||
currentItem = if (Build.VERSION.SDK_INT >= 33){
|
||||
arguments?.getParcelable("item", ObserveSourcesItem::class.java)
|
||||
}else{
|
||||
arguments?.getParcelable<ObserveSourcesItem>("item")
|
||||
}
|
||||
|
||||
type = currentItem?.downloadItemTemplate?.type ?: arguments?.getSerializable("type") as Type
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
|
@ -259,6 +251,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
endsAfter = view.findViewById(R.id.after)
|
||||
endsAfterNr = view.findViewById(R.id.after_nr)
|
||||
retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads)
|
||||
getOnlyNewUploads = view.findViewById(R.id.get_new_uploads)
|
||||
okButton = view.findViewById(R.id.okButton)
|
||||
|
||||
title.editText!!.setText(currentItem?.name ?: "")
|
||||
|
|
@ -451,6 +444,9 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
endsAfter.performClick()
|
||||
}
|
||||
|
||||
retryMissingDownloads.isChecked = currentItem?.retryMissingDownloads ?: false
|
||||
getOnlyNewUploads.isChecked = currentItem?.getOnlyNewUploads ?: false
|
||||
|
||||
if (currentItem != null) okButton.text = getString(R.string.update)
|
||||
okButton.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
|
|
@ -483,6 +479,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
ends,
|
||||
endsAfterCount,
|
||||
0,
|
||||
getOnlyNewUploads.isChecked,
|
||||
retryMissingDownloads.isChecked,
|
||||
mutableListOf()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
import android.widget.*
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.map
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
|
|
@ -39,13 +39,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkManager
|
||||
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.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
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.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
|
|
@ -71,7 +71,8 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
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.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -19,13 +18,11 @@ import androidx.navigation.fragment.findNavController
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.ResultItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
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.setTextAndRecalculateWidth
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ import androidx.work.WorkInfo
|
|||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkQuery
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.ActiveDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
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.NotificationUtil
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
|
|
|
|||
|
|
@ -27,14 +27,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
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.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloads
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
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 (
|
||||
fragmentManager : FragmentManager,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
package com.deniscerri.ytdlnis.ui.downloads
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.icu.text.Transliterator.Position
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.work.WorkManager
|
||||
|
|
@ -20,14 +20,17 @@ import com.deniscerri.ytdlnis.MainActivity
|
|||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
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.UiUtil
|
||||
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.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -40,6 +43,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
private lateinit var viewPager2: ViewPager2
|
||||
private lateinit var fragmentAdapter : DownloadListFragmentAdapter
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -47,13 +51,14 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
mainActivity = activity as MainActivity
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
return inflater.inflate(R.layout.fragment_download_queue_main_screen, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
workManager = WorkManager.getInstance(requireContext())
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
|
||||
topAppBar = view.findViewById(R.id.logs_toolbar)
|
||||
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
||||
|
|
@ -113,6 +118,50 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
viewPager2.setCurrentItem(3, false)
|
||||
}, 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() {
|
||||
|
|
|
|||
|
|
@ -28,14 +28,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
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.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ import android.widget.Toast
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
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.os.bundleOf
|
||||
import androidx.core.view.forEach
|
||||
|
|
@ -43,16 +41,14 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.MainActivity
|
||||
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.repository.HistoryRepository
|
||||
import com.deniscerri.ytdlnis.database.DBManager.SORTING
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.ui.adapter.HistoryAdapter
|
||||
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.MaterialToolbar
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
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.NotificationUtil
|
||||
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.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -201,7 +200,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
|||
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 -> {
|
||||
lifecycleScope.launch {
|
||||
if (adapter.getSelectedObjectsCount(totalSize) == 1){
|
||||
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()
|
||||
val selectedObjects = if (adapter.inverted || adapter.checkedItems.isEmpty()){
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,13 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
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.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.color.MaterialColors
|
||||
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.deniscerri.ytdlnis.MainActivity
|
||||
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.models.CommandTemplate
|
||||
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.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.more
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context.INPUT_METHOD_SERVICE
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
|
|
@ -63,6 +64,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
return inflater.inflate(R.layout.fragment_cookies, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
topAppBar = view.findViewById(R.id.logs_toolbar)
|
||||
|
|
@ -164,7 +166,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
|
||||
private fun showDialog(url: String){
|
||||
lifecycleScope.launch {
|
||||
var item = withContext(Dispatchers.IO){
|
||||
val item = withContext(Dispatchers.IO){
|
||||
cookiesViewModel.getByURL(url)
|
||||
}
|
||||
|
||||
|
|
@ -178,11 +180,15 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
|
|||
editText.setText(text)
|
||||
editText.setSelection(editText.text.length)
|
||||
builder.setView(inputLayout)
|
||||
builder.setNeutralButton(
|
||||
getString(android.R.string.copy)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
UiUtil.copyToClipboard(item.content, requireActivity())
|
||||
|
||||
item?.apply {
|
||||
builder.setNeutralButton(
|
||||
getString(android.R.string.copy)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
UiUtil.copyToClipboard(item.content, requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.get_cookies)
|
||||
) { dialog: DialogInterface?, which: Int ->
|
||||
|
|
|
|||
|
|
@ -4,23 +4,24 @@ import android.annotation.SuppressLint
|
|||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.R
|
||||
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.chip.Chip
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -73,13 +74,8 @@ class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickList
|
|||
)
|
||||
val newSource = view.findViewById<Chip>(R.id.newSource)
|
||||
recyclerView = view.findViewById(R.id.source_recyclerView)
|
||||
recyclerView.layoutManager = LinearLayoutManager(context)
|
||||
recyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
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]
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
|
|
@ -163,63 +159,4 @@ class ObserveSourcesFragment : Fragment(), ObserveSourcesAdapter.OnItemClickList
|
|||
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.WebView
|
||||
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.MaterialToolbar
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
|
@ -57,48 +58,58 @@ class WebViewActivity : BaseActivity() {
|
|||
cookieManager = CookieManager.getInstance()
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
webViewClient = object : AccompanistWebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
kotlin.runCatching {
|
||||
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()
|
||||
lifecycleScope.launch{
|
||||
webViewClient = object : AccompanistWebViewClient() {
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
kotlin.runCatching {
|
||||
toolbar.title = view?.title ?: ""
|
||||
cookies = CookieManager.getInstance().getCookie(view?.url)
|
||||
}
|
||||
}.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")
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import android.widget.LinearLayout
|
|||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
|
|
@ -168,10 +169,12 @@ class DownloadLogFragment : Fragment() {
|
|||
requireActivity().runOnUiThread{
|
||||
if (logItem != null){
|
||||
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 com.deniscerri.ytdlnis.MainActivity
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.DownloadLogsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
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.forceFastScrollMode
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.settings
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.EditTextPreference
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import android.os.Bundle
|
|||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
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.DownloadItem
|
||||
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.TemplateShortcut
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||
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.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UiUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
|
@ -70,6 +71,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var cookieViewModel: CookieViewModel
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
private lateinit var observeSourcesViewModel: ObserveSourcesViewModel
|
||||
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
|
@ -127,6 +129,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
|
||||
observeSourcesViewModel = ViewModelProvider(this)[ObserveSourcesViewModel::class.java]
|
||||
|
||||
backup = findPreference("backup")
|
||||
restore = findPreference("restore")
|
||||
|
|
@ -175,6 +178,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
"templates" -> json.add("templates", backupCommandTemplates() )
|
||||
"shortcuts" -> json.add("shortcuts", backupShortcuts() )
|
||||
"searchHistory" -> json.add("search_history", backupSearchHistory() )
|
||||
"observeSources" -> json.add("observe_sources", backupObserveSources() )
|
||||
}
|
||||
}.onFailure {err ->
|
||||
withContext(Dispatchers.Main){
|
||||
|
|
@ -374,6 +378,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
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(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
|
|
@ -400,204 +418,263 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
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
|
||||
if(json.has("settings")){
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
||||
clear()
|
||||
val prefs = json.getAsJsonArray("settings")
|
||||
prefs.forEach {
|
||||
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
||||
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
||||
"String" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
||||
putString(key, value)
|
||||
}
|
||||
"Boolean" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
||||
Log.e("REST", value.toString())
|
||||
Log.e("REST", key)
|
||||
putBoolean(key, value)
|
||||
}
|
||||
"Int" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
||||
putInt(key, value)
|
||||
}
|
||||
"HashSet" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("(\")|(\\[)|(])|([ \\t])".toRegex(), "").split(",")
|
||||
putStringSet(key, value.toHashSet())
|
||||
kotlin.runCatching {
|
||||
if(json.has("settings")){
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
||||
clear()
|
||||
val prefs = json.getAsJsonArray("settings")
|
||||
prefs.forEach {
|
||||
val key : String = it.asJsonObject.get("key").toString().replace("\"", "")
|
||||
when(it.asJsonObject.get("type").toString().replace("\"", "")){
|
||||
"String" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "")
|
||||
putString(key, value)
|
||||
}
|
||||
"Boolean" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toBoolean()
|
||||
Log.e("REST", value.toString())
|
||||
Log.e("REST", key)
|
||||
putBoolean(key, value)
|
||||
}
|
||||
"Int" -> {
|
||||
val value = it.asJsonObject.get("value").toString().replace("\"", "").toInt()
|
||||
putInt(key, value)
|
||||
}
|
||||
"HashSet" -> {
|
||||
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
|
||||
if(json.has("downloads")){
|
||||
val items = json.getAsJsonArray("downloads")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java)
|
||||
item.id = 0L
|
||||
withContext(Dispatchers.IO){
|
||||
historyViewModel.insert(item)
|
||||
kotlin.runCatching {
|
||||
if(json.has("downloads")){
|
||||
val items = json.getAsJsonArray("downloads")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java)
|
||||
item.id = 0L
|
||||
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
|
||||
if(json.has("queued")){
|
||||
val items = json.getAsJsonArray("queued")
|
||||
val queued = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
queued.add(item)
|
||||
}
|
||||
if (queued.isNotEmpty()) {
|
||||
finalMessage.append("${getString(R.string.in_queue)}: ${queued.count()}\n")
|
||||
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(queued)}
|
||||
kotlin.runCatching {
|
||||
if(json.has("queued")){
|
||||
val items = json.getAsJsonArray("queued")
|
||||
val queued = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
queued.add(item)
|
||||
}
|
||||
if (queued.isNotEmpty()) {
|
||||
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
|
||||
if(json.has("cancelled")){
|
||||
val items = json.getAsJsonArray("cancelled")
|
||||
val cancelled = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
cancelled.add(item)
|
||||
kotlin.runCatching {
|
||||
if(json.has("cancelled")){
|
||||
val items = json.getAsJsonArray("cancelled")
|
||||
val cancelled = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
cancelled.add(item)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cancelled.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(f)
|
||||
cancelled.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(f)
|
||||
}
|
||||
}
|
||||
|
||||
if(cancelled.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
if(cancelled.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n CANCELLED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
}
|
||||
|
||||
//erorred downloads restore
|
||||
if(json.has("errored")){
|
||||
val items = json.getAsJsonArray("errored")
|
||||
val errored = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
errored.add(item)
|
||||
kotlin.runCatching {
|
||||
if(json.has("errored")){
|
||||
val items = json.getAsJsonArray("errored")
|
||||
val errored = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
errored.add(item)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
errored.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(f)
|
||||
errored.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(f)
|
||||
}
|
||||
}
|
||||
|
||||
if(errored.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
if(errored.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n ERRORED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
}
|
||||
|
||||
//erorred downloads restore
|
||||
if(json.has("saved")){
|
||||
val items = json.getAsJsonArray("saved")
|
||||
val saved = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
saved.add(item)
|
||||
//saved downloads restore
|
||||
kotlin.runCatching {
|
||||
if(json.has("saved")){
|
||||
val items = json.getAsJsonArray("saved")
|
||||
val saved = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
saved.add(item)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
saved.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(f)
|
||||
saved.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(f)
|
||||
}
|
||||
}
|
||||
|
||||
if(saved.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.saved)}: ${saved.count()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
if(saved.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.saved)}: ${saved.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n SAVED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
}
|
||||
|
||||
//cookies restore
|
||||
if(json.has("cookies")){
|
||||
val items = json.getAsJsonArray("cookies")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java)
|
||||
item.id = 0L
|
||||
withContext(Dispatchers.IO){
|
||||
cookieViewModel.insert(item)
|
||||
kotlin.runCatching {
|
||||
if(json.has("cookies")){
|
||||
val items = json.getAsJsonArray("cookies")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java)
|
||||
item.id = 0L
|
||||
withContext(Dispatchers.IO){
|
||||
cookieViewModel.insert(item)
|
||||
}
|
||||
}
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.cookies)}: ${items.count()}\n")
|
||||
}
|
||||
}
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.cookies)}: ${items.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n COOKIES RESTORE FAILED: \n ${it.message}")
|
||||
}
|
||||
|
||||
//command template restore
|
||||
if(json.has("templates")){
|
||||
val items = json.getAsJsonArray("templates")
|
||||
val templates = mutableListOf<CommandTemplate>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
|
||||
item.id = 0L
|
||||
templates.add(item)
|
||||
kotlin.runCatching {
|
||||
if(json.has("templates")){
|
||||
val items = json.getAsJsonArray("templates")
|
||||
val templates = mutableListOf<CommandTemplate>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
|
||||
item.id = 0L
|
||||
templates.add(item)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
templates.asReversed().forEach { t ->
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.insert(t)
|
||||
templates.asReversed().forEach { t ->
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.insert(t)
|
||||
}
|
||||
}
|
||||
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n COMMAND TEMPLATE RESTORE FAILED: \n ${it.message}")
|
||||
}
|
||||
|
||||
//shortcuts restore
|
||||
if(json.has("shortcuts")){
|
||||
val items = json.getAsJsonArray("shortcuts")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), TemplateShortcut::class.java)
|
||||
item.id = 0L
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.insertShortcut(item)
|
||||
kotlin.runCatching {
|
||||
if(json.has("shortcuts")){
|
||||
val items = json.getAsJsonArray("shortcuts")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), TemplateShortcut::class.java)
|
||||
item.id = 0L
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.insertShortcut(item)
|
||||
}
|
||||
}
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.shortcuts)}: ${items.count()}\n")
|
||||
}
|
||||
}
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.shortcuts)}: ${items.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n COMMAND SHORTCUTS RESTORE FAILED: \n ${it.message}")
|
||||
}
|
||||
|
||||
|
||||
//search history restore
|
||||
if(json.has("search_history")){
|
||||
val items = json.getAsJsonArray("search_history")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), SearchHistoryItem::class.java)
|
||||
withContext(Dispatchers.IO){
|
||||
resultViewModel.addSearchQueryToHistory(item.query)
|
||||
kotlin.runCatching {
|
||||
if(json.has("search_history")){
|
||||
val items = json.getAsJsonArray("search_history")
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), SearchHistoryItem::class.java)
|
||||
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){
|
||||
finalMessage.append("${getString(R.string.search_history)}: ${items.count()}\n")
|
||||
}
|
||||
}.onFailure {
|
||||
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())
|
||||
builder.setTitle(getString(R.string.restore))
|
||||
builder.setMessage(finalMessage)
|
||||
builder.setMessage("${getString(R.string.restore_complete)}\n $finalMessage \n $errorMessage")
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.restart)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.ui.more.settings
|
|||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdlnis.ui.more.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
|
@ -21,6 +22,7 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
override val title: Int = R.string.updating
|
||||
private var updateYTDL: Preference? = null
|
||||
private var ytdlVersion: Preference? = null
|
||||
private var ytdlSource: Preference? = null
|
||||
private var updateUtil: UpdateUtil? = null
|
||||
private var version: Preference? = null
|
||||
|
||||
|
|
@ -34,52 +36,22 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
val editor = preferences.edit()
|
||||
updateYTDL = findPreference("update_ytdl")
|
||||
ytdlVersion = findPreference("ytdl-version")
|
||||
ytdlSource = findPreference("ytdlp_source")
|
||||
|
||||
YoutubeDL.getInstance().version(context)?.let {
|
||||
editor.putString("ytdl-version", it)
|
||||
editor.apply()
|
||||
ytdlVersion!!.summary = it
|
||||
}
|
||||
|
||||
ytdlSource?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
initYTDLUpdate(editor)
|
||||
true
|
||||
}
|
||||
|
||||
updateYTDL!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
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(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()
|
||||
}
|
||||
}
|
||||
initYTDLUpdate(editor)
|
||||
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.work.WorkManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.ui.adapter.TerminalDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.database.models.TerminalItem
|
||||
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
|
||||
import com.deniscerri.ytdlnis.ui.adapter.TerminalDownloadsAdapter
|
||||
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
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.viewmodel.CommandTemplateViewModel
|
||||
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.NotificationUtil
|
||||
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.bottomappbar.BottomAppBar
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
|
@ -228,6 +228,8 @@ class TerminalFragment : Fragment() {
|
|||
input!!.requestFocus()
|
||||
input!!.setSelection(input!!.text.length)
|
||||
output?.text = bundle?.getString("output") ?: output?.text
|
||||
output?.isVisible = output?.text.toString().isNotEmpty()
|
||||
|
||||
if (bundle?.getBoolean("run") == true){
|
||||
showCancelFab()
|
||||
}
|
||||
|
|
@ -326,6 +328,7 @@ class TerminalFragment : Fragment() {
|
|||
requireActivity().runOnUiThread{
|
||||
if (it != null){
|
||||
if (!it.log.isNullOrBlank()) {
|
||||
output?.isVisible = true
|
||||
output?.text = it.log
|
||||
}
|
||||
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.TextView
|
||||
import androidx.annotation.Px
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.withStarted
|
||||
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.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.util.listener.HighlightTextWatcher
|
||||
import com.neo.highlight.util.scheme.ColorScheme
|
||||
|
|
@ -205,8 +202,6 @@ object Extensions {
|
|||
format.substring(3) else if (this < 36000) format = format.substring(1)
|
||||
return format
|
||||
}
|
||||
|
||||
|
||||
fun View.popup(){
|
||||
val animator = ValueAnimator.ofFloat( 0.75f, 1f)
|
||||
animator.addUpdateListener { animation: ValueAnimator ->
|
||||
|
|
@ -219,6 +214,25 @@ object Extensions {
|
|||
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()) }
|
||||
|
||||
class CustomInterpolator : Interpolator {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import java.io.InputStreamReader
|
|||
import java.lang.reflect.Type
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
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 {
|
||||
when(sharedPreferences.getString("search_engine", "ytsearch")){
|
||||
"ytsearch" -> searchFromPiped(query)
|
||||
|
|
@ -66,8 +67,8 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
fun searchFromPiped(query: String): ArrayList<ResultItem> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val data = genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCODE}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||
|
|
@ -85,8 +86,8 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun searchFromPipedMusic(query: String): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
fun searchFromPipedMusic(query: String): ArrayList<ResultItem> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs®ion=${countryCODE}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||
|
|
@ -106,7 +107,7 @@ class InfoUtil(private val context: Context) {
|
|||
@Throws(JSONException::class)
|
||||
fun getPlaylist(id: String, nextPageToken: String, playlistName: String): PlaylistTuple {
|
||||
try{
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
val items = arrayListOf<ResultItem>()
|
||||
// -------------- PIPED API FUNCTION -------------------
|
||||
var url = ""
|
||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||
|
|
@ -120,10 +121,11 @@ class InfoUtil(private val context: Context) {
|
|||
for (i in 0 until dataArray.length()){
|
||||
kotlin.runCatching {
|
||||
val obj = dataArray.getJSONObject(i)
|
||||
val itm = createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))
|
||||
itm?.playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||
itm?.playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||
items.add(itm)
|
||||
createVideoFromPipedJSON(obj, "https://youtube.com" + obj.getString("url"))?.apply {
|
||||
playlistTitle = runCatching { res.getString("name") }.getOrElse { playlistName }
|
||||
playlistURL = "https://www.youtube.com/playlist?list=$id"
|
||||
items.add(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextpage == "null") nextpage = ""
|
||||
|
|
@ -137,17 +139,25 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun getYoutubeVideo(url: String): List<ResultItem?> {
|
||||
fun getYoutubeVideo(url: String): List<ResultItem>? {
|
||||
val theURL = url.replace("\\?list.*".toRegex(), "")
|
||||
return try {
|
||||
try{
|
||||
val id = getIDFromYoutubeURL(theURL)
|
||||
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){
|
||||
val v = getFromYTDL(theURL)
|
||||
v.forEach { it!!.url = theURL }
|
||||
v
|
||||
v.forEach { it.url = theURL }
|
||||
return v
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createVideofromJSON(obj: JSONObject): ResultItem? {
|
||||
|
|
@ -359,7 +369,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun getTrending(): ArrayList<ResultItem?> {
|
||||
fun getTrending(): ArrayList<ResultItem> {
|
||||
if (sharedPreferences.getString("api_key", "")!!.isNotBlank()){
|
||||
return getTrendingFromYoutubeAPI()
|
||||
}
|
||||
|
|
@ -367,8 +377,8 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun getTrendingFromYoutubeAPI(): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
fun getTrendingFromYoutubeAPI(): ArrayList<ResultItem> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
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"
|
||||
//short data
|
||||
|
|
@ -397,8 +407,8 @@ class InfoUtil(private val context: Context) {
|
|||
return items
|
||||
}
|
||||
|
||||
private fun getTrendingFromPiped(): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
private fun getTrendingFromPiped(): ArrayList<ResultItem> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val url = "$pipedURL/trending?region=$countryCODE"
|
||||
val res = genericArrayRequest(url)
|
||||
for (i in 0 until res.length()) {
|
||||
|
|
@ -438,7 +448,7 @@ class InfoUtil(private val context: Context) {
|
|||
return try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
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")
|
||||
item!!.formats
|
||||
}catch(e: Exception) {
|
||||
|
|
@ -571,8 +581,8 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
|
||||
val items = arrayListOf<ResultItem?>()
|
||||
fun getFromYTDL(query: String, singleItem: Boolean = false): ArrayList<ResultItem> {
|
||||
val items = arrayListOf<ResultItem>()
|
||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||
|
||||
val request : YoutubeDLRequest
|
||||
|
|
@ -596,7 +606,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
request.addOption("--flat-playlist")
|
||||
request.addOption("-j")
|
||||
request.addOption(if (singleItem) "-J" else "-j")
|
||||
request.addOption("--skip-download")
|
||||
request.addOption("-R", "1")
|
||||
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){
|
||||
jsonObject.getString("url")
|
||||
}else{
|
||||
if (Patterns.WEB_URL.matcher(query).matches()){
|
||||
if (Patterns.WEB_URL.matcher(query).matches() && playlistURL?.isEmpty() == true){
|
||||
query
|
||||
}else{
|
||||
jsonObject.getStringByAny("webpage_url", "original_url", "url", )
|
||||
jsonObject.getString("webpage_url")
|
||||
}
|
||||
}
|
||||
|
||||
items.add(ResultItem(0,
|
||||
val res = ResultItem(0,
|
||||
url,
|
||||
title,
|
||||
author,
|
||||
|
|
@ -715,7 +726,8 @@ class InfoUtil(private val context: Context) {
|
|||
playlistURL,
|
||||
playlistIndex
|
||||
)
|
||||
)
|
||||
|
||||
items.add(res)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
|
@ -772,79 +784,6 @@ class InfoUtil(private val context: Context) {
|
|||
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> {
|
||||
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) {
|
||||
request.addOption("--write-thumbnail")
|
||||
request.addOption("--convert-thumbnails", "jpg")
|
||||
request.addOption("--convert-thumbnails", sharedPreferences.getString("thumbnail_format", "jpg")!!)
|
||||
}
|
||||
if (!sharedPreferences.getBoolean("mtime", false)){
|
||||
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)s:%(album_artist)s")
|
||||
if (downloadItem.author.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author))
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||
request.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$")
|
||||
|
||||
if (embedMetadata){
|
||||
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
|
||||
}
|
||||
|
||||
if (downloadItem.downloadSections.isNotBlank()){
|
||||
|
|
@ -1122,7 +1088,7 @@ class InfoUtil(private val context: Context) {
|
|||
"%(section_title&{} |)s$filenameTemplate"
|
||||
}
|
||||
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()){
|
||||
val cache = File(FileUtil.getCachePath(context))
|
||||
cache.mkdirs()
|
||||
val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}.txt")
|
||||
val conf = File(cache.absolutePath + "/${System.currentTimeMillis()}${UUID.randomUUID()}.txt")
|
||||
conf.createNewFile()
|
||||
conf.writeText(downloadItem.extraCommands)
|
||||
request.addOption(
|
||||
|
|
@ -1146,16 +1112,9 @@ class InfoUtil(private val context: Context) {
|
|||
request.addOption("--write-description")
|
||||
}
|
||||
|
||||
filenameTemplate = filenameTemplate.replace("%(uploader)s", "%(uploader,channel)s")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
filenameTemplate = filenameTemplate
|
||||
.replace("%(uploader)s", "%(uploader,channel).30B")
|
||||
.replace("%(title)s", "%(title).170B")
|
||||
}
|
||||
|
||||
if (sharedPreferences.getBoolean("download_archive", false)){
|
||||
|
|
@ -1216,9 +1175,9 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
if (embedMetadata){
|
||||
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", "video:%(uploader)s:%(album_artist)s")
|
||||
|
||||
if (downloadItem.playlistTitle.isNotEmpty()) {
|
||||
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(
|
||||
var nextPageToken: String,
|
||||
var videos: ArrayList<ResultItem?>
|
||||
var videos: ArrayList<ResultItem>
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -7,19 +7,15 @@ import android.app.PendingIntent
|
|||
import android.app.TaskStackBuilder
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.navigation.NavDeepLinkBuilder
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
|
||||
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.ShareFileActivity
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
class NotificationUtil(var context: Context) {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ import android.content.pm.PackageManager
|
|||
import android.text.Spanned
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.google.android.material.color.DynamicColors
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.deniscerri.ytdlnis.util
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
|
@ -10,6 +9,8 @@ import android.content.DialogInterface
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.text.format.DateFormat
|
||||
|
|
@ -25,20 +26,16 @@ import android.widget.Button
|
|||
import android.widget.CheckBox
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.ui.text.toUpperCase
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.core.view.setMargins
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
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.downloadcard.ConfigureDownloadBottomSheetDialog
|
||||
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.enableTextHighlight
|
||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
||||
|
|
@ -94,7 +90,6 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
|
|
@ -496,6 +491,7 @@ object UiUtil {
|
|||
|
||||
fun showDatePicker(fragmentManager: FragmentManager , onSubmit : (chosenDate: Calendar) -> Unit ){
|
||||
val currentDate = Calendar.getInstance()
|
||||
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
|
||||
val date = Calendar.getInstance()
|
||||
|
||||
val datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
|
|
@ -530,6 +526,7 @@ object UiUtil {
|
|||
|
||||
fun showTimePicker(fragmentManager: FragmentManager , onSubmit : (chosenTime: Calendar) -> Unit ){
|
||||
val currentDate = Calendar.getInstance()
|
||||
currentDate.timeInMillis = (currentDate.timeInMillis - (currentDate.timeInMillis % 1800000)) + 1800000
|
||||
val date = Calendar.getInstance()
|
||||
|
||||
val timepicker = MaterialTimePicker.Builder()
|
||||
|
|
@ -1055,16 +1052,22 @@ object UiUtil {
|
|||
val chips = mutableListOf<Chip>()
|
||||
context.getStringArray(R.array.subtitle_langs).forEachIndexed { index, s ->
|
||||
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.setOnClickListener {
|
||||
val c = it as Chip
|
||||
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)
|
||||
}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,
|
||||
sponsorBlockItemsSet: (values: Array<String>, checkedItems: List<Boolean>) -> Unit,
|
||||
cutClicked: (VideoCutListener) -> Unit,
|
||||
cutDisabledClicked: () -> Unit,
|
||||
filenameTemplateSet: (String) -> Unit,
|
||||
saveSubtitlesClicked: (Boolean) -> Unit,
|
||||
saveAutoSubtitlesClicked: (Boolean) -> Unit,
|
||||
subtitleLanguagesSet: (String) -> Unit,
|
||||
removeAudioClicked: (Boolean) -> Unit,
|
||||
extraCommandsClicked: () -> Unit,
|
||||
updateDataClicked: () -> Unit
|
||||
alsoDownloadAsAudioClicked: (Boolean) -> Unit,
|
||||
extraCommandsClicked: () -> Unit
|
||||
){
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
|
|
@ -1322,11 +1326,7 @@ object UiUtil {
|
|||
}else{
|
||||
cut.alpha = 0.3f
|
||||
cut.setOnClickListener {
|
||||
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
|
||||
snack.setAction(R.string.update){
|
||||
updateDataClicked()
|
||||
}
|
||||
snack.show()
|
||||
cutDisabledClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1372,10 +1372,41 @@ object UiUtil {
|
|||
}
|
||||
}
|
||||
|
||||
val removeAudio = view.findViewById<Chip>(R.id.remove_audio)
|
||||
removeAudio.isChecked = items.all { it.videoPreferences.removeAudio }
|
||||
removeAudio.setOnCheckedChangeListener { _, _ ->
|
||||
removeAudioClicked(removeAudio.isChecked)
|
||||
if (items.size == 1 && items.first().id == 0L){
|
||||
val adjustAudio = view.findViewById<Chip>(R.id.adjust_audio)
|
||||
adjustAudio.setOnClickListener {
|
||||
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)
|
||||
|
|
@ -1399,8 +1430,8 @@ object UiUtil {
|
|||
filenameTemplateSet: (String) -> Unit,
|
||||
sponsorBlockItemsSet: (Array<String>, List<Boolean>) -> Unit,
|
||||
cutClicked: (VideoCutListener) -> Unit,
|
||||
extraCommandsClicked: () -> Unit,
|
||||
updateDataClicked: () -> Unit
|
||||
cutDisabledClicked: () -> Unit,
|
||||
extraCommandsClicked: () -> Unit
|
||||
){
|
||||
val embedThumb = view.findViewById<Chip>(R.id.embed_thumb)
|
||||
val cropThumb = view.findViewById<Chip>(R.id.crop_thumb)
|
||||
|
|
@ -1513,11 +1544,7 @@ object UiUtil {
|
|||
}else{
|
||||
cut.alpha = 0.3f
|
||||
cut.setOnClickListener {
|
||||
val snack = Snackbar.make(view, context.getString(R.string.cut_unavailable), Snackbar.LENGTH_SHORT)
|
||||
snack.setAction(R.string.update){
|
||||
updateDataClicked()
|
||||
}
|
||||
snack.show()
|
||||
cutDisabledClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.preference.PreferenceManager
|
|||
import com.deniscerri.ytdlnis.BuildConfig
|
||||
import com.deniscerri.ytdlnis.R
|
||||
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.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
|
|
@ -175,7 +176,7 @@ class UpdateUtil(var context: Context) {
|
|||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
layoutParams.setMargins(20, 20, 20, 0)
|
||||
layoutParams.setMargins(5, 5, 5, 0)
|
||||
scrollView.layoutParams = layoutParams
|
||||
scrollView.addView(linearLayout)
|
||||
val releases = getGithubReleases()
|
||||
|
|
@ -212,7 +213,9 @@ class UpdateUtil(var context: Context) {
|
|||
|
||||
val assetGroup = findViewById<ChipGroup>(R.id.assets)
|
||||
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.id = idx
|
||||
tmp.setOnClickListener {
|
||||
|
|
|
|||
|
|
@ -18,16 +18,15 @@ import androidx.work.WorkManager
|
|||
import androidx.work.WorkerParameters
|
||||
import androidx.work.await
|
||||
import androidx.work.workDataOf
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdlnis.App
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.DBManager
|
||||
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||
import com.deniscerri.ytdlnis.util.Extensions.getMediaDuration
|
||||
import com.deniscerri.ytdlnis.util.Extensions.toStringDuration
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
|
|
@ -36,6 +35,7 @@ import com.deniscerri.ytdlnis.util.NotificationUtil
|
|||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -59,6 +59,7 @@ class DownloadWorker(
|
|||
val historyDao = dbManager.historyDao
|
||||
val resultDao = dbManager.resultDao
|
||||
val logRepo = LogRepository(dbManager.logDao)
|
||||
val resultRepo = ResultRepository(dbManager.resultDao, context)
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val alarmScheduler = AlarmScheduler(context)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
|
@ -67,6 +68,8 @@ class DownloadWorker(
|
|||
val currentWork = WorkManager.getInstance(context).getWorkInfosByTag("download").await()
|
||||
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 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]))
|
||||
|
|
@ -111,8 +114,11 @@ class DownloadWorker(
|
|||
val request = infoUtil.buildYoutubeDLRequest(downloadItem)
|
||||
downloadItem.status = DownloadRepository.Status.Active.toString()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
delay(1500)
|
||||
//update item if its incomplete
|
||||
updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
||||
dao.updateWithoutUpsert(this)
|
||||
}
|
||||
}
|
||||
|
||||
val cacheDir = FileUtil.getCachePath(context)
|
||||
|
|
@ -164,7 +170,10 @@ class DownloadWorker(
|
|||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
resultRepo.updateDownloadItem(downloadItem)?.apply {
|
||||
dao.updateWithoutUpsert(this)
|
||||
}
|
||||
val wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||
runBlocking {
|
||||
var finalPaths : List<String>?
|
||||
|
||||
|
|
@ -178,12 +187,6 @@ class DownloadWorker(
|
|||
.map { it.removeSuffix("'") }
|
||||
.sortedBy { File(it).lastModified() }
|
||||
.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)
|
||||
if (finalPaths.isEmpty()){
|
||||
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)
|
||||
|
||||
//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
|
||||
)
|
||||
|
||||
if (wasQuickDownloaded){
|
||||
if (wasQuickDownloaded && createResultItem){
|
||||
runCatching {
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id))
|
||||
runBlocking {
|
||||
|
|
@ -338,32 +348,6 @@ class DownloadWorker(
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -6,27 +6,23 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import com.deniscerri.ytdlnis.App
|
||||
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.Format
|
||||
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.HistoryRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.ObserveSourcesRepository
|
||||
import com.deniscerri.ytdlnis.database.repository.ResultRepository
|
||||
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.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Calendar
|
||||
|
||||
|
|
@ -41,6 +37,7 @@ class ObserveSourceWorker(
|
|||
if (sourceID == 0L) return Result.failure()
|
||||
val dbManager = DBManager.getInstance(context)
|
||||
val repo = ObserveSourcesRepository(dbManager.observeSourcesDao)
|
||||
val resultsRepo = ResultRepository(dbManager.resultDao, App.instance)
|
||||
val historyRepo = HistoryRepository(dbManager.historyDao)
|
||||
val downloadRepo = DownloadRepository(dbManager.downloadDao)
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
|
@ -48,7 +45,7 @@ class ObserveSourceWorker(
|
|||
|
||||
val item = repo.getByID(sourceID)
|
||||
if (item.status == ObserveSourcesRepository.SourceStatus.STOPPED){
|
||||
DownloadUtil.cancelObservationTaskByID(context, item.id)
|
||||
repo.cancelObservationTaskByID(context, item.id)
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
|
|
@ -57,36 +54,50 @@ class ObserveSourceWorker(
|
|||
val foregroundInfo = ForegroundInfo(workerID, notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
item.runCount = item.runCount + 1
|
||||
|
||||
var res = runCatching { infoUtil.getFromYTDL(item.url).toList() }.getOrElse { listOf() }
|
||||
res = if (!item.retryMissingDownloads){
|
||||
res.filter { result ->
|
||||
//all items that are not present in history
|
||||
!historyRepo.getAllByURL(result!!.url).filter { hi ->
|
||||
hi.downloadPath.any { path -> FileUtil.exists(path) }
|
||||
}.map { it.url }.contains(result.url)
|
||||
val res = runCatching { infoUtil.getFromYTDL(item.url).toList() }.getOrElse { listOf() }
|
||||
.filter { result ->
|
||||
//if first run and get new items only is preferred then dont get anything on first run
|
||||
if (item.getOnlyNewUploads && item.runCount == 0){
|
||||
item.alreadyProcessedLinks.add(result.url)
|
||||
false
|
||||
}else{
|
||||
true
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//all items that are not already processed
|
||||
res.filter { !item.alreadyProcessedLinks.contains(it!!.url) }
|
||||
}
|
||||
.filter { result ->
|
||||
|
||||
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>()
|
||||
|
||||
res.forEach {
|
||||
val string = Gson().toJson(item.downloadItemTemplate, 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
|
||||
items.add(downloadItem)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
repo.update(item)
|
||||
}
|
||||
|
||||
|
||||
if (items.isNotEmpty()){
|
||||
|
||||
|
|
@ -108,69 +119,73 @@ class ObserveSourceWorker(
|
|||
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
d.logID = null
|
||||
d.customFileNameTemplate = it.customFileNameTemplate
|
||||
d.status = DownloadRepository.Status.Queued.toString()
|
||||
d.toString() == it.toString()
|
||||
items.forEach {
|
||||
if (it.status != DownloadRepository.Status.ActivePaused.toString()) it.status = DownloadRepository.Status.Queued.toString()
|
||||
val currentCommand = infoUtil.buildYoutubeDLRequest(it)
|
||||
val parsedCurrentCommand = infoUtil.parseYTDLRequestString(currentCommand)
|
||||
val existingDownload = activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
d.logID = null
|
||||
d.customFileNameTemplate = it.customFileNameTemplate
|
||||
d.status = DownloadRepository.Status.Queued.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)
|
||||
}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 (it.id == 0L){
|
||||
it.id = downloadRepo.insert(it)
|
||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||
downloadRepo.update(it)
|
||||
}
|
||||
|
||||
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)
|
||||
}else{
|
||||
if (it.id == 0L){
|
||||
it.id = downloadRepo.insert(it)
|
||||
}else if (it.status == DownloadRepository.Status.Queued.toString()){
|
||||
downloadRepo.update(it)
|
||||
}
|
||||
|
||||
queuedItems.add(it)
|
||||
}
|
||||
queuedItems.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!useScheduler || alarmScheduler.isDuringTheScheduledTime() || queuedItems.any { it.downloadStartTime > 0L } ){
|
||||
DownloadUtil.startDownloadWorker(queuedItems, context)
|
||||
downloadRepo.startDownloadWorker(queuedItems, context, Data.Builder().putBoolean("createResultItem", false))
|
||||
|
||||
if(!useScheduler){
|
||||
queuedItems.filter { it.downloadStartTime != 0L || (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||
try{
|
||||
updateDownloadItem(it, infoUtil, downloadRepo, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
runCatching {
|
||||
resultsRepo.updateDownloadItem(it)?.apply {
|
||||
downloadRepo.updateWithoutUpsert(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
queuedItems.filter { it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty() }.forEach {
|
||||
try{
|
||||
updateDownloadItem(it, infoUtil, downloadRepo, dbManager.resultDao)
|
||||
}catch (ignored: Exception){}
|
||||
runCatching {
|
||||
resultsRepo.updateDownloadItem(it)?.apply {
|
||||
downloadRepo.updateWithoutUpsert(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.alreadyProcessedLinks.removeAll(items.map { it.url })
|
||||
item.alreadyProcessedLinks.addAll(items.map { it.url })
|
||||
}
|
||||
|
||||
item.runCount = item.runCount + 1
|
||||
|
||||
if (item.runCount > item.endsAfterCount && item.endsAfterCount > 0){
|
||||
item.status = ObserveSourcesRepository.SourceStatus.STOPPED
|
||||
withContext(Dispatchers.IO){
|
||||
|
|
@ -185,10 +200,14 @@ class ObserveSourceWorker(
|
|||
withContext(Dispatchers.IO){
|
||||
repo.update(item)
|
||||
}
|
||||
DownloadUtil.cancelObservationTaskByID(context, item.id)
|
||||
repo.cancelObservationTaskByID(context, item.id)
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
repo.update(item)
|
||||
}
|
||||
|
||||
scheduleForNextTime(item)
|
||||
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.ui.more.terminal.TerminalActivity
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
|
|
@ -93,7 +92,7 @@ class TerminalDownloadWorker(
|
|||
0,
|
||||
"Terminal Task",
|
||||
"Terminal Task\n" +
|
||||
"Command: ${command}\n\n",
|
||||
"Command: \n ${command}\n\n",
|
||||
Format(),
|
||||
DownloadViewModel.Type.command,
|
||||
System.currentTimeMillis(),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.deniscerri.ytdlnis.work
|
|||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<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="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"/>
|
||||
<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,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>
|
||||
|
|
|
|||
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"
|
||||
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="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"/>
|
||||
<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"
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<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="M18,6.41l-1.41,-1.41l-4.59,4.58l-4.59,-4.58l-1.41,1.41l6,6z"/>
|
||||
<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"/>
|
||||
<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="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>
|
||||
|
|
|
|||
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"
|
||||
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="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"/>
|
||||
<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="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>
|
||||
|
|
|
|||
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