history revamp paginated wip

This commit is contained in:
zaednasr 2024-11-13 21:57:07 +01:00
parent 0c00ab350d
commit 8ae2a4d8a3
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
24 changed files with 2249 additions and 322 deletions

View file

@ -0,0 +1,753 @@
{
"formatVersion": 1,
"database": {
"version": 20,
"identityHash": "3be2d00eeed0d077b84c352d10f6628f",
"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, `filesize` INTEGER NOT NULL DEFAULT 0, `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": "filesize",
"columnName": "filesize",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"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, `incognito` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "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
},
{
"fieldPath": "incognito",
"columnName": "incognito",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"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, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "useAsExtraCommandAudio",
"columnName": "useAsExtraCommandAudio",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandVideo",
"columnName": "useAsExtraCommandVideo",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandDataFetching",
"columnName": "useAsExtraCommandDataFetching",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "searchHistory",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "templateShortcuts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cookies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "logs",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadType",
"columnName": "downloadType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadTime",
"columnName": "downloadTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "terminalDownloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "log",
"columnName": "log",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL, `syncWithSource` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadItemTemplate",
"columnName": "downloadItemTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyNr",
"columnName": "everyNr",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "everyCategory",
"columnName": "everyCategory",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyTime",
"columnName": "everyTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "weeklyConfig",
"columnName": "weeklyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "monthlyConfig",
"columnName": "monthlyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "startsTime",
"columnName": "startsTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endsDate",
"columnName": "endsDate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "endsAfterCount",
"columnName": "endsAfterCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "runCount",
"columnName": "runCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "getOnlyNewUploads",
"columnName": "getOnlyNewUploads",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "retryMissingDownloads",
"columnName": "retryMissingDownloads",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ignoredLinks",
"columnName": "ignoredLinks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
},
{
"fieldPath": "alreadyProcessedLinks",
"columnName": "alreadyProcessedLinks",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "syncWithSource",
"columnName": "syncWithSource",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"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, '3be2d00eeed0d077b84c352d10f6628f')"
]
}
}

View file

@ -0,0 +1,753 @@
{
"formatVersion": 1,
"database": {
"version": 21,
"identityHash": "3be2d00eeed0d077b84c352d10f6628f",
"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, `filesize` INTEGER NOT NULL DEFAULT 0, `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": "filesize",
"columnName": "filesize",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"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, `incognito` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "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
},
{
"fieldPath": "incognito",
"columnName": "incognito",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"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, `useAsExtraCommandDataFetching` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "useAsExtraCommandAudio",
"columnName": "useAsExtraCommandAudio",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandVideo",
"columnName": "useAsExtraCommandVideo",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandDataFetching",
"columnName": "useAsExtraCommandDataFetching",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "searchHistory",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "templateShortcuts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cookies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "logs",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadType",
"columnName": "downloadType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadTime",
"columnName": "downloadTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "terminalDownloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "log",
"columnName": "log",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL, `syncWithSource` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadItemTemplate",
"columnName": "downloadItemTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyNr",
"columnName": "everyNr",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "everyCategory",
"columnName": "everyCategory",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyTime",
"columnName": "everyTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "weeklyConfig",
"columnName": "weeklyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "monthlyConfig",
"columnName": "monthlyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "startsTime",
"columnName": "startsTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endsDate",
"columnName": "endsDate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "endsAfterCount",
"columnName": "endsAfterCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "runCount",
"columnName": "runCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "getOnlyNewUploads",
"columnName": "getOnlyNewUploads",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "retryMissingDownloads",
"columnName": "retryMissingDownloads",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ignoredLinks",
"columnName": "ignoredLinks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
},
{
"fieldPath": "alreadyProcessedLinks",
"columnName": "alreadyProcessedLinks",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "syncWithSource",
"columnName": "syncWithSource",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"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, '3be2d00eeed0d077b84c352d10f6628f')"
]
}
}

View file

@ -52,8 +52,6 @@ import com.deniscerri.ytdl.ui.downloads.DownloadQueueMainFragment
import com.deniscerri.ytdl.ui.downloads.HistoryFragment
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
import com.deniscerri.ytdl.util.CrashListener
import com.deniscerri.ytdl.util.Extensions.extractURL
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.NavbarUtil.applyNavBarStyle
import com.deniscerri.ytdl.util.ThemeUtil
@ -440,7 +438,7 @@ class MainActivity : BaseActivity() {
textBuilder.append(c.toChar())
}
val bundle = Bundle()
bundle.putString("url", textBuilder.toString().extractURL())
bundle.putString("url", textBuilder.toString())
navController.popBackStack(R.id.homeFragment, true)
navController.navigate(
R.id.homeFragment,

View file

@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
TerminalItem::class,
ObserveSourcesItem::class
],
version = 19,
version = 21,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@ -61,6 +61,8 @@ import com.deniscerri.ytdl.database.models.TerminalItem
AutoMigration (from = 16, to = 17),
AutoMigration (from = 17, to = 18),
AutoMigration (from = 18, to = 19),
AutoMigration (from = 19, to = 20),
//AutoMigration (from = 20, to = 21) MANUALLY HANDLED
]
)
abstract class DBManager : RoomDatabase(){

View file

@ -5,6 +5,8 @@ import androidx.room.DeleteTable
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.deniscerri.ytdl.database.models.Format
import com.google.gson.Gson
object Migrations {
@ -28,6 +30,19 @@ object Migrations {
// Migration(17, 18 ){ database ->
// database.execSQL("ALTER TABLE `sources` ADD COLUMN `syncWithSource` INTEGER NOT NULL DEFAULT 0")
// }
//add filesizes to history
Migration(20, 21) { database ->
val cursor = database.query("SELECT * FROM history")
while(cursor.moveToNext()) {
kotlin.runCatching {
val id = cursor.getLong(cursor.getColumnIndex("id"))
val format = cursor.getString(cursor.getColumnIndex("format"))
val parsed = Gson().fromJson(format, Format::class.java)
database.execSQL("UPDATE history SET filesize = ${parsed.filesize} WHERE id = $id")
}
}
}
)
@DeleteTable.Entries(

View file

@ -1,10 +1,12 @@
package com.deniscerri.ytdl.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.HistoryItem
import kotlinx.coroutines.flow.Flow
@ -30,9 +32,44 @@ interface HistoryDao {
fun getHistorySortedByAuthor(query : String, type : String, site : String, sort : String) : List<HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN id END ASC," +
"CASE WHEN :sort = 'DESC' THEN id END DESC," +
"CASE WHEN :sort = '' THEN id END DESC ")
fun getHistorySortedByIDPaginated(query : String, type : String, site : String, sort : String) : PagingSource<Int, HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN title END ASC," +
"CASE WHEN :sort = 'DESC' THEN title END DESC," +
"CASE WHEN :sort = '' THEN title END DESC ")
fun getHistorySortedByTitlePaginated(query : String, type : String, site : String, sort : String) : PagingSource<Int, HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN author END ASC," +
"CASE WHEN :sort = 'DESC' THEN author END DESC," +
"CASE WHEN :sort = '' THEN author END DESC ")
fun getHistorySortedByAuthorPaginated(query : String, type : String, site : String, sort : String) : PagingSource<Int, HistoryItem>
@Query("SELECT * FROM history WHERE (title LIKE '%'||:query||'%' OR author LIKE '%'||:query||'%') AND type LIKE '%'||:type||'%' AND website LIKE '%'||:site||'%' ORDER BY " +
"CASE WHEN :sort = 'ASC' THEN filesize END ASC," +
"CASE WHEN :sort = 'DESC' THEN filesize END DESC," +
"CASE WHEN :sort = '' THEN filesize END DESC ")
fun getHistorySortedByFilesizePaginated(query : String, type : String, site : String, sort : String) : PagingSource<Int, HistoryItem>
@Query("SELECT * FROM history")
fun getAllHistory() : Flow<List<HistoryItem>>
@Query("SELECT DISTINCT website FROM history")
fun getWebsites() : Flow<List<String>>
@Query("SELECT COUNT(*) FROM history")
fun getCount() : Flow<Int>
@Query("SELECT * FROM history")
fun getAllHistoryPaginated() : PagingSource<Int, HistoryItem>
@Query("SELECT * FROM history")
fun getAllHistoryList() : List<HistoryItem>

View file

@ -17,5 +17,7 @@ data class CommandTemplate(
@ColumnInfo(defaultValue = "1")
var useAsExtraCommandAudio: Boolean,
@ColumnInfo(defaultValue = "1")
var useAsExtraCommandVideo: Boolean
var useAsExtraCommandVideo: Boolean,
@ColumnInfo(defaultValue = "0")
var useAsExtraCommandDataFetching: Boolean
)

View file

@ -20,6 +20,8 @@ data class HistoryItem(
val website: String,
val format: Format,
@ColumnInfo(defaultValue = "0")
val filesize: Long = 0,
@ColumnInfo(defaultValue = "0")
val downloadId: Long,
@ColumnInfo(defaultValue = "")
val command: String = ""

View file

@ -1,5 +1,10 @@
package com.deniscerri.ytdl.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.filter
import com.deniscerri.ytdl.database.DBManager.SORTING
import com.deniscerri.ytdl.database.dao.HistoryDao
import com.deniscerri.ytdl.database.models.HistoryItem
@ -7,9 +12,12 @@ import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdl.util.FileUtil
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
class HistoryRepository(private val historyDao: HistoryDao) {
val items : Flow<List<HistoryItem>> = historyDao.getAllHistory()
val websites : Flow<List<String>> = historyDao.getWebsites()
val count : Flow<Int> = historyDao.getCount()
enum class HistorySortType {
DATE, TITLE, AUTHOR, FILESIZE
@ -27,6 +35,7 @@ class HistoryRepository(private val historyDao: HistoryDao) {
return historyDao.getAllHistoryByURL(url)
}
fun getFiltered(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryViewModel.HistoryStatus) : List<HistoryItem> {
var filtered = when(sortType){
HistorySortType.DATE -> historyDao.getHistorySortedByID(query, type, site, sort.toString())
@ -54,6 +63,20 @@ class HistoryRepository(private val historyDao: HistoryDao) {
return filtered
}
fun getPaginatedSource(query : String, type : String, site : String, sortType: HistorySortType, sort: SORTING) : PagingSource<Int, HistoryItem> {
val source = when(sortType){
HistorySortType.DATE -> historyDao.getHistorySortedByIDPaginated(query, type, site, sort.toString())
HistorySortType.TITLE -> historyDao.getHistorySortedByTitlePaginated(query, type, site, sort.toString())
HistorySortType.AUTHOR -> historyDao.getHistorySortedByAuthorPaginated(query, type, site, sort.toString())
HistorySortType.FILESIZE -> {
historyDao.getHistorySortedByFilesizePaginated(query, type, site, sort.toString())
}
}
return source
}
suspend fun insert(item: HistoryItem){
historyDao.insert(item)
}

View file

@ -550,7 +550,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
}
return generateCommandFormat(
c ?: CommandTemplate(0, "", preferredTemplateContent, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false)
c ?: CommandTemplate(0, "", preferredTemplateContent, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false)
)
}
}

View file

@ -6,68 +6,109 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.switchMap
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.filter
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.DBManager.SORTING
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.HistoryRepository
import com.deniscerri.ytdl.database.repository.HistoryRepository.HistorySortType
import com.deniscerri.ytdl.util.Extensions
import com.deniscerri.ytdl.util.FileUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.cache
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flattenMerge
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
private val repository : HistoryRepository
val sortOrder = MutableLiveData(SORTING.DESC)
val sortType = MutableLiveData(HistorySortType.DATE)
val websiteFilter = MutableLiveData("")
val statusFilter = MutableLiveData(HistoryStatus.ALL)
private val queryFilter = MutableLiveData("")
private val typeFilter = MutableLiveData("")
val sortOrder = MutableStateFlow(SORTING.DESC)
val sortType = MutableStateFlow(HistorySortType.DATE)
val websiteFilter = MutableStateFlow("")
val statusFilter = MutableStateFlow(HistoryStatus.ALL)
private val queryFilter = MutableStateFlow("")
private val typeFilter = MutableStateFlow("")
enum class HistoryStatus {
UNSET, DELETED, NOT_DELETED, ALL
}
val allItems : LiveData<List<HistoryItem>>
private var _items = MediatorLiveData<List<HistoryItem>>()
var paginatedItems : Flow<PagingData<HistoryItem>>
var websites : Flow<List<String>>
var totalCount : Flow<Int>
data class HistoryFilters(
var type: String = "",
var sortType: HistorySortType = HistorySortType.DATE,
var sortOrder: SORTING = SORTING.DESC,
var query: String = "",
var status: HistoryStatus = HistoryStatus.ALL,
var website: String = ""
)
init {
val dao = DBManager.getInstance(application).historyDao
repository = HistoryRepository(dao)
allItems = repository.items.asLiveData()
_items.addSource(allItems){
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
}
_items.addSource(typeFilter){
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
}
_items.addSource(sortType){
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
}
_items.addSource(websiteFilter){
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
}
_items.addSource(queryFilter){
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
}
websites = repository.websites
totalCount = repository.count
_items.addSource(statusFilter){
filter(queryFilter.value!!, typeFilter.value!!, websiteFilter.value!!, sortType.value!!, sortOrder.value!!, statusFilter.value!!)
}
val filters = listOf(dao.getAllHistory(), sortOrder, sortType, websiteFilter, statusFilter, queryFilter, typeFilter)
paginatedItems = combine(filters) { f ->
val sortOrder = f[1] as SORTING
val sortType = f[2] as HistorySortType
val website = f[3] as String
val status = f[4] as HistoryStatus
val query = f[5] as String
val type = f[6] as String
}
var pager = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {
repository.getPaginatedSource(query, type, website, sortType, sortOrder)
}
).flow
fun getFilteredList() : LiveData<List<HistoryItem>>{
return _items
when(status) {
HistoryStatus.DELETED -> {
pager = pager.map {
it.filter { it2 ->
it2.downloadPath.any { it3 ->
!FileUtil.exists(it3)
}
}
}
}
HistoryStatus.NOT_DELETED -> {
pager = pager.map {
it.filter { it2 ->
it2.downloadPath.any { it3 ->
FileUtil.exists(it3)
}
}
}
}
else -> {}
}
pager
}.flatMapLatest { it }
}
fun setSorting(sort: HistorySortType){
@ -98,7 +139,8 @@ class HistoryViewModel(application: Application) : AndroidViewModel(application)
}
private fun filter(query : String, format : String, site : String, sortType: HistorySortType, sort: SORTING, statusFilter: HistoryStatus) = viewModelScope.launch(Dispatchers.IO){
_items.postValue(repository.getFiltered(query, format, site, sortType, sort, statusFilter))
}
fun getAll() : List<HistoryItem> {

View file

@ -41,8 +41,6 @@ import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.ui.BaseActivity
import com.deniscerri.ytdl.util.Extensions.extractURL
import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -50,7 +48,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.properties.Delegates
import kotlin.system.exitProcess
class ShareActivity : BaseActivity() {

View file

@ -45,6 +45,7 @@ import com.deniscerri.ytdl.ui.adapter.SearchSuggestionsAdapter
import com.deniscerri.ytdl.ui.downloads.HistoryFragment
import com.deniscerri.ytdl.ui.downloads.HistoryFragment.Companion
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.isURL
import com.deniscerri.ytdl.util.NotificationUtil
import com.deniscerri.ytdl.util.ThemeUtil
import com.deniscerri.ytdl.util.UiUtil
@ -231,7 +232,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
val url = requireArguments().getString("url")
if (inputQueries == null) inputQueries = mutableListOf()
searchBar?.setText(url)
val argList = url!!.split("\n").toMutableList()
val argList = url!!.split("\n").filter { it.isURL() }.toMutableList()
argList.removeAll(listOf("", null))
inputQueries!!.addAll(argList)
}

View file

@ -0,0 +1,243 @@
package com.deniscerri.ytdl.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.content.SharedPreferences
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.util.Extensions.loadThumbnail
import com.deniscerri.ytdl.util.Extensions.popup
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.MaterialColors
import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
class HistoryPaginatedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<HistoryItem, HistoryPaginatedAdapter.ViewHolder>(
DIFF_CALLBACK
) {
val checkedItems: MutableSet<Long>
var inverted: Boolean
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences: SharedPreferences
init {
checkedItems = mutableSetOf()
this.inverted = false
this.onItemClickListener = onItemClickListener
this.activity = activity
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: MaterialCardView
init {
cardView = itemView.findViewById(R.id.downloads_card_view)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryPaginatedAdapter.ViewHolder {
return if (viewType == 0){
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.history_card, parent, false)
ViewHolder(cardView, onItemClickListener)
}else{
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.history_card_multiple, parent, false)
ViewHolder(cardView, onItemClickListener)
}
}
override fun getItemViewType(position: Int): Int {
getItem(position)?.apply {
return if(this.downloadPath.size == 1){
0
}else{
1
}
}
return 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) ?: return
val card = holder.cardView
card.tag = item.id.toString()
card.popup()
val uiHandler = Handler(Looper.getMainLooper())
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
// THUMBNAIL ----------------------------------
val hideThumb = sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")
uiHandler.post { thumbnail.loadThumbnail(hideThumb, item!!.thumb) }
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.downloads_title)
var title = item!!.title.ifEmpty { item.url }
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
// Bottom Info ----------------------------------
val author = card.findViewById<TextView>(R.id.downloads_info_bottom)
author.text = item.author
val length = card.findViewById<TextView>(R.id.length)
length.text = if(item.downloadPath.size == 1) item.duration else ""
// TIME DOWNLOADED ----------------------------------
val datetime = card.findViewById<TextView>(R.id.downloads_info_time)
datetime.text = SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.time * 1000L)
// BUTTON ----------------------------------
val btn = card.findViewById<FloatingActionButton>(R.id.downloads_download_button_type)
var filesPresent = true
//IS IN THE FILE SYSTEM?
if (item.downloadPath.all { !File(it).exists() && it.isNotBlank()}) {
filesPresent = false
thumbnail.colorFilter = ColorMatrixColorFilter(object : ColorMatrix() {
init {
setSaturation(0f)
}
})
thumbnail.alpha = 0.7f
btn.backgroundTintList = MaterialColors.getColorStateList(activity, R.attr.colorSurface, ContextCompat.getColorStateList(activity, android.R.color.transparent)!!)
}else{
thumbnail.alpha = 1f
thumbnail.colorFilter = null
btn.backgroundTintList = MaterialColors.getColorStateList(activity, R.attr.colorPrimaryContainer, ContextCompat.getColorStateList(activity, android.R.color.transparent)!!)
}
if (item.type == DownloadViewModel.Type.audio) {
if (filesPresent) btn.setImageResource(R.drawable.ic_music_downloaded) else {
btn.setImageResource(R.drawable.ic_music)
}
} else if (item.type == DownloadViewModel.Type.video) {
if (filesPresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
}else{
if (filesPresent) btn.setImageResource(R.drawable.ic_terminal) else btn.setImageResource(R.drawable.baseline_code_off_24)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = filesPresent
if (checkedItems.contains(item.id)) {
card.isChecked = true
card.strokeWidth = 5
} else {
card.isChecked = false
card.strokeWidth = 0
}
val finalFilePresent = filesPresent
card.setOnLongClickListener {
checkCard(card, item.id)
true
}
card.setOnClickListener {
if (checkedItems.size > 0) {
checkCard(card, item.id)
} else {
onItemClickListener.onCardClick(item.id, finalFilePresent)
}
}
btn.setOnClickListener {
onItemClickListener.onButtonClick(item.id, finalFilePresent)
}
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckedItems() {
inverted = false
checkedItems.clear()
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun checkAll() {
checkedItems.clear()
inverted = true
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun checkMultipleItems(list: List<Long>){
checkedItems.clear()
inverted = false
checkedItems.addAll(list)
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun invertSelected() {
inverted = !inverted
notifyDataSetChanged()
}
fun getSelectedObjectsCount(totalSize: Int) : Int{
return if (inverted){
totalSize - checkedItems.size
}else{
checkedItems.size
}
}
private fun checkCard(card: MaterialCardView, itemID: Long) {
if (card.isChecked) {
card.strokeWidth = 0
if (inverted) checkedItems.add(itemID)
else checkedItems.remove(itemID)
} else {
card.strokeWidth = 5
if (inverted) checkedItems.remove(itemID)
else checkedItems.add(itemID)
}
card.isChecked = !card.isChecked
onItemClickListener.onCardSelect(itemID, card.isChecked)
}
interface OnItemClickListener {
fun onCardClick(itemID: Long, isPresent: Boolean)
fun onButtonClick(itemID: Long, isPresent: Boolean)
fun onCardSelect(itemID: Long, isChecked: Boolean)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<HistoryItem> = object : DiffUtil.ItemCallback<HistoryItem>() {
override fun areItemsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: HistoryItem, newItem: HistoryItem): Boolean {
return oldItem.time == newItem.time
}
}
}
}

View file

@ -93,7 +93,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
if (!Patterns.WEB_URL.matcher(downloadItem.url).matches() && downloadItem.url.endsWith(".txt")){
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false))
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false))
downloadItem.url = ""
}
@ -249,7 +249,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0,
"",
chosenCommandView.editText!!.text.toString(),
useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false
useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false, useAsExtraCommandDataFetching = false
)
UiUtil.showCommandTemplateCreationOrUpdatingSheet(
current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel,

View file

@ -1,7 +1,6 @@
package com.deniscerri.ytdl.ui.downloads
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.graphics.Canvas
@ -16,8 +15,6 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.Window
import android.widget.LinearLayout
@ -45,13 +42,12 @@ import com.deniscerri.ytdl.MainActivity
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.DBManager.SORTING
import com.deniscerri.ytdl.database.models.HistoryItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.HistoryRepository
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdl.ui.adapter.HistoryAdapter
import com.deniscerri.ytdl.ui.downloadcard.FormatSelectionBottomSheetDialog.FormatCategory
import com.deniscerri.ytdl.ui.adapter.HistoryPaginatedAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.NavbarUtil
import com.deniscerri.ytdl.util.UiUtil
@ -73,26 +69,23 @@ import kotlinx.coroutines.withContext
/**
* A fragment representing a list of Items.
*/
class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
class HistoryFragment : Fragment(), HistoryPaginatedAdapter.OnItemClickListener{
private lateinit var historyViewModel : HistoryViewModel
private lateinit var downloadViewModel : DownloadViewModel
private var fragmentView: View? = null
private var activity: Activity? = null
private lateinit var fragmentView: View
private var mainActivity: MainActivity? = null
private var fragmentContext: Context? = null
private var layoutinflater: LayoutInflater? = null
private var topAppBar: MaterialToolbar? = null
private lateinit var layoutinflater: LayoutInflater
private lateinit var topAppBar: MaterialToolbar
private lateinit var recyclerView: RecyclerView
private var historyAdapter: HistoryAdapter? = null
private var sortSheet: BottomSheetDialog? = null
private var uiHandler: Handler? = null
private var noResults: RelativeLayout? = null
private var selectionChips: LinearLayout? = null
private var historyList: List<HistoryItem?>? = null
private var allhistoryList: List<HistoryItem?>? = null
private lateinit var historyAdapter: HistoryPaginatedAdapter
private lateinit var sortSheet: BottomSheetDialog
private lateinit var uiHandler: Handler
private lateinit var noResults: RelativeLayout
private lateinit var selectionChips: LinearLayout
private var websiteList: MutableList<String> = mutableListOf()
private lateinit var selectedObjects: ArrayList<HistoryItem>
private var totalCount = 0
private var actionMode : ActionMode? = null
private lateinit var sortChip: Chip
@ -100,9 +93,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
): View {
fragmentView = inflater.inflate(R.layout.fragment_history, container, false)
activity = getActivity()
mainActivity = activity as MainActivity?
return fragmentView
}
@ -118,24 +110,15 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
selectionChips = view.findViewById(R.id.history_selection_chips)
uiHandler = Handler(Looper.getMainLooper())
sortChip = view.findViewById(R.id.sortChip)
selectedObjects = arrayListOf()
val isInNavBar = NavbarUtil.getNavBarItems(requireActivity()).any { n -> n.itemId == R.id.historyFragment && n.isVisible }
if (isInNavBar) {
topAppBar?.navigationIcon = null
topAppBar.navigationIcon = null
}else{
mainActivity?.hideBottomNavigation()
}
topAppBar?.setNavigationOnClickListener { mainActivity?.onBackPressedDispatcher?.onBackPressed() }
historyList = mutableListOf()
historyAdapter =
HistoryAdapter(
this,
requireActivity()
)
topAppBar.setNavigationOnClickListener { mainActivity?.onBackPressedDispatcher?.onBackPressed() }
historyAdapter = HistoryPaginatedAdapter(this, requireActivity())
recyclerView = view.findViewById(R.id.recyclerviewhistorys)
recyclerView.enableFastScroll()
@ -146,37 +129,44 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
recyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
noResults?.visibility = GONE
noResults.isVisible = false
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
historyViewModel.allItems.observe(viewLifecycleOwner) {
allhistoryList = it
if(it.isEmpty()){
noResults!!.visibility = VISIBLE
selectionChips!!.visibility = GONE
topAppBar!!.menu.children.firstOrNull { m -> m.itemId == R.id.filters }?.isVisible = false
}else{
noResults!!.visibility = GONE
selectionChips!!.visibility = VISIBLE
topAppBar!!.menu.children.firstOrNull { m -> m.itemId == R.id.filters }?.isVisible = true
updateWebsiteChips(it)
recyclerView.adapter = historyAdapter
lifecycleScope.launch {
historyViewModel.paginatedItems.collectLatest {
withContext(Dispatchers.IO){
historyAdapter.submitData(it)
}
}
}
lifecycleScope.launch {
historyViewModel.websites.collectLatest {
if(it.isEmpty()) {
noResults.isVisible = true
selectionChips.isVisible = false
topAppBar.menu.children.firstOrNull { m -> m.itemId == R.id.filters }?.isVisible = false
}else{
noResults.isVisible = false
selectionChips.isVisible = true
topAppBar.menu.children.firstOrNull { m -> m.itemId == R.id.filters }?.isVisible = true
websiteList = mutableListOf()
for (item in it){
if (item == "null" || item.isEmpty()) continue
if (!websiteList.any { it.contentEquals(item, true) }) websiteList.add(item)
}
}
}
}
historyViewModel.getFilteredList().observe(viewLifecycleOwner) {
historyAdapter!!.submitList(it)
historyList = it
if (recyclerView.adapter == null){
recyclerView.adapter = historyAdapter
}else{
scrollToTop()
lifecycleScope.launch {
historyViewModel.totalCount.collectLatest {
totalCount = it
}
}
historyViewModel.sortOrder.observe(viewLifecycleOwner){
if (it != null){
lifecycleScope.launch {
historyViewModel.sortOrder.collectLatest {
when(it){
SORTING.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
SORTING.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
@ -184,20 +174,18 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
}
historyViewModel.sortType.observe(viewLifecycleOwner){
if(it != null){
when(it){
lifecycleScope.launch {
historyViewModel.sortType.collectLatest {
when(it){
HistoryRepository.HistorySortType.AUTHOR -> sortChip.text = getString(R.string.author)
HistoryRepository.HistorySortType.DATE -> sortChip.text = getString(R.string.date_added)
HistoryRepository.HistorySortType.TITLE -> sortChip.text = getString(R.string.title)
HistoryRepository.HistorySortType.FILESIZE -> sortChip.text = getString(R.string.file_size)
}
}
}
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
lifecycleScope.launch{
downloadViewModel.alreadyExistsUiState.collectLatest { res ->
if (res.isNotEmpty()){
@ -212,7 +200,6 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
}
initMenu()
initChips()
}
@ -220,7 +207,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
fun scrollToTop() {
recyclerView.scrollToPosition(0)
Handler(Looper.getMainLooper()).post {
(topAppBar!!.parent as AppBarLayout).setExpanded(
(topAppBar.parent as AppBarLayout).setExpanded(
true,
true
)
@ -238,14 +225,14 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
return true
}
}
topAppBar!!.menu.findItem(R.id.search_history)
topAppBar.menu.findItem(R.id.search_history)
.setOnActionExpandListener(onActionExpandListener)
val searchView = topAppBar!!.menu.findItem(R.id.search_history).actionView as SearchView?
val searchView = topAppBar.menu.findItem(R.id.search_history).actionView as SearchView?
searchView!!.inputType = InputType.TYPE_CLASS_TEXT
searchView.queryHint = getString(R.string.search_history_hint)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
topAppBar!!.menu.findItem(R.id.search_history).collapseActionView()
topAppBar.menu.findItem(R.id.search_history).collapseActionView()
historyViewModel.setQueryFilter(query)
return true
}
@ -255,13 +242,13 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
return true
}
})
topAppBar!!.setOnClickListener { scrollToTop() }
topAppBar.setOnClickListener { scrollToTop() }
val showingDownloadQueue = NavbarUtil.getNavBarItems(requireContext()).any { n -> n.itemId == R.id.downloadQueueMainFragment && n.isVisible }
topAppBar!!.menu.findItem(R.id.download_queue).isVisible = !showingDownloadQueue
topAppBar!!.setOnMenuItemClickListener { m: MenuItem ->
topAppBar.menu.findItem(R.id.download_queue).isVisible = !showingDownloadQueue
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
when (m.itemId) {
R.id.remove_history -> {
if(allhistoryList!!.isEmpty()){
if(websiteList.isEmpty()){
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
}else{
val deleteFile = booleanArrayOf(false)
@ -282,7 +269,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
findNavController().navigate(R.id.downloadQueueMainFragment)
}
R.id.remove_deleted_history -> {
if(allhistoryList!!.isEmpty()){
if(websiteList.isEmpty()){
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
}else{
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
@ -296,7 +283,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
}
R.id.remove_duplicates -> {
if(allhistoryList!!.isEmpty()){
if(websiteList.isEmpty()){
Toast.makeText(context, R.string.history_is_empty, Toast.LENGTH_SHORT).show()
}else{
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
@ -384,7 +371,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
for (i in websiteList.indices) {
val w = websiteList[i]
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
val tmp = layoutinflater.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
tmp.text = w
tmp.id = i
tmp.setOnClickListener {
@ -427,16 +414,16 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
private fun initChips() {
val sortChip = fragmentView!!.findViewById<Chip>(R.id.sortChip)
val sortChip = fragmentView.findViewById<Chip>(R.id.sortChip)
sortChip.setOnClickListener {
sortSheet = BottomSheetDialog(requireContext())
sortSheet!!.requestWindowFeature(Window.FEATURE_NO_TITLE)
sortSheet!!.setContentView(R.layout.history_sort_sheet)
sortSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
sortSheet.setContentView(R.layout.history_sort_sheet)
val date = sortSheet!!.findViewById<TextView>(R.id.date)
val title = sortSheet!!.findViewById<TextView>(R.id.title)
val author = sortSheet!!.findViewById<TextView>(R.id.author)
val filesize = sortSheet!!.findViewById<TextView>(R.id.filesize)
val date = sortSheet.findViewById<TextView>(R.id.date)
val title = sortSheet.findViewById<TextView>(R.id.title)
val author = sortSheet.findViewById<TextView>(R.id.author)
val filesize = sortSheet.findViewById<TextView>(R.id.filesize)
val sortOptions = listOf(date!!, title!!, author!!, filesize!!)
sortOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
@ -469,12 +456,12 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
sortSheet!!.behavior.peekHeight = displayMetrics.heightPixels
sortSheet!!.show()
sortSheet.behavior.peekHeight = displayMetrics.heightPixels
sortSheet.show()
}
//format
val audio = fragmentView!!.findViewById<Chip>(R.id.audio_chip)
val audio = fragmentView.findViewById<Chip>(R.id.audio_chip)
audio.setOnClickListener {
if (audio.isChecked) {
historyViewModel.setTypeFilter(DownloadViewModel.Type.audio.toString())
@ -484,7 +471,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
audio.isChecked = false
}
}
val video = fragmentView!!.findViewById<Chip>(R.id.video_chip)
val video = fragmentView.findViewById<Chip>(R.id.video_chip)
video.setOnClickListener {
if (video.isChecked) {
historyViewModel.setTypeFilter(DownloadViewModel.Type.video.toString())
@ -494,7 +481,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
video.isChecked = false
}
}
val command = fragmentView!!.findViewById<Chip>(R.id.command_chip)
val command = fragmentView.findViewById<Chip>(R.id.command_chip)
command.setOnClickListener {
if (command.isChecked) {
historyViewModel.setTypeFilter(DownloadViewModel.Type.command.toString())
@ -508,71 +495,68 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
private fun updateWebsiteChips(list : List<HistoryItem>) {
websiteList = mutableListOf()
for (item in list){
if (item.website == "null" || item.website.isEmpty()) continue
if (!websiteList.any { it.contentEquals(item.website, true) }) websiteList.add(item.website)
}
}
override fun onCardClick(itemID: Long, isPresent: Boolean) {
val item = findItem(itemID)
UiUtil.showHistoryItemDetailsCard(item, requireActivity(), isPresent,
removeItem = { it, deleteFile ->
historyViewModel.delete(it, deleteFile)
},
redownloadItem = {
val downloadItem = downloadViewModel.createDownloadItemFromHistory(it)
runBlocking{
downloadViewModel.queueDownloads(listOf(downloadItem))
}
historyViewModel.delete(it, false)
},
redownloadShowDownloadCard = {
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
Pair("result", downloadViewModel.createResultItemFromHistory(it)),
Pair("type", it.type),
))
lifecycleScope.launch {
val item = withContext(Dispatchers.IO){
historyViewModel.getByID(itemID)
}
)
UiUtil.showHistoryItemDetailsCard(item, requireActivity(), isPresent,
removeItem = { it, deleteFile ->
historyViewModel.delete(it, deleteFile)
},
redownloadItem = {
val downloadItem = downloadViewModel.createDownloadItemFromHistory(it)
runBlocking{
downloadViewModel.queueDownloads(listOf(downloadItem))
}
historyViewModel.delete(it, false)
},
redownloadShowDownloadCard = {
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
Pair("result", downloadViewModel.createResultItemFromHistory(it)),
Pair("type", it.type),
))
}
)
}
}
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
lifecycleScope.launch {
val item = findItem(itemID)
if (actionMode == null) actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
actionMode?.apply {
if (isChecked) selectedObjects.add(item!!)
else selectedObjects.remove(item!!)
val item = withContext(Dispatchers.IO) {
historyViewModel.getByID(itemID)
}
if (selectedObjects.size == 0){
if (actionMode == null) actionMode = (activity as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
val selectedObjects = historyAdapter.getSelectedObjectsCount(totalCount)
actionMode?.apply {
if (selectedObjects == 0){
this.finish()
}else{
actionMode?.title = "${selectedObjects.size} ${getString(R.string.selected)}"
actionMode?.title = "$selectedObjects ${getString(R.string.selected)}"
this.menu.findItem(R.id.select_between).isVisible = false
if(selectedObjects.size == 2){
val selectedIDs = selectedObjects.sortedBy { it.id }
val resultsInMiddle = withContext(Dispatchers.IO){
historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
}.toMutableList()
this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
if(selectedObjects == 2){
//TODO
// val selectedIDs = contextualActionBar.getSelectedIDs().sortedBy { it }
// val resultsInMiddle = withContext(Dispatchers.IO){
// historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
// }.toMutableList()
// this.menu.findItem(R.id.select_between).isVisible = resultsInMiddle.isNotEmpty()
}
}
}
}
}
private fun findItem(id : Long): HistoryItem? {
return historyList?.find { it?.id == id }
}
private val contextualActionBar = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
mode!!.menuInflater.inflate(R.menu.history_menu_context, menu)
mode.title = "${selectedObjects.size} ${getString(R.string.selected)}"
mode.title = "${historyAdapter.getSelectedObjectsCount(totalCount)} ${getString(R.string.selected)}"
(activity as MainActivity).disableBottomNavigation()
topAppBar!!.menu.forEach { it.isEnabled = false }
topAppBar.menu.forEach { it.isEnabled = false }
return true
}
@ -589,62 +573,66 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
): Boolean {
return when (item!!.itemId) {
R.id.select_between -> {
lifecycleScope.launch {
val selectedIDs = selectedObjects.sortedBy { it.id }
val resultsInMiddle = withContext(Dispatchers.IO){
historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
}.toMutableList()
if (resultsInMiddle.isNotEmpty()){
selectedObjects.addAll(resultsInMiddle)
historyAdapter?.checkMultipleItems(selectedObjects.map { it.id })
actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}"
}
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
}
//TODO
// lifecycleScope.launch {
// val selectedIDs = selectedObjects.sortedBy { it.id }
// val resultsInMiddle = withContext(Dispatchers.IO){
// historyViewModel.getRecordsBetweenTwoItems(selectedIDs.first().id, selectedIDs.last().id)
// }.toMutableList()
// if (resultsInMiddle.isNotEmpty()){
// selectedObjects.addAll(resultsInMiddle)
// historyAdapter.checkMultipleItems(selectedObjects.map { it.id })
// actionMode?.title = "${selectedObjects.count()} ${getString(R.string.selected)}"
// }
// mode?.menu?.findItem(R.id.select_between)?.isVisible = false
// }
true
}
R.id.delete_results -> {
val deleteFile = booleanArrayOf(false)
val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
deleteDialog.setMultiChoiceItems(
arrayOf(getString(R.string.delete_files_too)),
booleanArrayOf(false)
) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
for (obj in selectedObjects){
historyViewModel.delete(obj, deleteFile[0])
}
clearCheckedItems()
actionMode?.finish()
}
deleteDialog.show()
//TODO
// val deleteFile = booleanArrayOf(false)
// val deleteDialog = MaterialAlertDialogBuilder(fragmentContext!!)
// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
// deleteDialog.setMultiChoiceItems(
// arrayOf(getString(R.string.delete_files_too)),
// booleanArrayOf(false)
// ) { _: DialogInterface?, _: Int, b: Boolean -> deleteFile[0] = b }
// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
// for (obj in selectedObjects){
// historyViewModel.delete(obj, deleteFile[0])
// }
// clearCheckedItems()
// actionMode?.finish()
// }
// deleteDialog.show()
true
}
R.id.share -> {
FileUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten())
clearCheckedItems()
actionMode?.finish()
//TODO
// FileUtil.shareFileIntent(requireContext(), selectedObjects.map { it.downloadPath }.flatten())
// clearCheckedItems()
// actionMode?.finish()
true
}
R.id.select_all -> {
historyAdapter?.checkAll(historyList)
selectedObjects.clear()
historyList?.forEach { selectedObjects.add(it!!) }
mode?.title = "(${selectedObjects.size}) ${resources.getString(R.string.all_items_selected)}"
//TODO
// historyAdapter.checkAll()
// historyList?.forEach { selectedObjects.add(it!!) }
// mode?.title = "(${selectedObjects.size}) ${resources.getString(R.string.all_items_selected)}"
true
}
R.id.invert_selected -> {
historyAdapter?.invertSelected(historyList)
val invertedList = arrayListOf<HistoryItem>()
historyList?.forEach {
if (!selectedObjects.contains(it)!!) invertedList.add(it!!)
}
selectedObjects.clear()
selectedObjects.addAll(invertedList)
actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
if (invertedList.isEmpty()) actionMode?.finish()
//TODO
// historyAdapter.invertSelected()
// val invertedList = arrayListOf<HistoryItem>()
// historyList?.forEach {
// if (!selectedObjects.contains(it)!!) invertedList.add(it!!)
// }
// selectedObjects.clear()
// selectedObjects.addAll(invertedList)
// actionMode!!.title = "${selectedObjects.size} ${getString(R.string.selected)}"
// if (invertedList.isEmpty()) actionMode?.finish()
true
}
else -> false
@ -654,15 +642,23 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
(activity as MainActivity).enableBottomNavigation()
clearCheckedItems()
topAppBar!!.menu.forEach { it.isEnabled = true }
historyAdapter.clearCheckedItems()
topAppBar.menu.forEach { it.isEnabled = true }
}
suspend fun getSelectedIDs() : List<Long>{
//TODO
// return if (historyAdapter.inverted || historyAdapter.checkedItems.isEmpty()) {
// withContext(Dispatchers.IO){
// historyViewModel.getItemIDsNotPresentIn(historyAdapter.checkedItems.toList())
// }
// }else{
// historyAdapter.checkedItems.toList()
// }
return listOf()
}
}
private fun clearCheckedItems(){
historyAdapter?.clearCheckeditems()
selectedObjects.clear()
}
private var simpleCallback: ItemTouchHelper.SimpleCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
@ -672,34 +668,44 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val itemID = viewHolder.itemView.tag.toString().toLong()
val position = viewHolder.bindingAdapterPosition
when (direction) {
ItemTouchHelper.LEFT -> {
val deletedItem = historyList!![position]
historyAdapter!!.notifyItemChanged(position)
UiUtil.showRemoveHistoryItemDialog(deletedItem!!, requireActivity(),
delete = { item, deleteFile ->
lifecycleScope.launch {
withContext(Dispatchers.IO){
historyViewModel.delete(item, deleteFile)
}
lifecycleScope.launch {
val deletedItem = withContext(Dispatchers.IO) {
historyViewModel.getByID(itemID)
}
historyAdapter.notifyItemChanged(position)
UiUtil.showRemoveHistoryItemDialog(deletedItem, requireActivity(),
delete = { item, deleteFile ->
lifecycleScope.launch {
withContext(Dispatchers.IO){
historyViewModel.delete(item, deleteFile)
}
if (!deleteFile){
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
historyViewModel.insert(deletedItem)
}.show()
if (!deleteFile){
Snackbar.make(recyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_INDEFINITE)
.setAction(getString(R.string.undo)) {
historyViewModel.insert(deletedItem)
}.show()
}
}
}
})
)
}
}
ItemTouchHelper.RIGHT -> {
val item = historyList!![position]!!
historyAdapter!!.notifyItemChanged(position)
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
Pair("result", downloadViewModel.createResultItemFromHistory(item)),
Pair("type", item.type)
))
lifecycleScope.launch {
val item = withContext(Dispatchers.IO) {
historyViewModel.getByID(itemID)
}
historyAdapter.notifyItemChanged(position)
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
Pair("result", downloadViewModel.createResultItemFromHistory(item)),
Pair("type", item.type)
))
}
}
}
}
@ -737,7 +743,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
super.onChildDraw(
c,
recyclerView,
viewHolder!!,
viewHolder,
dX,
dY,
actionState,
@ -748,7 +754,13 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onButtonClick(itemID: Long, isPresent: Boolean) {
if (isPresent){
FileUtil.shareFileIntent(requireContext(), historyList!!.first { it!!.id == itemID }!!.downloadPath)
lifecycleScope.launch {
val item = withContext(Dispatchers.IO){
historyViewModel.getByID(itemID)
}
FileUtil.shareFileIntent(requireContext(), item.downloadPath)
}
}
}
companion object {

View file

@ -49,6 +49,12 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
true
}
ytdlVersion!!.setOnPreferenceClickListener {
initYTDLUpdate(editor)
true
}
updateYTDL!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
initYTDLUpdate(editor)

View file

@ -46,9 +46,6 @@ import com.deniscerri.ytdl.database.models.Format
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
import com.deniscerri.ytdl.database.repository.DownloadRepository
import com.deniscerri.ytdl.database.repository.ObserveSourcesRepository.EveryCategory
import com.deniscerri.ytdl.util.Extensions.isYoutubeChannelURL
import com.deniscerri.ytdl.util.Extensions.isYoutubeURL
import com.deniscerri.ytdl.util.Extensions.toTimePeriodsArray
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.tabs.TabLayout
@ -59,6 +56,7 @@ import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import com.squareup.picasso.Picasso
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
@ -526,11 +524,43 @@ object Extensions {
}
fun String.extractURL() : String {
val res = Pattern.compile("(http|ftp|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])").matcher(this)
return if (res.find()){
val res =
Pattern.compile("(http|ftp|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])")
.matcher(this)
return if (res.find()) {
res.group()
}else{
} else {
this
}
}
fun String.isURL(): Boolean {
return Pattern.compile("(http|ftp|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])").matcher(this).find()
}
fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
flow7: Flow<T7>,
transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
): Flow<R> = combine(
flow,
combine(flow2, flow3, ::Pair),
combine(flow4, flow5, ::Pair),
combine(flow6, flow7, ::Pair),
) { t1, t2, t3, t4, ->
transform(
t1,
t2.first,
t2.second,
t3.first,
t3.second,
t4.first,
t4.second
)
}
}

View file

@ -191,6 +191,7 @@ object UiUtil {
val extraCommandsSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandsSwitch)!!
val extraCommandsAudio : CheckBox = bottomSheet.findViewById(R.id.checkbox_audio)!!
val extraCommandsVideo : CheckBox = bottomSheet.findViewById(R.id.checkbox_video)!!
val extraCommandsDataFetchingSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandDataFetching)!!
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
@ -252,6 +253,7 @@ object UiUtil {
if (item != null){
preferredCommandSwitch.isChecked = item.content == sharedPreferences.getString("preferred_command_template", "")
extraCommandsDataFetchingSwitch.isChecked = item.useAsExtraCommandDataFetching
extraCommandsSwitch.isChecked = item.useAsExtraCommand
if (item.useAsExtraCommand){
@ -307,7 +309,7 @@ object UiUtil {
if (item == null){
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked)
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked, extraCommandsDataFetchingSwitch.isChecked)
commandTemplateViewModel.insert(t)
newTemplate(t)
}else{
@ -316,6 +318,7 @@ object UiUtil {
item.useAsExtraCommand = extraCommandsSwitch.isChecked
item.useAsExtraCommandAudio = extraCommandsAudio.isChecked
item.useAsExtraCommandVideo = extraCommandsVideo.isChecked
item.useAsExtraCommandDataFetching = extraCommandsDataFetchingSwitch.isChecked
commandTemplateViewModel.update(item)
newTemplate(item)
}

View file

@ -113,8 +113,8 @@ class DownloadWorker(
setForegroundAsync(ForegroundInfo(notificationID, workNotif))
}
queuedItems.collectLatest { items ->
if (this@DownloadWorker.isStopped) return@collectLatest
queuedItems.collect { items ->
if (this@DownloadWorker.isStopped) return@collect
runningYTDLInstances.clear()
val activeDownloads = dao.getActiveDownloadsList()
@ -126,19 +126,19 @@ class DownloadWorker(
val useScheduler = sharedPreferences.getBoolean("use_scheduler", false)
if (items.isEmpty() && running.isEmpty()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collectLatest
return@collect
}
if (useScheduler){
if (items.none{it.downloadStartTime > 0L} && running.isEmpty() && !alarmScheduler.isDuringTheScheduledTime()) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collectLatest
return@collect
}
}
if (priorityItemIDs.isEmpty() && !continueAfterPriorityIds) {
WorkManager.getInstance(context).cancelWorkById(this@DownloadWorker.id)
return@collectLatest
return@collect
}
val concurrentDownloads = sharedPreferences.getInt("concurrent_downloads", 1) - running.size
@ -312,6 +312,7 @@ class DownloadWorker(
finalPaths,
downloadItem.website,
downloadItem.format,
downloadItem.format.filesize,
downloadItem.id,
commandString)
historyDao.insert(historyItem)

View file

@ -157,6 +157,20 @@
</LinearLayout>
<View
style="@style/Divider.Horizontal"
android:layout_marginVertical="10dp" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/extraCommandDataFetching"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="10dp"
android:checked="false"
android:text="@string/data_fetching_extra_command"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent" />
<View
style="@style/Divider.Horizontal"
android:layout_marginVertical="10dp" />

View file

@ -434,6 +434,6 @@
<string name="socket_timeout">Socket Timeout</string>
<string name="socket_timeout_description">Time to wait before giving up, in seconds</string>
<string name="advanced">Advanced</string>
<string name="data_fetching_extra_commands">Data Fetching Extra Commands</string>
<string name="data_fetching_extra_command">Data Fetching Extra Command</string>
<string name="thumbnail">Thumbnail</string>
</resources>

View file

@ -2,32 +2,27 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
android:defaultValue="default,mediaconnect"
android:icon="@drawable/ic_code"
android:key="youtube_player_client"
app:useSimpleSummaryProvider="true"
android:title="Player Client" />
<PreferenceCategory android:title="YouTube">
<EditTextPreference
android:defaultValue="default,mediaconnect"
android:icon="@drawable/ic_code"
android:key="youtube_player_client"
app:useSimpleSummaryProvider="true"
android:title="Player Client" />
<EditTextPreference
android:defaultValue=""
android:icon="@drawable/ic_code"
android:key="youtube_po_token"
app:useSimpleSummaryProvider="true"
android:title="PO Token" />
<EditTextPreference
android:defaultValue=""
android:icon="@drawable/ic_code"
android:key="youtube_po_token"
app:useSimpleSummaryProvider="true"
android:title="PO Token" />
<EditTextPreference
android:defaultValue=""
android:icon="@drawable/ic_code"
android:key="youtube_other_extractor_args"
app:useSimpleSummaryProvider="true"
android:title="@string/other_youtube_extractor_args" />
<EditTextPreference
android:defaultValue=""
android:icon="@drawable/ic_terminal"
android:key="data_fetching_extra_commands"
app:useSimpleSummaryProvider="true"
android:title="@string/data_fetching_extra_commands" />
<EditTextPreference
android:defaultValue=""
android:icon="@drawable/ic_code"
android:key="youtube_other_extractor_args"
app:useSimpleSummaryProvider="true"
android:title="@string/other_youtube_extractor_args" />
</PreferenceCategory>
</PreferenceScreen>

View file

@ -2,6 +2,11 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"/>
<PreferenceCategory android:title="@string/source">
<Preference
@ -35,6 +40,36 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/app">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_update_app"
android:key="update_app"
app:summary="@string/update_app_summary"
app:title="@string/update_app" />
<EditTextPreference
app:isPreferenceVisible="false"
app:key="skip_updates"
app:defaultValue="" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_experiment"
android:key="update_beta"
app:summary="@string/update_app_beta_summary"
app:title="@string/update_app_beta" />
<Preference
app:icon="@drawable/ic_chapters"
app:key="changelog"
app:title="@string/changelog" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/format">
<ListPreference
android:defaultValue="filesize"
@ -63,41 +98,4 @@
app:title="@string/update_formats" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/app">
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
android:icon="@drawable/ic_update_app"
android:key="update_app"
app:summary="@string/update_app_summary"
app:title="@string/update_app" />
<EditTextPreference
app:isPreferenceVisible="false"
app:key="skip_updates"
app:defaultValue="" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/ic_experiment"
android:key="update_beta"
app:summary="@string/update_app_beta_summary"
app:title="@string/update_app_beta" />
<Preference
app:icon="@drawable/ic_chapters"
app:key="changelog"
app:title="@string/changelog" />
<Preference
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"/>
</PreferenceCategory>
</PreferenceScreen>