1.7.8
This commit is contained in:
parent
313358c4bb
commit
15a4ecf092
81 changed files with 3827 additions and 764 deletions
|
|
@ -10,7 +10,7 @@ plugins {
|
|||
def properties = new Properties()
|
||||
def versionMajor = 1
|
||||
def versionMinor = 7
|
||||
def versionPatch = 7
|
||||
def versionPatch = 8
|
||||
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
|
||||
def isBeta = false
|
||||
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
|
||||
|
|
@ -130,6 +130,10 @@ dependencies {
|
|||
implementation "com.github.yausername.youtubedl-android:library:$youtubedlAndroidVer"
|
||||
implementation "com.github.yausername.youtubedl-android:ffmpeg:$youtubedlAndroidVer"
|
||||
implementation "com.github.yausername.youtubedl-android:aria2c:$youtubedlAndroidVer"
|
||||
//
|
||||
// implementation "io.github.junkfood02.youtubedl-android:library:0.16.0"
|
||||
// implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.16.0"
|
||||
// implementation "io.github.junkfood02.youtubedl-android:aria2c:0.16.0"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appCompatVer"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
||||
|
|
@ -142,6 +146,7 @@ dependencies {
|
|||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||
implementation 'androidx.compose.ui:ui-android:1.6.5'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
testImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "junit:junit:$junitVer"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
|
||||
|
|
@ -177,7 +182,7 @@ dependencies {
|
|||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.2"
|
||||
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
|
||||
|
||||
// For media playback using ExoPlayer
|
||||
|
|
|
|||
732
app/schemas/com.deniscerri.ytdl.database.DBManager/18.json
Normal file
732
app/schemas/com.deniscerri.ytdl.database.DBManager/18.json
Normal file
|
|
@ -0,0 +1,732 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 18,
|
||||
"identityHash": "0e5e77e8657b2b151e63613f8d6f8110",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "results",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `creationTime` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "author",
|
||||
"columnName": "author",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "thumb",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "website",
|
||||
"columnName": "website",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistTitle",
|
||||
"columnName": "playlistTitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "formats",
|
||||
"columnName": "formats",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "urls",
|
||||
"columnName": "urls",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "chapters",
|
||||
"columnName": "chapters",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistURL",
|
||||
"columnName": "playlistURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistIndex",
|
||||
"columnName": "playlistIndex",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "creationTime",
|
||||
"columnName": "creationTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "history",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadId` INTEGER NOT NULL DEFAULT 0, `command` TEXT NOT NULL DEFAULT '')",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "author",
|
||||
"columnName": "author",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "thumb",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "time",
|
||||
"columnName": "time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadPath",
|
||||
"columnName": "downloadPath",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "website",
|
||||
"columnName": "website",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "format",
|
||||
"columnName": "format",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadId",
|
||||
"columnName": "downloadId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "command",
|
||||
"columnName": "command",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "downloads",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "author",
|
||||
"columnName": "author",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumb",
|
||||
"columnName": "thumb",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "format",
|
||||
"columnName": "format",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "container",
|
||||
"columnName": "container",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'Default'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadSections",
|
||||
"columnName": "downloadSections",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "allFormats",
|
||||
"columnName": "allFormats",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadPath",
|
||||
"columnName": "downloadPath",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "website",
|
||||
"columnName": "website",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadSize",
|
||||
"columnName": "downloadSize",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistTitle",
|
||||
"columnName": "playlistTitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "audioPreferences",
|
||||
"columnName": "audioPreferences",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "videoPreferences",
|
||||
"columnName": "videoPreferences",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "extraCommands",
|
||||
"columnName": "extraCommands",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customFileNameTemplate",
|
||||
"columnName": "customFileNameTemplate",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "SaveThumb",
|
||||
"columnName": "SaveThumb",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "'Queued'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadStartTime",
|
||||
"columnName": "downloadStartTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "logID",
|
||||
"columnName": "logID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistURL",
|
||||
"columnName": "playlistURL",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistIndex",
|
||||
"columnName": "playlistIndex",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "commandTemplates",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "useAsExtraCommand",
|
||||
"columnName": "useAsExtraCommand",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "useAsExtraCommandAudio",
|
||||
"columnName": "useAsExtraCommandAudio",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "useAsExtraCommandVideo",
|
||||
"columnName": "useAsExtraCommandVideo",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "searchHistory",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "query",
|
||||
"columnName": "query",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "templateShortcuts",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "cookies",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "logs",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "format",
|
||||
"columnName": "format",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadType",
|
||||
"columnName": "downloadType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadTime",
|
||||
"columnName": "downloadTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "terminalDownloads",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "command",
|
||||
"columnName": "command",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "log",
|
||||
"columnName": "log",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL, `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, '0e5e77e8657b2b151e63613f8d6f8110')"
|
||||
]
|
||||
}
|
||||
}
|
||||
739
app/schemas/com.deniscerri.ytdl.database.DBManager/19.json
Normal file
739
app/schemas/com.deniscerri.ytdl.database.DBManager/19.json
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 19,
|
||||
"identityHash": "348500cc087b2ef2e8f43470db15069e",
|
||||
"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, `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)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "useAsExtraCommand",
|
||||
"columnName": "useAsExtraCommand",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "useAsExtraCommandAudio",
|
||||
"columnName": "useAsExtraCommandAudio",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "useAsExtraCommandVideo",
|
||||
"columnName": "useAsExtraCommandVideo",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "searchHistory",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "query",
|
||||
"columnName": "query",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "templateShortcuts",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "cookies",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "url",
|
||||
"columnName": "url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "logs",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "format",
|
||||
"columnName": "format",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadType",
|
||||
"columnName": "downloadType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadTime",
|
||||
"columnName": "downloadTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "terminalDownloads",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "command",
|
||||
"columnName": "command",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "log",
|
||||
"columnName": "log",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL, `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, '348500cc087b2ef2e8f43470db15069e')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -42,9 +42,13 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
|||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.ui.BaseActivity
|
||||
import com.deniscerri.ytdl.ui.HomeFragment
|
||||
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.FileUtil
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.NavbarUtil.applyNavBarStyle
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
|
|
@ -125,26 +129,39 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
if (savedInstanceState == null){
|
||||
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
|
||||
graph.setStartDestination(R.id.homeFragment)
|
||||
when(preferences.getString("start_destination", "")) {
|
||||
"History" -> graph.setStartDestination(R.id.historyFragment)
|
||||
"More" -> if (navigationView is NavigationBarView) graph.setStartDestination(R.id.moreFragment)
|
||||
}
|
||||
|
||||
navController.graph = graph
|
||||
}
|
||||
NavbarUtil.init(this)
|
||||
|
||||
if (navigationView is NavigationBarView){
|
||||
if (savedInstanceState == null){
|
||||
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
|
||||
graph.setStartDestination(NavbarUtil.getStartFragmentId(this))
|
||||
navController.graph = graph
|
||||
}
|
||||
(navigationView as NavigationBarView).applyNavBarStyle()
|
||||
|
||||
val showingDownloadQueue = NavbarUtil.getNavBarItems(this).any { n -> n.itemId == R.id.downloadQueueMainFragment && n.isVisible }
|
||||
|
||||
(navigationView as NavigationBarView).setupWithNavController(navController)
|
||||
(navigationView as NavigationBarView).setOnItemReselectedListener {
|
||||
when (it.itemId) {
|
||||
R.id.homeFragment -> {
|
||||
(navHostFragment.childFragmentManager.primaryNavigationFragment!! as HomeFragment).scrollToTop()
|
||||
kotlin.runCatching {
|
||||
(navHostFragment.childFragmentManager.primaryNavigationFragment!! as HomeFragment).scrollToTop()
|
||||
}
|
||||
}
|
||||
R.id.historyFragment -> {
|
||||
navController.navigate(R.id.downloadQueueMainFragment)
|
||||
if(!showingDownloadQueue) {
|
||||
navController.navigate(R.id.downloadQueueMainFragment)
|
||||
}else{
|
||||
kotlin.runCatching {
|
||||
(navHostFragment.childFragmentManager.primaryNavigationFragment!! as HistoryFragment).scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.downloadQueueMainFragment -> {
|
||||
kotlin.runCatching {
|
||||
(navHostFragment.childFragmentManager.primaryNavigationFragment!! as DownloadQueueMainFragment).scrollToActive()
|
||||
}
|
||||
}
|
||||
R.id.moreFragment -> {
|
||||
val intent = Intent(context, SettingsActivity::class.java)
|
||||
|
|
@ -153,7 +170,11 @@ class MainActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
val activeDownloadsBadge = (navigationView as NavigationBarView).getOrCreateBadge(R.id.historyFragment)
|
||||
val activeDownloadsBadge = if (showingDownloadQueue) {
|
||||
(navigationView as NavigationBarView).getOrCreateBadge(R.id.downloadQueueMainFragment)
|
||||
}else{
|
||||
(navigationView as NavigationBarView).getOrCreateBadge(R.id.historyFragment)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||
if (it == 0) {
|
||||
|
|
@ -220,12 +241,14 @@ class MainActivity : BaseActivity() {
|
|||
(navigationView as NavigationView).getHeaderView(0).findViewById<TextView>(R.id.title).text = ThemeUtil.getStyledAppName(this)
|
||||
}
|
||||
|
||||
val showingNavbarItems = NavbarUtil.getNavBarItems(this).filter { it.isVisible }.map { it.itemId }
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (navigationView is NavigationBarView){
|
||||
when(destination.id){
|
||||
R.id.homeFragment, R.id.historyFragment, R.id.moreFragment -> showBottomNavigation()
|
||||
else -> hideBottomNavigation()
|
||||
if (showingNavbarItems.contains(destination.id)) {
|
||||
showBottomNavigation()
|
||||
}else{
|
||||
hideBottomNavigation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
|
|||
TerminalItem::class,
|
||||
ObserveSourcesItem::class
|
||||
],
|
||||
version = 17,
|
||||
version = 19,
|
||||
autoMigrations = [
|
||||
AutoMigration (from = 1, to = 2),
|
||||
AutoMigration (from = 2, to = 3),
|
||||
|
|
@ -58,7 +58,9 @@ import com.deniscerri.ytdl.database.models.TerminalItem
|
|||
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
|
||||
AutoMigration (from = 14, to = 15),
|
||||
AutoMigration (from = 15, to = 16, spec = Migrations.resetObserveSources::class),
|
||||
AutoMigration (from = 16, to = 17)
|
||||
AutoMigration (from = 16, to = 17),
|
||||
AutoMigration (from = 17, to = 18),
|
||||
AutoMigration (from = 18, to = 19),
|
||||
]
|
||||
)
|
||||
abstract class DBManager : RoomDatabase(){
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ object Migrations {
|
|||
}
|
||||
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `observeSources` (`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, `retryMissingDownloads` INTEGER NOT NULL, `alreadyProcessedLinks` TEXT NOT NULL)")
|
||||
}
|
||||
},
|
||||
|
||||
// Migration(17, 18 ){ database ->
|
||||
// database.execSQL("ALTER TABLE `sources` ADD COLUMN `syncWithSource` INTEGER NOT NULL DEFAULT 0")
|
||||
// }
|
||||
)
|
||||
|
||||
@DeleteTable.Entries(
|
||||
|
|
@ -39,5 +43,4 @@ object Migrations {
|
|||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -83,6 +83,9 @@ interface CommandTemplateDao {
|
|||
@Query("DELETE FROM templateShortcuts WHERE id=:itemId")
|
||||
suspend fun deleteShortcut(itemId: Long)
|
||||
|
||||
@Query("DELETE FROM templateShortcuts")
|
||||
suspend fun deleteAllShortcuts()
|
||||
|
||||
@Update
|
||||
suspend fun update(item: CommandTemplate)
|
||||
}
|
||||
|
|
@ -50,8 +50,6 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status = 'Processing' ORDER BY id LIMIT 1")
|
||||
fun getFirstProcessingDownload() : DownloadItem
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
fun getActiveAndPausedDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status = 'Processing'")
|
||||
fun getProcessingDownloadsList() : List<DownloadItem>
|
||||
|
|
@ -63,12 +61,12 @@ interface DownloadDao {
|
|||
suspend fun updateProcessingDownloadPath(path: String)
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status='Active'")
|
||||
suspend fun getActiveDownloadsList() : List<DownloadItem>
|
||||
fun getActiveDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status in('Active','Queued', 'Scheduled')")
|
||||
fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
|
||||
@Query("UPDATE downloads SET status='Queued' where status in ('Active', 'QueuedPaused', 'ActivePaused')")
|
||||
suspend fun resetActivePausedItems()
|
||||
@Query("UPDATE downloads SET status='Queued' where status = 'Active'")
|
||||
suspend fun resetActiveToQueued()
|
||||
|
||||
@Query("SELECT id FROM downloads WHERE status in('Active','Queued')")
|
||||
fun getActiveAndQueuedDownloadIDs() : List<Long>
|
||||
|
|
@ -102,8 +100,6 @@ interface DownloadDao {
|
|||
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
|
||||
fun getCancelledDownloadsList() : List<DownloadItem>
|
||||
|
||||
@Query("SELECT * FROM downloads WHERE status LIKE '%Paused%'")
|
||||
fun getPausedDownloadsList() : List<DownloadItem>
|
||||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||
fun getErroredDownloads() : PagingSource<Int, DownloadItemSimple>
|
||||
|
|
@ -159,6 +155,9 @@ interface DownloadDao {
|
|||
@Query("DELETE FROM downloads WHERE status='Error'")
|
||||
suspend fun deleteErrored()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Queued'")
|
||||
suspend fun deleteQueued()
|
||||
|
||||
@Query("DELETE FROM downloads WHERE status='Saved'")
|
||||
suspend fun deleteSaved()
|
||||
|
||||
|
|
@ -269,10 +268,16 @@ interface DownloadDao {
|
|||
@Query("Update downloads set id=:newId where id=:id")
|
||||
suspend fun updateDownloadID(id: Long, newId: Long)
|
||||
|
||||
|
||||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in (:statuses) ORDER BY id DESC")
|
||||
fun getIDsBetweenTwoItems(item1: Long, item2: Long, statuses: List<String>) : List<Long>
|
||||
|
||||
@Query("SELECT id from downloads WHERE id > :item1 AND id < :item2 AND status in('Scheduled') ORDER BY downloadStartTime, id")
|
||||
fun getScheduledIDsBetweenTwoItems(item1: Long, item2: Long) : List<Long>
|
||||
|
||||
|
||||
@Query("UPDATE downloads set incognito=:incognito WHERE status='Processing'")
|
||||
suspend fun updateProcessingIncognito(incognito: Boolean)
|
||||
|
||||
@Query("SELECT COUNT(id) FROM downloads WHERE status='Processing' AND incognito='1'")
|
||||
fun getProcessingAsIncognitoCount(): Int
|
||||
}
|
||||
|
|
@ -42,5 +42,7 @@ data class DownloadItem(
|
|||
@ColumnInfo(defaultValue = "")
|
||||
var playlistURL: String? = "",
|
||||
@ColumnInfo(defaultValue = "")
|
||||
var playlistIndex: Int? = null
|
||||
var playlistIndex: Int? = null,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var incognito: Boolean = false
|
||||
) : Parcelable
|
||||
|
|
@ -22,4 +22,5 @@ data class DownloadItemSimple(
|
|||
var type: DownloadViewModel.Type,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var downloadStartTime: Long,
|
||||
var incognito: Boolean = false
|
||||
)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.deniscerri.ytdl.database.models
|
||||
|
||||
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
|
||||
import com.google.gson.JsonArray
|
||||
|
||||
data class RestoreAppDataItem(
|
||||
var settings : JsonArray? = null,
|
||||
var downloads: List<HistoryItem>? = null,
|
||||
var queued: List<DownloadItem>? = null,
|
||||
var scheduled: List<DownloadItem>? = null,
|
||||
var cancelled: List<DownloadItem>? = null,
|
||||
var errored: List<DownloadItem>? = null,
|
||||
var saved: List<DownloadItem>? = null,
|
||||
var cookies: List<CookieItem>? = null,
|
||||
var templates: List<CommandTemplate>? = null,
|
||||
var shortcuts: List<TemplateShortcut>? = null,
|
||||
var searchHistory: List<SearchHistoryItem>? = null,
|
||||
var observeSources: List<ObserveSourcesItem>? = null,
|
||||
)
|
||||
|
|
@ -37,5 +37,7 @@ data class ObserveSourcesItem(
|
|||
var retryMissingDownloads: Boolean,
|
||||
@ColumnInfo(defaultValue = "[]")
|
||||
var ignoredLinks: MutableList<String>,
|
||||
var alreadyProcessedLinks : MutableList<String>
|
||||
var alreadyProcessedLinks : MutableList<String>,
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var syncWithSource: Boolean
|
||||
) : Parcelable
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ class CommandTemplateRepository(private val commandDao: CommandTemplateDao) {
|
|||
commandDao.deleteShortcut(item.id)
|
||||
}
|
||||
|
||||
suspend fun deleteAllShortcuts() {
|
||||
commandDao.deleteAllShortcuts()
|
||||
}
|
||||
|
||||
suspend fun deleteAll(){
|
||||
commandDao.deleteAll()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
val scheduledDownloadsCount : Flow<Int> = downloadDao.getDownloadsCountByStatusFlow(listOf(Status.Scheduled).toListString())
|
||||
|
||||
enum class Status {
|
||||
Active, ActivePaused, Queued, Error, Cancelled, Saved, Processing, Scheduled
|
||||
Active, Queued, Error, Cancelled, Saved, Processing, Scheduled
|
||||
}
|
||||
|
||||
suspend fun insert(item: DownloadItem) : Long {
|
||||
|
|
@ -118,12 +118,8 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getDownloadsByIds(ids)
|
||||
}
|
||||
|
||||
fun getAllItemsByIDsFlow(ids: List<Long>) : Flow<List<DownloadItem>> {
|
||||
return downloadDao.getDownloadsByIdsFlow(ids)
|
||||
}
|
||||
|
||||
fun getActiveDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getActiveAndPausedDownloadsList()
|
||||
return downloadDao.getActiveDownloadsList()
|
||||
}
|
||||
|
||||
fun getProcessingDownloads() : List<DownloadItem> {
|
||||
|
|
@ -154,10 +150,6 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
return downloadDao.getCancelledDownloadsList()
|
||||
}
|
||||
|
||||
fun getPausedDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getPausedDownloadsList()
|
||||
}
|
||||
|
||||
fun getErroredDownloads() : List<DownloadItem> {
|
||||
return downloadDao.getErroredDownloadsList()
|
||||
}
|
||||
|
|
@ -182,6 +174,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
deleteCache(errored)
|
||||
}
|
||||
|
||||
suspend fun deleteQueued() {
|
||||
downloadDao.deleteQueued()
|
||||
}
|
||||
|
||||
suspend fun deleteSaved(){
|
||||
downloadDao.deleteSaved()
|
||||
}
|
||||
|
|
@ -238,7 +234,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
else {
|
||||
workConstraints.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
//workConstraints.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
}
|
||||
|
||||
val workRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||
|
|
@ -268,12 +264,14 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
|||
}, 0)
|
||||
}
|
||||
|
||||
val first = queuedItems.first()
|
||||
if (first.downloadStartTime > 0L) {
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(queuedItems.first().downloadStartTime)
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
}, 0)
|
||||
if (queuedItems.isNotEmpty()) {
|
||||
val first = queuedItems.first()
|
||||
if (first.downloadStartTime > 0L) {
|
||||
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(queuedItems.first().downloadStartTime)
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, context.getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import java.util.regex.Pattern
|
|||
|
||||
class ResultRepository(private val resultDao: ResultDao, private val context: Context) {
|
||||
private val tag: String = "ResultRepository"
|
||||
val YTDLNIS_SEARCH = "YTDLNIS_SEARCH"
|
||||
val allResults : Flow<List<ResultItem>> = resultDao.getResults()
|
||||
var itemCount = MutableStateFlow(-1)
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
deleteAll()
|
||||
itemCount.value = v.size
|
||||
}else{
|
||||
v.filter { it.playlistTitle.isBlank() }.forEach { it.playlistTitle = "ytdlnis-Search" }
|
||||
v.filter { it.playlistTitle.isBlank() }.forEach { it.playlistTitle = YTDLNIS_SEARCH }
|
||||
}
|
||||
if (addToResults){
|
||||
val ids = resultDao.insertMultiple(v)
|
||||
|
|
@ -99,7 +100,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
deleteAll()
|
||||
itemCount.value = items.size
|
||||
}else{
|
||||
items.filter { it.playlistTitle.isNullOrBlank() }.forEach { it.playlistTitle = "ytdlnis-Search" }
|
||||
items.filter { it.playlistTitle.isBlank() }.forEach { it.playlistTitle = YTDLNIS_SEARCH }
|
||||
}
|
||||
|
||||
if (addToResults){
|
||||
|
|
@ -182,7 +183,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
|||
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
|
||||
if (downloadItem.playlistTitle.isEmpty() && downloadItem.playlistTitle != "ytdlnis-Search") downloadItem.playlistTitle = info.playlistTitle
|
||||
if (downloadItem.playlistTitle.isNotBlank() && downloadItem.playlistTitle != YTDLNIS_SEARCH) downloadItem.playlistTitle = info.playlistTitle
|
||||
downloadItem.duration = info.duration
|
||||
downloadItem.website = info.website
|
||||
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ class CommandTemplateViewModel(private val application: Application) : AndroidVi
|
|||
repository.deleteShortcut(item)
|
||||
}
|
||||
|
||||
fun deleteAllShortcuts() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteAllShortcuts()
|
||||
}
|
||||
|
||||
fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteAll()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,21 +76,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
|
||||
val alreadyExistsUiState: MutableStateFlow<List<SharedDownloadViewModel.AlreadyExistsIDs>>
|
||||
|
||||
private var bestVideoFormat : Format
|
||||
private var bestAudioFormat : Format
|
||||
private var defaultVideoFormats : MutableList<Format>
|
||||
|
||||
private val videoQualityPreference: String
|
||||
private val formatIDPreference: List<String>
|
||||
private val audioFormatIDPreference: List<String>
|
||||
private val resources : Resources
|
||||
private var extraCommandsForAudio: String = ""
|
||||
private var extraCommandsForVideo: String = ""
|
||||
|
||||
private var audioContainer: String?
|
||||
private var videoContainer: String?
|
||||
private var videoCodec: String?
|
||||
private var audioCodec: String?
|
||||
private val dao: DownloadDao
|
||||
private val historyRepository: HistoryRepository
|
||||
private val resultRepository: ResultRepository
|
||||
|
|
@ -137,37 +125,6 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
}
|
||||
|
||||
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
confTmp.setLocale(Locale(sharedPreferences.getString("app_language", "en")!!))
|
||||
val metrics = DisplayMetrics()
|
||||
resources = Resources(application.assets, metrics, confTmp)
|
||||
|
||||
|
||||
videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||
defaultVideoFormats = infoUtil.getGenericVideoFormats(resources)
|
||||
bestVideoFormat = defaultVideoFormats.first()
|
||||
|
||||
audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||
bestAudioFormat = if (audioFormatIDPreference.isEmpty()){
|
||||
infoUtil.getGenericAudioFormats(resources).first()
|
||||
}else{
|
||||
Format(
|
||||
audioFormatIDPreference.first().split("+").first(),
|
||||
audioContainer!!,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
audioFormatIDPreference.first().split("+").first()
|
||||
)
|
||||
}
|
||||
|
||||
videoCodec = sharedPreferences.getString("video_codec", "")
|
||||
audioCodec = sharedPreferences.getString("audio_codec", "")
|
||||
}
|
||||
|
||||
fun deleteDownload(id: Long) = viewModelScope.launch(Dispatchers.IO) {
|
||||
|
|
@ -325,14 +282,26 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
container,
|
||||
"",
|
||||
ArrayList(),
|
||||
path, historyItem.website, "", "", audioPreferences, videoPreferences, extraCommands, customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null
|
||||
path,
|
||||
historyItem.website,
|
||||
"",
|
||||
"",
|
||||
audioPreferences,
|
||||
videoPreferences,
|
||||
extraCommands,
|
||||
customFileNameTemplate!!,
|
||||
saveThumb,
|
||||
DownloadRepository.Status.Queued.toString(),
|
||||
0,
|
||||
null,
|
||||
incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun getPreferredAudioRequirements(): MutableList<(Format) -> Int> {
|
||||
return sharedDownloadViewModel.getPreferredVideoRequirements()
|
||||
return sharedDownloadViewModel.getPreferredAudioRequirements()
|
||||
}
|
||||
|
||||
//requirement and importance
|
||||
|
|
@ -371,7 +340,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
try {
|
||||
itemIDs.forEachIndexed { idx, it ->
|
||||
val item = repository.getItemByID(it)
|
||||
if (!isActive) throw CancellationException()
|
||||
if (processingItemsFlow?.job?.isCancelled == true) throw CancellationException()
|
||||
|
||||
item.id = 0
|
||||
item.status = DownloadRepository.Status.Processing.toString()
|
||||
|
|
@ -383,11 +352,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
processingItems.emit(false)
|
||||
} catch (e: Exception) {
|
||||
processingItemsFlow?.apply {
|
||||
this.processingDownloadItemIDs.chunked(100).forEach {
|
||||
repository.deleteAllWithIDs(it)
|
||||
}
|
||||
}
|
||||
deleteProcessing()
|
||||
updateProcessingJobData(null)
|
||||
processingItems.emit(false)
|
||||
}
|
||||
|
|
@ -399,6 +364,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
|
||||
updateProcessingJobData(ProcessingItemsJob(null, ResultItem::class.java.toString(), itemIDs))
|
||||
val job = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteProcessing()
|
||||
processingItems.emit(true)
|
||||
try {
|
||||
itemIDs.forEach { id ->
|
||||
|
|
@ -409,7 +375,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
))
|
||||
downloadItem.status = DownloadRepository.Status.Processing.toString()
|
||||
|
||||
if (!isActive) {
|
||||
if (processingItemsFlow?.job?.isCancelled == true) {
|
||||
throw CancellationException()
|
||||
}
|
||||
|
||||
|
|
@ -425,11 +391,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
processingItems.emit(false)
|
||||
}catch (e: Exception) {
|
||||
processingItemsFlow?.apply {
|
||||
this.processingDownloadItemIDs.chunked(100).forEach {
|
||||
repository.deleteAllWithIDs(it)
|
||||
}
|
||||
}
|
||||
deleteProcessing()
|
||||
updateProcessingJobData(null)
|
||||
processingItems.emit(false)
|
||||
}
|
||||
|
|
@ -472,6 +434,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.deleteErrored()
|
||||
}
|
||||
|
||||
fun deleteQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteQueued()
|
||||
}
|
||||
|
||||
fun deleteSaved() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteSaved()
|
||||
}
|
||||
|
|
@ -513,7 +479,11 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
}
|
||||
|
||||
fun getActiveDownloadsCount() : Int {
|
||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Active, DownloadRepository.Status.ActivePaused).toListString())
|
||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Active).toListString())
|
||||
}
|
||||
|
||||
fun getActiveQueuedDownloadsCount() : Int {
|
||||
return dao.getDownloadsCountByStatus(listOf(DownloadRepository.Status.Active, DownloadRepository.Status.Queued).toListString())
|
||||
}
|
||||
|
||||
fun getQueuedDownloadsCount() : Int {
|
||||
|
|
@ -529,8 +499,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
repository.startDownloadWorker(emptyList(), application)
|
||||
}
|
||||
|
||||
suspend fun resetActivePaused() {
|
||||
dbManager.downloadDao.resetActivePausedItems()
|
||||
suspend fun resetActiveToQueued() {
|
||||
dbManager.downloadDao.resetActiveToQueued()
|
||||
}
|
||||
|
||||
suspend fun startDownloadWorker(list: List<DownloadItem>){
|
||||
|
|
@ -751,5 +721,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
|
|||
return dao.getScheduledIDsBetweenTwoItems(item1, item2)
|
||||
}
|
||||
|
||||
suspend fun updateProcessingIncognito(incognito: Boolean) {
|
||||
dao.updateProcessingIncognito(incognito)
|
||||
}
|
||||
|
||||
fun areAllProcessingIncognito() : Boolean {
|
||||
return dao.getProcessingAsIncognitoCount() > 0
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import com.deniscerri.ytdl.database.repository.ResultRepository
|
|||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.FormatSorter
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.work.AlarmScheduler
|
||||
import com.google.gson.Gson
|
||||
|
|
@ -230,13 +231,18 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
resultItem.formats,
|
||||
downloadPath!!, resultItem.website,
|
||||
"",
|
||||
resultItem.playlistTitle,
|
||||
if (resultItem.playlistTitle == resultRepository.YTDLNIS_SEARCH) "" else resultItem.playlistTitle,
|
||||
audioPreferences,
|
||||
videoPreferences,
|
||||
extraCommands,
|
||||
customFileNameTemplate!!,
|
||||
saveThumb,
|
||||
DownloadRepository.Status.Queued.toString(), 0, null, playlistURL = resultItem.playlistURL, playlistIndex = resultItem.playlistIndex
|
||||
DownloadRepository.Status.Queued.toString(),
|
||||
0,
|
||||
null,
|
||||
playlistURL = resultItem.playlistURL,
|
||||
playlistIndex = resultItem.playlistIndex,
|
||||
incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -281,7 +287,7 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
|
||||
}
|
||||
|
||||
private fun getPreferredAudioRequirements(): MutableList<(Format) -> Int> {
|
||||
fun getPreferredAudioRequirements(): MutableList<(Format) -> Int> {
|
||||
val requirements: MutableList<(Format) -> Int> = mutableListOf()
|
||||
|
||||
val itemValues = resources.getStringArray(R.array.format_importance_audio_values).toSet()
|
||||
|
|
@ -345,9 +351,8 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
for(i in 0..preferenceIndex){
|
||||
removeAt(0)
|
||||
}
|
||||
requirements.add { it: Format -> if (it.format_note.contains(preference, ignoreCase = true)) importance else 0 }
|
||||
add(0, preference)
|
||||
forEachIndexed { index, res ->
|
||||
if (index >= 2) importance = 0
|
||||
requirements.add { it: Format -> if (it.format_note.contains(res, ignoreCase = true)) (importance - index - 1) else 0 }
|
||||
}
|
||||
}
|
||||
|
|
@ -381,8 +386,10 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
return cloneFormat (
|
||||
try {
|
||||
val theFormats = formats.filter { it.vcodec.isBlank() || it.vcodec == "none" }
|
||||
val requirements = getPreferredAudioRequirements()
|
||||
theFormats.maxByOrNull { f -> requirements.sumOf{ req -> req(f)} } ?: throw Exception()
|
||||
FormatSorter(context).sortAudioFormats(theFormats).first()
|
||||
//
|
||||
// val requirements = getPreferredAudioRequirements()
|
||||
// theFormats.maxByOrNull { f -> requirements.sumOf{ req -> req(f)} } ?: throw Exception()
|
||||
}catch (e: Exception){
|
||||
bestAudioFormat
|
||||
}
|
||||
|
|
@ -395,24 +402,27 @@ class SharedDownloadViewModel(private val context: Context) {
|
|||
val theFormats = formats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.ifEmpty {
|
||||
defaultVideoFormats.sortedByDescending { it.filesize }
|
||||
}
|
||||
when (videoQualityPreference) {
|
||||
"worst" -> {
|
||||
theFormats.last()
|
||||
}
|
||||
else /*best*/ -> {
|
||||
val requirements = getPreferredVideoRequirements()
|
||||
theFormats.run {
|
||||
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)){
|
||||
sortedBy { it.filesize }.maxByOrNull { f -> requirements.sumOf { req -> req(f) } } ?: throw Exception()
|
||||
}else{
|
||||
sortedByDescending { it.filesize }.maxByOrNull { f ->
|
||||
val summ = requirements.sumOf { req -> req(f) }
|
||||
summ
|
||||
} ?: throw Exception()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormatSorter(context).sortVideoFormats(theFormats).first()
|
||||
//
|
||||
// when (videoQualityPreference) {
|
||||
// "worst" -> {
|
||||
// theFormats.last()
|
||||
// }
|
||||
// else /*best*/ -> {
|
||||
// val requirements = getPreferredVideoRequirements()
|
||||
// theFormats.run {
|
||||
// if (sharedPreferences.getBoolean("prefer_smaller_formats", false)){
|
||||
// sortedBy { it.filesize }.maxByOrNull { f -> requirements.sumOf { req -> req(f) } } ?: throw Exception()
|
||||
// }else{
|
||||
// sortedByDescending { it.filesize }.maxByOrNull { f ->
|
||||
// val summ = requirements.sumOf { req -> req(f) }
|
||||
// summ
|
||||
// } ?: throw Exception()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}catch (e: Exception){
|
||||
bestVideoFormat
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,28 +14,28 @@ 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 {
|
||||
val title = intent.getStringExtra("title")
|
||||
val notificationUtil = NotificationUtil(c)
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
val dbManager = DBManager.getInstance(c)
|
||||
CoroutineScope(Dispatchers.IO).launch{
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// val result = goAsync()
|
||||
// val id = intent.getIntExtra("itemID", 0)
|
||||
// if (id != 0) {
|
||||
// runCatching {
|
||||
// val title = intent.getStringExtra("title")
|
||||
// val notificationUtil = NotificationUtil(c)
|
||||
// notificationUtil.cancelDownloadNotification(id)
|
||||
// YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
// val dbManager = DBManager.getInstance(c)
|
||||
// CoroutineScope(Dispatchers.IO).launch{
|
||||
// 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()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
@ -122,16 +122,24 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
|
||||
cancelButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)}
|
||||
|
||||
val activePaused = item.status == DownloadRepository.Status.ActivePaused.toString()
|
||||
val resumeButton = card.findViewById<MaterialButton>(R.id.active_download_resume)
|
||||
resumeButton.isVisible = activePaused
|
||||
if (resumeButton.hasOnClickListeners()) resumeButton.setOnClickListener(null)
|
||||
resumeButton.setOnClickListener {
|
||||
resumeButton.isVisible = false
|
||||
onItemClickListener.onResumeClick(item.id)
|
||||
}
|
||||
// val resumeButton = card.findViewById<MaterialButton>(R.id.active_download_resume)
|
||||
// resumeButton.isEnabled = true
|
||||
// if (resumeButton.hasOnClickListeners()) resumeButton.setOnClickListener(null)
|
||||
// if (activePaused) {
|
||||
// resumeButton.setIconResource(R.drawable.exomedia_ic_play_arrow_white)
|
||||
// resumeButton.setOnClickListener {
|
||||
// resumeButton.isEnabled = false
|
||||
// onItemClickListener.onResumeClick(item.id)
|
||||
// }
|
||||
// }else {
|
||||
// resumeButton.setIconResource(R.drawable.exomedia_ic_pause_white)
|
||||
// resumeButton.setOnClickListener {
|
||||
// resumeButton.isEnabled = false
|
||||
// onItemClickListener.onPauseClick(item.id)
|
||||
// }
|
||||
// }
|
||||
|
||||
if (sharedPreferences.getBoolean("paused_downloads", false) || activePaused) {
|
||||
if (sharedPreferences.getBoolean("paused_downloads", false)) {
|
||||
progressBar.isIndeterminate = false
|
||||
cancelButton.isEnabled = true
|
||||
output.text = activity.getString(R.string.exo_download_paused)
|
||||
|
|
@ -142,7 +150,6 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
}
|
||||
interface OnItemClickListener {
|
||||
fun onCancelClick(itemID: Long)
|
||||
fun onResumeClick(itemID: Long)
|
||||
fun onOutputClick(item: DownloadItem)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
else fileSize.text = fileSizeReadable
|
||||
|
||||
val menu = card.findViewById<View>(R.id.options)
|
||||
val paused = sharedPreferences.getBoolean("paused_downloads", false) || item.status == DownloadRepository.Status.ActivePaused.toString()
|
||||
//val paused = sharedPreferences.getBoolean("paused_downloads", false) || item.status == DownloadRepository.Status.ActivePaused.toString()
|
||||
menu.setOnClickListener {
|
||||
val popup = PopupMenu(activity, it)
|
||||
popup.menuInflater.inflate(R.menu.active_downloads_minified, popup.menu)
|
||||
|
|
@ -133,27 +133,27 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
|
||||
val pause = popup.menu[0]
|
||||
val resume = popup.menu[1]
|
||||
|
||||
if (paused){
|
||||
pause.isVisible = false
|
||||
resume.isVisible = true
|
||||
}else{
|
||||
pause.isVisible = true
|
||||
resume.isVisible = false
|
||||
}
|
||||
//
|
||||
// if (paused){
|
||||
// pause.isVisible = false
|
||||
// resume.isVisible = true
|
||||
// }else{
|
||||
// pause.isVisible = true
|
||||
// resume.isVisible = false
|
||||
// }
|
||||
|
||||
popup.setOnMenuItemClickListener { m ->
|
||||
when(m.itemId){
|
||||
R.id.pause -> {
|
||||
onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
|
||||
if (progressBar.progress == 0) progressBar.isIndeterminate = false
|
||||
popup.dismiss()
|
||||
}
|
||||
R.id.resume -> {
|
||||
onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
|
||||
progressBar.isIndeterminate = true
|
||||
popup.dismiss()
|
||||
}
|
||||
// R.id.pause -> {
|
||||
// onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Pause, position)
|
||||
// if (progressBar.progress == 0) progressBar.isIndeterminate = false
|
||||
// popup.dismiss()
|
||||
// }
|
||||
// R.id.resume -> {
|
||||
// onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Resume, position)
|
||||
// progressBar.isIndeterminate = true
|
||||
// popup.dismiss()
|
||||
// }
|
||||
R.id.cancel -> {
|
||||
onItemClickListener.onCancelClick(item.id)
|
||||
popup.dismiss()
|
||||
|
|
@ -166,7 +166,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
|
||||
}
|
||||
|
||||
progressBar.isIndeterminate = !paused
|
||||
progressBar.isIndeterminate = !sharedPreferences.getBoolean("paused_downloads", false)
|
||||
|
||||
card.setOnClickListener {
|
||||
onItemClickListener.onCardClick()
|
||||
|
|
@ -174,7 +174,6 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
|
|||
}
|
||||
interface OnItemClickListener {
|
||||
fun onCancelClick(itemID: Long)
|
||||
fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int)
|
||||
fun onCardClick()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
formatNote.visibility = View.GONE
|
||||
}
|
||||
|
||||
|
||||
val incognitoLabel = card.findViewById<MaterialButton>(R.id.incognitoLabel)
|
||||
incognitoLabel.isVisible = item.incognito
|
||||
|
||||
val codec = card.findViewById<TextView>(R.id.codec)
|
||||
val codecText =
|
||||
if (item.format.encoding != "") {
|
||||
|
|
@ -160,7 +164,8 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
|||
return oldItem.title == newItem.title &&
|
||||
oldItem.author == newItem.author &&
|
||||
oldItem.type == newItem.type &&
|
||||
oldItem.format == newItem.format
|
||||
oldItem.format == newItem.format &&
|
||||
oldItem.incognito == newItem.incognito
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
|||
)
|
||||
}
|
||||
|
||||
val incognitoLabel = card.findViewById<MaterialButton>(R.id.incognitoLabel)
|
||||
incognitoLabel.isVisible = item.incognito
|
||||
|
||||
val formatNote = card.findViewById<TextView>(R.id.format_note)
|
||||
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||
View.GONE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package com.deniscerri.ytdl.ui.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.databinding.NavOptionsItemBinding
|
||||
|
||||
class NavBarOptionsAdapter(
|
||||
val items: MutableList<MenuItem>,
|
||||
var selectedHomeTabId: Int,
|
||||
onItemClickListener: OnItemClickListener,
|
||||
) : RecyclerView.Adapter<NavBarOptionsAdapter.NavBarOptionsViewHolder>() {
|
||||
|
||||
private val onItemClickListener: OnItemClickListener
|
||||
|
||||
init {
|
||||
this.onItemClickListener = onItemClickListener
|
||||
}
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NavBarOptionsViewHolder {
|
||||
val binding = NavOptionsItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
return NavBarOptionsViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
class NavBarOptionsViewHolder(
|
||||
val binding: NavOptionsItemBinding
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
override fun onBindViewHolder(holder: NavBarOptionsViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
val essential = listOf(R.id.homeFragment, R.id.moreFragment)
|
||||
val noHome = listOf(R.id.terminalActivity)
|
||||
holder.binding.apply {
|
||||
title.text = item.title
|
||||
checkbox.isChecked = item.isVisible || essential.contains(item.itemId)
|
||||
checkbox.isEnabled = !essential.contains(item.itemId)
|
||||
home.setImageResource(
|
||||
if (item.itemId == selectedHomeTabId)
|
||||
R.drawable.baseline_home_filled_24 else R.drawable.ic_home_outlined
|
||||
)
|
||||
home.isVisible = !noHome.contains(item.itemId)
|
||||
home.setOnClickListener {
|
||||
if (!checkbox.isChecked || selectedHomeTabId == item.itemId) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
val oldSelection = items.indexOfFirst { it.itemId == selectedHomeTabId }
|
||||
selectedHomeTabId = item.itemId
|
||||
listOf(position, oldSelection).forEach {
|
||||
notifyItemChanged(it)
|
||||
}
|
||||
}
|
||||
checkbox.setOnClickListener {
|
||||
item.isVisible = checkbox.isChecked
|
||||
if (!checkbox.isChecked){
|
||||
onItemClickListener.onNavBarOptionDeselected(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onNavBarOptionDeselected(item: NavOptionsItemBinding)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,9 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.models.ResultItem
|
||||
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
|
@ -36,21 +34,19 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private val currentDownloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
|
||||
class ConfigureDownloadBottomSheetDialog(private val currentDownloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
|
||||
private lateinit var tabLayout: TabLayout
|
||||
private lateinit var viewPager2: ViewPager2
|
||||
private lateinit var fragmentAdapter : DownloadFragmentAdapter
|
||||
private lateinit var downloadViewModel: DownloadViewModel
|
||||
private lateinit var resultViewModel: ResultViewModel
|
||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||
private lateinit var behavior: BottomSheetBehavior<View>
|
||||
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
|
||||
private lateinit var sharedPreferences : SharedPreferences
|
||||
|
||||
private var incognito: Boolean = currentDownloadItem.incognito
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||
commandTemplateViewModel = ViewModelProvider(requireActivity())[CommandTemplateViewModel::class.java]
|
||||
onDownloadItemUpdateListener = listener
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
|
@ -100,7 +96,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
}
|
||||
|
||||
//check if the item has formats and its audio-only
|
||||
val isAudioOnly = result.formats.isNotEmpty() && result.formats.none { !it.format_note.contains("audio") }
|
||||
val isAudioOnly = currentDownloadItem.allFormats.isNotEmpty() && currentDownloadItem.allFormats.none { !it.format_note.contains("audio") }
|
||||
if (isAudioOnly){
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.isClickable = true
|
||||
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(1)?.alpha = 0.3f
|
||||
|
|
@ -111,7 +107,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
fragmentAdapter = DownloadFragmentAdapter(
|
||||
fragmentManager,
|
||||
lifecycle,
|
||||
result,
|
||||
null,
|
||||
currentDownloadItem
|
||||
)
|
||||
viewPager2.adapter = fragmentAdapter
|
||||
|
|
@ -194,24 +190,39 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
val ok = view.findViewById<Button>(R.id.bottom_sheet_ok)
|
||||
ok!!.setOnClickListener {
|
||||
val item = getDownloadItem()
|
||||
onDownloadItemUpdateListener.onDownloadItemUpdate(result.id, item)
|
||||
onDownloadItemUpdateListener.onDownloadItemUpdate(item)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
||||
link.text = result.url
|
||||
link.text = currentDownloadItem.url
|
||||
link.setOnClickListener{
|
||||
UiUtil.openLinkIntent(requireContext(), result.url)
|
||||
UiUtil.openLinkIntent(requireContext(), currentDownloadItem.url)
|
||||
}
|
||||
link.setOnLongClickListener{
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), result.url)
|
||||
UiUtil.copyLinkToClipBoard(requireContext(), currentDownloadItem.url)
|
||||
true
|
||||
}
|
||||
|
||||
val incognitoBtn = view.findViewById<Button>(R.id.bottomsheet_incognito)
|
||||
incognitoBtn.alpha = if (incognito) 1f else 0.3f
|
||||
incognitoBtn.setOnClickListener {
|
||||
if (incognito) {
|
||||
it.alpha = 0.3f
|
||||
}else{
|
||||
it.alpha = 1f
|
||||
}
|
||||
|
||||
incognito = !incognito
|
||||
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
|
||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: $onOff", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
|
||||
return when(selectedTabPosition){
|
||||
val item = when(selectedTabPosition){
|
||||
0 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||
|
|
@ -225,6 +236,8 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
f.downloadItem.apply { id = currentDownloadItem.id }
|
||||
}
|
||||
}
|
||||
item.incognito = incognito
|
||||
return item
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -261,7 +274,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
|
|||
|
||||
|
||||
interface OnDownloadItemUpdateListener {
|
||||
fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem)
|
||||
fun onDownloadItemUpdate(item: DownloadItem)
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
|
@ -17,7 +18,9 @@ import android.widget.Button
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
|
|
@ -85,6 +88,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
private lateinit var result: ResultItem
|
||||
private lateinit var type: Type
|
||||
private var currentDownloadItem: DownloadItem? = null
|
||||
private var incognito: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
@ -113,6 +117,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
result = res
|
||||
currentDownloadItem = dwl
|
||||
incognito = currentDownloadItem?.incognito ?: sharedPreferences.getBoolean("incognito", false)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
|
@ -424,6 +429,20 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
(updateItem.parent as LinearLayout).visibility = View.GONE
|
||||
}
|
||||
|
||||
val incognitoBtn = view.findViewById<Button>(R.id.bottomsheet_incognito)
|
||||
incognitoBtn.alpha = if (incognito) 1f else 0.3f
|
||||
incognitoBtn.setOnClickListener {
|
||||
if (incognito) {
|
||||
it.alpha = 0.3f
|
||||
}else{
|
||||
it.alpha = 1f
|
||||
}
|
||||
|
||||
incognito = !incognito
|
||||
val onOff = if (incognito) getString(R.string.ok) else getString(R.string.disabled)
|
||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: $onOff", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
//update in the background if there is no data
|
||||
if(result.title.isEmpty() && currentDownloadItem == null && !sharedPreferences.getBoolean("quick_download", false) && type != Type.command){
|
||||
|
|
@ -604,7 +623,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem {
|
||||
return when(selectedTabPosition){
|
||||
val item = when(selectedTabPosition){
|
||||
0 -> {
|
||||
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
|
||||
f.downloadItem
|
||||
|
|
@ -618,6 +637,9 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
f.downloadItem
|
||||
}
|
||||
}
|
||||
|
||||
item.incognito = incognito
|
||||
return item
|
||||
}
|
||||
|
||||
private fun getAlsoAudioDownloadItem(finished: (it: DownloadItem) -> Unit) {
|
||||
|
|
|
|||
|
|
@ -199,9 +199,10 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
|
|||
}
|
||||
|
||||
freeSpace = view.findViewById(R.id.freespace)
|
||||
freeSpace.text = String.format(getString(R.string.freespace) + ": " + FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace
|
||||
))
|
||||
val free = FileUtil.convertFileSize(
|
||||
File(FileUtil.formatPath(downloadItem.downloadPath)).freeSpace)
|
||||
freeSpace.text = String.format( getString(R.string.freespace) + ": " + free)
|
||||
if (free == "?") freeSpace.visibility = View.GONE
|
||||
|
||||
val shortcutCount = withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.getTotalShortcutNumber()
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.deniscerri.ytdl.database.models.ResultItem
|
|||
class DownloadFragmentAdapter (
|
||||
fragmentManager : FragmentManager,
|
||||
lifecycle : Lifecycle,
|
||||
private var result: ResultItem,
|
||||
private var result: ResultItem?,
|
||||
private var downloadItem: DownloadItem?,
|
||||
private var nonSpecific: Boolean = false
|
||||
) : FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
|
|
@ -24,8 +24,8 @@ class DownloadFragmentAdapter (
|
|||
}
|
||||
|
||||
fun setTitleAuthor(t: String, a: String){
|
||||
result.title = t
|
||||
result.author = a
|
||||
result?.title = t
|
||||
result?.author = a
|
||||
downloadItem?.title = t
|
||||
downloadItem?.author = a
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,11 +167,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
if (items.all { it2 -> it2.type == items[0].type }) {
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
if (items[0].type != DownloadViewModel.Type.command) {
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomAppBar.menu[4].icon?.alpha = 255
|
||||
}
|
||||
} else {
|
||||
bottomAppBar.menu[1].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
bottomAppBar.menu[4].icon?.alpha = 30
|
||||
}
|
||||
|
||||
val type = items.first().type
|
||||
|
|
@ -270,8 +270,51 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
val allIncognito = withContext(Dispatchers.IO){
|
||||
downloadViewModel.areAllProcessingIncognito()
|
||||
}
|
||||
|
||||
bottomAppBar.menu.children.first { it.itemId == R.id.incognito }.icon!!.apply {
|
||||
alpha = if (allIncognito){
|
||||
255
|
||||
}else{
|
||||
30
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
|
||||
when (m.itemId) {
|
||||
R.id.incognito -> {
|
||||
lifecycleScope.launch {
|
||||
if (m.icon!!.alpha == 255) {
|
||||
bottomAppBar.menu[3].isEnabled = false
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.updateProcessingIncognito(false)
|
||||
withContext(Dispatchers.Main){
|
||||
m.icon!!.alpha = 30
|
||||
m.isEnabled = true
|
||||
}
|
||||
}
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
bottomAppBar.menu[3].isEnabled = true
|
||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.disabled)}", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
bottomAppBar.menu[3].isEnabled = false
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.updateProcessingIncognito(true)
|
||||
withContext(Dispatchers.Main){
|
||||
}
|
||||
}
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].isEnabled = true
|
||||
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.ok)}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.folder -> {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
|
|
@ -306,7 +349,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomAppBar.menu[4].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -318,7 +361,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 255
|
||||
bottomAppBar.menu[4].icon?.alpha = 255
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -330,7 +373,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
withContext(Dispatchers.Main){
|
||||
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
|
||||
bottomAppBar.menu[1].icon?.alpha = 255
|
||||
bottomAppBar.menu[3].icon?.alpha = 30
|
||||
bottomAppBar.menu[4].icon?.alpha = 30
|
||||
bottomSheet.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -728,11 +771,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
downloadViewModel.getItemByID(id)
|
||||
}
|
||||
|
||||
val resultItem = withContext(Dispatchers.IO){
|
||||
resultViewModel.getItemByURL(downloadItem.url)!!
|
||||
}
|
||||
if (parentFragmentManager.findFragmentByTag("configureDownloadSingleSheet") == null){
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem, this@DownloadMultipleBottomSheetDialog)
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(downloadItem, this@DownloadMultipleBottomSheetDialog)
|
||||
bottomSheet.show(parentFragmentManager, "configureDownloadSingleSheet")
|
||||
}
|
||||
}
|
||||
|
|
@ -771,7 +811,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
|
|||
override fun onClick(p0: View?) {
|
||||
}
|
||||
|
||||
override fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) {
|
||||
override fun onDownloadItemUpdate(item: DownloadItem) {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
|
|
|
|||
|
|
@ -125,10 +125,8 @@ class DownloadsAlreadyExistDialog : BottomSheetDialogFragment(), AlreadyExistsAd
|
|||
|
||||
|
||||
override fun onEditItem(alreadyExistsItem: AlreadyExistsItem, position: Int) {
|
||||
val resultItem = downloadViewModel.createResultItemFromDownload(alreadyExistsItem.downloadItem)
|
||||
val onItemUpdated = object: ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
|
||||
override fun onDownloadItemUpdate(
|
||||
resultItemID: Long,
|
||||
item: DownloadItem
|
||||
) {
|
||||
val currentIndex = duplicates.indexOf(alreadyExistsItem)
|
||||
|
|
@ -138,7 +136,7 @@ class DownloadsAlreadyExistDialog : BottomSheetDialogFragment(), AlreadyExistsAd
|
|||
adapter.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, alreadyExistsItem.downloadItem, onItemUpdated)
|
||||
val bottomSheet = ConfigureDownloadBottomSheetDialog(alreadyExistsItem.downloadItem, onItemUpdated)
|
||||
bottomSheet.show(requireActivity().supportFragmentManager, "configureDownloadSingleSheet")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.deniscerri.ytdl.database.models.Format
|
|||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.FormatSorter
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.facebook.shimmer.ShimmerFrameLayout
|
||||
|
|
@ -28,12 +29,14 @@ 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.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>? = null, private var _formats: List<List<Format>>? = null, private val _listener: OnFormatClickListener? = null) : BottomSheetDialogFragment() {
|
||||
|
|
@ -66,6 +69,8 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
private lateinit var formats: List<List<Format>>
|
||||
private lateinit var listener: OnFormatClickListener
|
||||
|
||||
private var currentFormatSource : String? = null
|
||||
|
||||
enum class FormatSorting {
|
||||
filesize, container, codec, id
|
||||
}
|
||||
|
|
@ -157,6 +162,31 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
val refreshBtn = view.findViewById<Button>(R.id.format_refresh)
|
||||
if (!hasGenericFormats || items.isEmpty() || items.first()?.url?.isEmpty() == true) refreshBtn.visibility = View.GONE
|
||||
|
||||
val formatSourceLinear = view.findViewById<MaterialCardView>(R.id.formatSourceLinear)
|
||||
val formatSourceGroup = view.findViewById<RadioGroup>(R.id.format_source_group)
|
||||
val youtubeURLMatcher = Pattern.compile("((^(https?)://)?(www.)?(m.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||
val canSwitch = items.all { youtubeURLMatcher.matcher(it!!.url).find() }
|
||||
if (canSwitch) {
|
||||
val availableSources = resources.getStringArray(R.array.formats_source)
|
||||
val availableSourcesValues = resources.getStringArray(R.array.formats_source_values)
|
||||
val currentSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
||||
formatSourceLinear.isVisible = true
|
||||
availableSources.forEachIndexed { idx, it ->
|
||||
val radio = RadioButton(requireContext())
|
||||
val tag = availableSourcesValues[idx]
|
||||
radio.text = it
|
||||
radio.tag = tag
|
||||
radio.setOnClickListener { compoundButton ->
|
||||
formatSourceGroup.clearCheck()
|
||||
formatSourceGroup.check(compoundButton.id)
|
||||
|
||||
currentFormatSource = compoundButton.tag.toString()
|
||||
refreshBtn.performClick()
|
||||
}
|
||||
formatSourceGroup.addView(radio)
|
||||
}
|
||||
formatSourceGroup.check(formatSourceGroup.children.firstOrNull { it.tag == currentSource }!!.id)
|
||||
}
|
||||
|
||||
refreshBtn.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
|
|
@ -172,6 +202,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
|
||||
chosenFormats = emptyList()
|
||||
refreshBtn.isEnabled = false
|
||||
formatSourceLinear.isVisible = false
|
||||
formatListLinearLayout.visibility = View.GONE
|
||||
shimmers.visibility = View.VISIBLE
|
||||
shimmers.startShimmer()
|
||||
|
|
@ -182,7 +213,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
formatCollection.clear()
|
||||
kotlin.runCatching {
|
||||
val res = withContext(Dispatchers.IO){
|
||||
infoUtil.getFormats(items.first()!!.url)
|
||||
infoUtil.getFormats(items.first()!!.url, currentFormatSource)
|
||||
}
|
||||
res.filter { it.format_note != "storyboard" }
|
||||
chosenFormats = if (items.first()?.type == Type.audio) {
|
||||
|
|
@ -215,7 +246,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
refreshBtn.text = progress
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
infoUtil.getFormatsMultiple(items.map { it!!.url }) {
|
||||
infoUtil.getFormatsMultiple(items.map { it!!.url }, currentFormatSource) {
|
||||
if (isActive) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
formatCollection.add(it)
|
||||
|
|
@ -259,6 +290,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
withContext(Dispatchers.Main){
|
||||
shimmers.visibility = View.GONE
|
||||
shimmers.stopShimmer()
|
||||
formatSourceLinear.isVisible = canSwitch
|
||||
filterBtn.isVisible = !hasGenericFormats
|
||||
addFormatsToView()
|
||||
refreshBtn.isVisible = hasGenericFormats
|
||||
|
|
@ -271,6 +303,7 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
refreshBtn.text = getString(R.string.update_formats)
|
||||
formatListLinearLayout.visibility = View.VISIBLE
|
||||
shimmers.visibility = View.GONE
|
||||
formatSourceLinear.isVisible = canSwitch
|
||||
shimmers.stopShimmer()
|
||||
|
||||
e.printStackTrace()
|
||||
|
|
@ -393,23 +426,16 @@ class FormatSelectionBottomSheetDialog(private val _items: List<DownloadItem?>?
|
|||
FormatSorting.filesize -> chosenFormats
|
||||
}
|
||||
|
||||
val formatSorter = FormatSorter(requireContext())
|
||||
|
||||
//filter category
|
||||
when(filterBy){
|
||||
FormatCategory.ALL -> {}
|
||||
FormatCategory.SUGGESTED -> {
|
||||
finalFormats = if (items.first()?.type == Type.audio){
|
||||
val req = downloadViewModel.getPreferredAudioRequirements()
|
||||
finalFormats
|
||||
.filter { f -> req.sumOf{req -> req(f)} >= 1 }
|
||||
.sortedByDescending { f -> req.sumOf{req -> req(f)} }
|
||||
.ifEmpty { listOf(downloadViewModel.getFormat(finalFormats, Type.audio)) }
|
||||
formatSorter.sortAudioFormats(finalFormats)
|
||||
}else{
|
||||
val req = downloadViewModel.getPreferredVideoRequirements()
|
||||
req.addAll(downloadViewModel.getPreferredAudioRequirements())
|
||||
finalFormats
|
||||
.filter { f -> req.sumOf { req -> req(f) } >= 1 }
|
||||
.sortedByDescending { f -> req.sumOf { req -> req(f) } }
|
||||
.ifEmpty { listOf(downloadViewModel.getFormat(finalFormats, Type.video)) }
|
||||
formatSorter.sortVideoFormats(finalFormats)
|
||||
}
|
||||
}
|
||||
FormatCategory.SMALLEST -> {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel.Type
|
|||
import com.deniscerri.ytdl.database.viewmodel.HistoryViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ObserveSourcesViewModel
|
||||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.Extensions.TextWithSubtitle
|
||||
import com.deniscerri.ytdl.util.InfoUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
|
@ -98,6 +99,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
private lateinit var endsAfterNr: TextInputLayout
|
||||
private lateinit var retryMissingDownloads: MaterialSwitch
|
||||
private lateinit var getOnlyNewUploads: MaterialSwitch
|
||||
private lateinit var syncWithSource: MaterialSwitch
|
||||
private lateinit var resetProcessedLinks: MaterialSwitch
|
||||
|
||||
|
||||
|
|
@ -262,9 +264,14 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
endsAfterNr = view.findViewById(R.id.after_nr)
|
||||
retryMissingDownloads = view.findViewById(R.id.retry_missing_downloads)
|
||||
getOnlyNewUploads = view.findViewById(R.id.get_new_uploads)
|
||||
syncWithSource = view.findViewById(R.id.sync_with_source)
|
||||
resetProcessedLinks = view.findViewById(R.id.reset_processed_links)
|
||||
okButton = view.findViewById(R.id.okButton)
|
||||
|
||||
// getOnlyNewUploads.text = TextWithSubtitle(getString(R.string.get_new_uploads), getString(R.string.get_new_uploads))
|
||||
// syncWithSource.text = TextWithSubtitle(getString(R.string.sync_with_source), getString(R.string.sync_with_source))
|
||||
// resetProcessedLinks.text = TextWithSubtitle(getString(R.string.reset_processed_links), getString(R.string.reset_processed_links))
|
||||
|
||||
title.editText!!.setText(currentItem?.name ?: "")
|
||||
title.editText?.doAfterTextChanged { checkIfValid() }
|
||||
|
||||
|
|
@ -469,6 +476,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
|
||||
retryMissingDownloads.isChecked = currentItem?.retryMissingDownloads ?: false
|
||||
getOnlyNewUploads.isChecked = currentItem?.getOnlyNewUploads ?: false
|
||||
syncWithSource.isChecked = currentItem?.syncWithSource ?: false
|
||||
resetProcessedLinks.isVisible = currentItem != null
|
||||
|
||||
if (currentItem != null) okButton.text = getString(R.string.update)
|
||||
|
|
@ -518,6 +526,7 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
|
|||
endsAfterCount = endsAfterCount,
|
||||
runCount = 0,
|
||||
getOnlyNewUploads = getOnlyNewUploads.isChecked,
|
||||
syncWithSource = syncWithSource.isChecked,
|
||||
retryMissingDownloads = retryMissingDownloads.isChecked,
|
||||
alreadyProcessedLinks = if (resetProcessedLinks.isChecked){
|
||||
mutableListOf()
|
||||
|
|
|
|||
|
|
@ -597,47 +597,6 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
|
||||
cancelActiveDownload(itemID)
|
||||
}
|
||||
override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
when(action){
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Pause -> {
|
||||
lifecycleScope.launch {
|
||||
cancelItem(itemID.toInt())
|
||||
item.status = DownloadRepository.Status.ActivePaused.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeAdapter.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
|
||||
lifecycleScope.launch {
|
||||
item.status = DownloadRepository.Status.Queued.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
activeAdapter.notifyItemChanged(position)
|
||||
|
||||
val queue = if (activeCount > 1) listOf(item)
|
||||
else withContext(Dispatchers.IO){
|
||||
val list = downloadViewModel.getQueued().toMutableList()
|
||||
list.map { it.status = DownloadRepository.Status.Queued.toString() }
|
||||
list.add(0, item)
|
||||
list
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(queue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
override fun onCardClick() {
|
||||
this.dismiss()
|
||||
findNavController().navigate(
|
||||
|
|
@ -645,12 +604,6 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
|
|||
)
|
||||
}
|
||||
|
||||
private fun cancelItem(id: Int){
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toString())
|
||||
notificationUtil.cancelDownloadNotification(id)
|
||||
}
|
||||
|
||||
private fun cancelActiveDownload(itemID: Long){
|
||||
lifecycleScope.launch {
|
||||
val id = itemID.toInt()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
package com.deniscerri.ytdl.ui.downloads
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -16,21 +29,36 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkQuery
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.adapter.ActiveDownloadAdapter
|
||||
import com.deniscerri.ytdl.ui.adapter.QueuedDownloadAdapter
|
||||
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.Extensions.forceFastScrollMode
|
||||
import com.deniscerri.ytdl.util.Extensions.toListString
|
||||
import com.deniscerri.ytdl.util.FileUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.work.DownloadWorker
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
import com.google.android.material.badge.ExperimentalBadgeUtils
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
|
@ -42,14 +70,27 @@ import org.greenrobot.eventbus.Subscribe
|
|||
import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
|
||||
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener {
|
||||
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, QueuedDownloadAdapter.OnItemClickListener {
|
||||
private var fragmentView: View? = null
|
||||
private var activity: Activity? = null
|
||||
private lateinit var downloadViewModel : DownloadViewModel
|
||||
|
||||
private lateinit var activeDownloadsLinear: LinearLayout
|
||||
private lateinit var activeDownloadsNoResults: View
|
||||
private lateinit var activeRecyclerView : RecyclerView
|
||||
private lateinit var activeDownloads : ActiveDownloadAdapter
|
||||
|
||||
private lateinit var queuedDownloadsLinear: LinearLayout
|
||||
private lateinit var queuedRecyclerView : RecyclerView
|
||||
private lateinit var queuedDownloads : QueuedDownloadAdapter
|
||||
private lateinit var queuedFilesizeCount : TextView
|
||||
private var queuedTotalSize = 0
|
||||
private lateinit var queuedDragHandleToggle : TextView
|
||||
private var queuedActionMode : ActionMode? = null
|
||||
private lateinit var inQueueLabel : MaterialButton
|
||||
|
||||
private lateinit var activeQueuedLinear: LinearLayout
|
||||
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var pause: ExtendedFloatingActionButton
|
||||
private lateinit var resume: ExtendedFloatingActionButton
|
||||
|
|
@ -67,7 +108,8 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
return fragmentView
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@OptIn(ExperimentalBadgeUtils::class)
|
||||
@SuppressLint("NotifyDataSetChanged", "RestrictedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
@ -77,12 +119,34 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
workManager = WorkManager.getInstance(requireContext())
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
activeDownloadsLinear = view.findViewById(R.id.active_linear)
|
||||
activeDownloadsNoResults = view.findViewById(R.id.active_no_results)
|
||||
activeDownloads = ActiveDownloadAdapter(this,requireActivity())
|
||||
activeRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||
activeRecyclerView.forceFastScrollMode()
|
||||
activeRecyclerView.adapter = activeDownloads
|
||||
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
queuedDownloadsLinear = view.findViewById(R.id.queue_linear)
|
||||
val itemTouchHelper = ItemTouchHelper(queuedDragDropHelper)
|
||||
queuedDownloads = QueuedDownloadAdapter(this, requireActivity(), itemTouchHelper)
|
||||
queuedRecyclerView = view.findViewById(R.id.queued_recyclerview)
|
||||
queuedRecyclerView.forceFastScrollMode()
|
||||
queuedRecyclerView.adapter = queuedDownloads
|
||||
queuedRecyclerView.enableFastScroll()
|
||||
itemTouchHelper.attachToRecyclerView(queuedRecyclerView)
|
||||
queuedFilesizeCount = view.findViewById(R.id.filesize)
|
||||
queuedDragHandleToggle = view.findViewById(R.id.drag)
|
||||
inQueueLabel = view.findViewById(R.id.in_queue_label)
|
||||
|
||||
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("queued")){
|
||||
val swipeItemTouchHelper = ItemTouchHelper(queuedSwipeCallback)
|
||||
swipeItemTouchHelper.attachToRecyclerView(queuedRecyclerView)
|
||||
}
|
||||
queuedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||
|
||||
activeQueuedLinear = view.findViewById(R.id.active_queued_linear)
|
||||
|
||||
pause = view.findViewById(R.id.pause)
|
||||
pause.isVisible = false
|
||||
resume = view.findViewById(R.id.resume)
|
||||
|
|
@ -91,56 +155,123 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
pause.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
withContext(Dispatchers.IO){
|
||||
val activeDownloadsList = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getActiveDownloads()
|
||||
}.forEach {
|
||||
}
|
||||
|
||||
activeDownloadsList.forEach {
|
||||
YoutubeDL.getInstance().destroyProcessById(it.id.toString())
|
||||
notificationUtil.cancelDownloadNotification(it.id.toInt())
|
||||
}
|
||||
preferences.edit().putBoolean("paused_downloads", true).apply()
|
||||
pause.isEnabled = false
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
resume.isVisible = true
|
||||
pause.isVisible = false
|
||||
withContext(Dispatchers.Main){
|
||||
delay(1000)
|
||||
pause.isVisible = false
|
||||
pause.isEnabled = true
|
||||
resume.isVisible = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
resume.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
preferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
resume.isVisible = false
|
||||
pause.isVisible = true
|
||||
resume.isEnabled = false
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadViewModel.resetActivePaused()
|
||||
downloadViewModel.resetActiveToQueued()
|
||||
downloadViewModel.startDownloadWorker(listOf())
|
||||
withContext(Dispatchers.Main){
|
||||
activeDownloads.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
delay(1000)
|
||||
resume.isVisible = false
|
||||
resume.isEnabled = true
|
||||
pause.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
val activeQueuedDownloadsCount = withContext(Dispatchers.IO) {
|
||||
downloadViewModel.getActiveDownloadsCount()
|
||||
}
|
||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||
pause.isVisible = activeQueuedDownloadsCount > 0 && !pausedDownloads
|
||||
resume.isVisible = activeQueuedDownloadsCount > 0 && pausedDownloads
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeQueuedDownloadsCount.collectLatest {
|
||||
activeQueuedLinear.isVisible = it > 0
|
||||
noResults.isVisible = it == 0
|
||||
if (it == 0) {
|
||||
pause.isVisible = false
|
||||
resume.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloads.collectLatest {
|
||||
activeDownloadsNoResults.isVisible = it.isEmpty()
|
||||
activeDownloads.submitList(it)
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.queuedDownloads.collectLatest {
|
||||
queuedDownloads.submitData(it)
|
||||
}
|
||||
}
|
||||
|
||||
queuedDownloads.addLoadStateListener { loadState ->
|
||||
lifecycleScope.launch {
|
||||
if (loadState.append.endOfPaginationReached )
|
||||
{
|
||||
if (queuedDownloads.itemCount < 1){
|
||||
queuedFilesizeCount.visibility = View.GONE
|
||||
}else{
|
||||
val size = withContext(Dispatchers.IO){
|
||||
FileUtil.convertFileSize(downloadViewModel.getQueuedCollectedFileSize())
|
||||
}
|
||||
if (size == "?") queuedFilesizeCount.visibility = View.GONE
|
||||
else {
|
||||
queuedFilesizeCount.visibility = View.VISIBLE
|
||||
queuedFilesizeCount.text = "${getString(R.string.file_size)}: ~ $size"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloadsCount.collectLatest {
|
||||
noResults.isVisible = it == 0
|
||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||
pause.isVisible = it > 0 && !pausedDownloads
|
||||
downloadViewModel.getTotalSize(listOf(DownloadRepository.Status.Queued)).observe(viewLifecycleOwner){
|
||||
queuedDownloadsLinear.isVisible = it > 0
|
||||
queuedTotalSize = it
|
||||
queuedDragHandleToggle.isVisible = it > 1
|
||||
if (it > 0) {
|
||||
val badgeDrawable = BadgeDrawable.create(requireContext())
|
||||
badgeDrawable.number = it
|
||||
badgeDrawable.isVisible = true
|
||||
badgeDrawable.verticalOffset = 25
|
||||
requireView().post {
|
||||
BadgeUtils.attachBadgeDrawable(badgeDrawable, inQueueLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeQueuedDownloadsCount.collectLatest {
|
||||
val pausedDownloads = preferences.getBoolean("paused_downloads", false)
|
||||
resume.isVisible = it > 0 && pausedDownloads
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.activeDownloads.collectLatest {
|
||||
delay(100)
|
||||
activeDownloads.submitList(it)
|
||||
}
|
||||
queuedDragHandleToggle.setOnClickListener {
|
||||
queuedDownloads.toggleShowDragHandle()
|
||||
}
|
||||
}
|
||||
|
||||
//dont remove
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onDownloadProgressEvent(event: DownloadWorker.WorkerProgress) {
|
||||
val progressBar = requireView().findViewWithTag<LinearProgressIndicator>("${event.downloadItemID}##progress")
|
||||
|
|
@ -154,6 +285,10 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
fun hidePauseBtn(){
|
||||
pause.isVisible = false
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
EventBus.getDefault().register(this)
|
||||
|
|
@ -205,17 +340,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
override fun onResumeClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOutputClick(item: DownloadItem) {
|
||||
if (item.logID != null && item.logID != 0L) {
|
||||
val bundle = Bundle()
|
||||
|
|
@ -227,4 +351,398 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
|
|||
}
|
||||
}
|
||||
|
||||
//QUEUED ITEMS FUNCTIONS
|
||||
private val queuedContextualActionBar = object : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode!!.menuInflater.inflate(R.menu.queued_menu_context, menu)
|
||||
mode.title = "${queuedDownloads.getSelectedObjectsCount(queuedTotalSize)} ${getString(R.string.selected)}"
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(
|
||||
mode: ActionMode?,
|
||||
menu: Menu?
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(
|
||||
mode: ActionMode?,
|
||||
item: MenuItem?
|
||||
): Boolean {
|
||||
return when (item!!.itemId) {
|
||||
R.id.select_between -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedIDs = getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(
|
||||
DownloadRepository.Status.Queued).toListString())
|
||||
}.toMutableList()
|
||||
idsInMiddle.addAll(selectedIDs)
|
||||
if (idsInMiddle.isNotEmpty()){
|
||||
queuedDownloads.checkMultipleItems(idsInMiddle)
|
||||
queuedActionMode?.title = "${idsInMiddle.count()} ${getString(R.string.selected)}"
|
||||
}
|
||||
mode?.menu?.findItem(R.id.select_between)?.isVisible = false
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.delete_results -> {
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete_multiple_items))
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
queuedDownloads.clearCheckedItems()
|
||||
for (id in selectedObjects){
|
||||
YoutubeDL.getInstance().destroyProcessById(id.toInt().toString())
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
|
||||
notificationUtil.cancelDownloadNotification(id.toInt())
|
||||
}
|
||||
downloadViewModel.deleteAllWithID(selectedObjects)
|
||||
queuedActionMode?.finish()
|
||||
}
|
||||
|
||||
}
|
||||
deleteDialog.show()
|
||||
true
|
||||
}
|
||||
R.id.select_all -> {
|
||||
queuedDownloads.checkAll()
|
||||
mode?.title = "(${queuedDownloads.getSelectedObjectsCount(queuedTotalSize)}) ${resources.getString(R.string.all_items_selected)}"
|
||||
true
|
||||
}
|
||||
R.id.invert_selected -> {
|
||||
queuedDownloads.invertSelected()
|
||||
val selectedObjects = queuedDownloads.getSelectedObjectsCount(queuedTotalSize)
|
||||
queuedActionMode!!.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
if (selectedObjects == 0) queuedActionMode?.finish()
|
||||
true
|
||||
}
|
||||
R.id.up -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
queuedDownloads.clearCheckedItems()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.putAtTopOfQueue(selectedObjects)
|
||||
}
|
||||
queuedActionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.down -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
queuedDownloads.clearCheckedItems()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.putAtBottomOfQueue(selectedObjects)
|
||||
}
|
||||
queuedActionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.copy_urls -> {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = getSelectedIDs()
|
||||
val urls = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getURLsByIds(selectedObjects)
|
||||
}
|
||||
UiUtil.copyToClipboard(urls.joinToString("\n"), requireActivity())
|
||||
queuedActionMode?.finish()
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
queuedActionMode = null
|
||||
queuedDownloads.clearCheckedItems()
|
||||
}
|
||||
|
||||
suspend fun getSelectedIDs() : List<Long>{
|
||||
return if (queuedDownloads.inverted || queuedDownloads.checkedItems.isEmpty()){
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemIDsNotPresentIn(queuedDownloads.checkedItems.toList(), listOf(
|
||||
DownloadRepository.Status.Queued))
|
||||
}
|
||||
}else{
|
||||
queuedDownloads.checkedItems.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var movedToNewPositionID = 0L
|
||||
private val queuedDragDropHelper: ItemTouchHelper.SimpleCallback =
|
||||
object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) {
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val fromPosition = viewHolder.bindingAdapterPosition
|
||||
val toPosition = target.bindingAdapterPosition
|
||||
movedToNewPositionID = target.itemView.tag.toString().toLong()
|
||||
queuedDownloads.notifyItemMoved(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
}
|
||||
|
||||
override fun onSelectedChanged(
|
||||
viewHolder: RecyclerView.ViewHolder?,
|
||||
actionState: Int
|
||||
) {
|
||||
super.onSelectedChanged(viewHolder, actionState)
|
||||
if (ItemTouchHelper.ACTION_STATE_DRAG == actionState) {
|
||||
/**
|
||||
* Change alpha, scale and elevation on drag.
|
||||
*/
|
||||
(viewHolder?.itemView as? MaterialCardView)?.also {
|
||||
AnimatorSet().apply {
|
||||
this.duration = 100L
|
||||
this.interpolator = AccelerateDecelerateInterpolator()
|
||||
|
||||
playTogether(
|
||||
UiUtil.getAlphaAnimator(it, 0.7f),
|
||||
UiUtil.getScaleXAnimator(it, 1.02f),
|
||||
UiUtil.getScaleYAnimator(it, 1.02f),
|
||||
UiUtil.getElevationAnimator(it, R.dimen.elevation_6dp)
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearView(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
) {
|
||||
super.clearView(recyclerView, viewHolder)
|
||||
/**
|
||||
* Clear alpha, scale and elevation after drag/swipe
|
||||
*/
|
||||
(viewHolder.itemView as? MaterialCardView)?.also {
|
||||
AnimatorSet().apply {
|
||||
this.duration = 100L
|
||||
this.interpolator = AccelerateDecelerateInterpolator()
|
||||
|
||||
playTogether(
|
||||
UiUtil.getAlphaAnimator(it, 1f),
|
||||
UiUtil.getScaleXAnimator(it, 1f),
|
||||
UiUtil.getScaleYAnimator(it, 1f),
|
||||
UiUtil.getElevationAnimator(it, R.dimen.elevation_2dp)
|
||||
)
|
||||
}.start()
|
||||
}
|
||||
|
||||
downloadViewModel.putAtPosition(viewHolder.itemView.tag.toString().toLong(), movedToNewPositionID)
|
||||
}
|
||||
|
||||
override fun isLongPressDragEnabled(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var queuedSwipeCallback: 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 itemID = viewHolder.itemView.tag.toString().toLong()
|
||||
when (direction) {
|
||||
ItemTouchHelper.LEFT -> {
|
||||
lifecycleScope.launch {
|
||||
val deletedItem = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
queuedDownloads.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
||||
removeQueuedItem(deletedItem.id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChildDraw(
|
||||
c: Canvas,
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
dX: Float,
|
||||
dY: Float,
|
||||
actionState: Int,
|
||||
isCurrentlyActive: Boolean
|
||||
) {
|
||||
RecyclerViewSwipeDecorator.Builder(
|
||||
requireContext(),
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
.addSwipeLeftBackgroundColor(Color.RED)
|
||||
.addSwipeLeftActionIcon(R.drawable.baseline_delete_24)
|
||||
.addSwipeRightBackgroundColor(
|
||||
MaterialColors.getColor(
|
||||
requireContext(),
|
||||
R.attr.colorOnSurfaceInverse, Color.TRANSPARENT
|
||||
)
|
||||
)
|
||||
.create()
|
||||
.decorate()
|
||||
super.onChildDraw(
|
||||
c,
|
||||
recyclerView,
|
||||
viewHolder,
|
||||
dX,
|
||||
dY,
|
||||
actionState,
|
||||
isCurrentlyActive
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueuedCancelClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
YoutubeDL.getInstance().destroyProcessById(itemID.toInt().toString())
|
||||
notificationUtil.cancelDownloadNotification(itemID.toInt())
|
||||
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}.let {
|
||||
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveQueuedItemToTop(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.putAtTopOfQueue(listOf(itemID))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMoveQueuedItemToBottom(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.putAtBottomOfQueue(listOf(itemID))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueuedCardClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
|
||||
UiUtil.showDownloadItemDetailsCard(
|
||||
item,
|
||||
requireActivity(),
|
||||
DownloadRepository.Status.valueOf(item.status),
|
||||
removeItem = { it: DownloadItem, sheet: BottomSheetDialog ->
|
||||
sheet.hide()
|
||||
removeQueuedItem(it.id)
|
||||
},
|
||||
downloadItem = {
|
||||
downloadViewModel.deleteDownload(it.id)
|
||||
it.downloadStartTime = 0
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(it.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(it))
|
||||
}
|
||||
},
|
||||
longClickDownloadButton = {
|
||||
lifecycleScope.launch {
|
||||
it.status = DownloadRepository.Status.Saved.toString()
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.updateToStatus(it.id, DownloadRepository.Status.Saved)
|
||||
}
|
||||
findNavController().navigate(R.id.downloadBottomSheetDialog, bundleOf(
|
||||
Pair("downloadItem", it),
|
||||
Pair("result", downloadViewModel.createResultItemFromDownload(it)),
|
||||
Pair("type", it.type)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
scheduleButtonClick = {downloadItem ->
|
||||
UiUtil.showDatePicker(parentFragmentManager) {
|
||||
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||
downloadViewModel.deleteDownload(downloadItem.id)
|
||||
downloadItem.downloadStartTime = it.timeInMillis
|
||||
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(downloadItem.id.toString())
|
||||
runBlocking {
|
||||
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onQueuedCardSelect(isChecked: Boolean, position: Int) {
|
||||
lifecycleScope.launch {
|
||||
val selectedObjects = queuedDownloads.getSelectedObjectsCount(queuedTotalSize)
|
||||
if (queuedActionMode == null) queuedActionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(queuedContextualActionBar)
|
||||
val now = System.currentTimeMillis()
|
||||
queuedActionMode?.apply {
|
||||
if (selectedObjects == 0){
|
||||
this.finish()
|
||||
}else{
|
||||
this.title = "$selectedObjects ${getString(R.string.selected)}"
|
||||
|
||||
this.menu.findItem(R.id.up).isVisible = position > 0
|
||||
this.menu.findItem(R.id.down).isVisible = position < queuedTotalSize
|
||||
this.menu.findItem(R.id.select_between).isVisible = false
|
||||
if(selectedObjects == 2){
|
||||
val selectedIDs = queuedContextualActionBar.getSelectedIDs().sortedBy { it }
|
||||
val idsInMiddle = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getIDsBetweenTwoItems(selectedIDs.first(), selectedIDs.last(), listOf(DownloadRepository.Status.Queued).toListString())
|
||||
}
|
||||
this.menu.findItem(R.id.select_between).isVisible = idsInMiddle.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeQueuedItem(id: Long){
|
||||
lifecycleScope.launch {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(id)
|
||||
}
|
||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||
lifecycleScope.launch(Dispatchers.IO){
|
||||
downloadViewModel.updateDownload(item)
|
||||
}
|
||||
|
||||
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||
.setAction(getString(R.string.undo)) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
downloadViewModel.deleteDownload(item.id)
|
||||
downloadViewModel.queueDownloads(listOf(item))
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
deleteDialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.deniscerri.ytdl.ui.downloads
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
|
|
@ -16,6 +17,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.get
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -29,7 +31,9 @@ import com.deniscerri.ytdl.MainActivity
|
|||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.downloadcard.DownloadAudioFragment
|
||||
import com.deniscerri.ytdl.util.Extensions.createBadge
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.NotificationUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
@ -52,6 +56,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
private lateinit var viewPager2: ViewPager2
|
||||
private lateinit var fragmentAdapter : DownloadListFragmentAdapter
|
||||
private lateinit var mainActivity: MainActivity
|
||||
private lateinit var notificationUtil: NotificationUtil
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -60,6 +65,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
mainActivity = activity as MainActivity
|
||||
notificationUtil = NotificationUtil(mainActivity)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
return inflater.inflate(R.layout.fragment_download_queue_main_screen, container, false)
|
||||
}
|
||||
|
|
@ -69,7 +75,11 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
workManager = WorkManager.getInstance(requireContext())
|
||||
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
|
||||
|
||||
topAppBar = view.findViewById(R.id.logs_toolbar)
|
||||
topAppBar = view.findViewById(R.id.downloads_toolbar)
|
||||
val isInNavBar = NavbarUtil.getNavBarItems(requireActivity()).any { n -> n.itemId == R.id.downloadQueueMainFragment && n.isVisible }
|
||||
if (isInNavBar) {
|
||||
topAppBar.navigationIcon = null
|
||||
}
|
||||
topAppBar.setNavigationOnClickListener { mainActivity.onBackPressedDispatcher.onBackPressed() }
|
||||
|
||||
tabLayout = view.findViewById(R.id.download_tablayout)
|
||||
|
|
@ -80,7 +90,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
}
|
||||
|
||||
val fragments = mutableListOf(ActiveDownloadsFragment(), QueuedDownloadsFragment(), ScheduledDownloadsFragment(), CancelledDownloadsFragment(), ErroredDownloadsFragment(), SavedDownloadsFragment())
|
||||
val fragments = mutableListOf(ActiveDownloadsFragment(), /*QueuedDownloadsFragment(),*/ ScheduledDownloadsFragment(), CancelledDownloadsFragment(), ErroredDownloadsFragment(), SavedDownloadsFragment())
|
||||
|
||||
fragmentAdapter = DownloadListFragmentAdapter(
|
||||
childFragmentManager,
|
||||
|
|
@ -94,11 +104,11 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
|
||||
when (position) {
|
||||
0 -> tab.text = getString(R.string.running)
|
||||
1 -> tab.text = getString(R.string.in_queue)
|
||||
2 -> tab.text = getString(R.string.scheduled)
|
||||
3 -> tab.text = getString(R.string.cancelled)
|
||||
4 -> tab.text = getString(R.string.errored)
|
||||
5 -> tab.text = getString(R.string.saved)
|
||||
// 1 -> tab.text = getString(R.string.in_queue)
|
||||
1 -> tab.text = getString(R.string.scheduled)
|
||||
2 -> tab.text = getString(R.string.cancelled)
|
||||
3 -> tab.text = getString(R.string.errored)
|
||||
4 -> tab.text = getString(R.string.saved)
|
||||
}
|
||||
}.attach()
|
||||
|
||||
|
|
@ -124,11 +134,12 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
initMenu()
|
||||
|
||||
if (arguments?.getString("tab") != null){
|
||||
tabLayout.getTabAt(4)!!.select()
|
||||
tabLayout.getTabAt(3)!!.select()
|
||||
viewPager2.postDelayed( {
|
||||
viewPager2.setCurrentItem(4, false)
|
||||
viewPager2.setCurrentItem(3, false)
|
||||
val reconfigureID = arguments?.getLong("reconfigure")
|
||||
reconfigureID?.apply {
|
||||
notificationUtil.cancelErroredNotification(this.toInt())
|
||||
lifecycleScope.launch {
|
||||
kotlin.runCatching {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
|
|
@ -155,30 +166,30 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
}
|
||||
}
|
||||
// lifecycleScope.launch {
|
||||
// downloadViewModel.queuedDownloadsCount.collectLatest {
|
||||
// tabLayout.getTabAt(1)?.apply {
|
||||
// createBadge(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.queuedDownloadsCount.collectLatest {
|
||||
downloadViewModel.scheduledDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(1)?.apply {
|
||||
createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.scheduledDownloadsCount.collectLatest {
|
||||
downloadViewModel.cancelledDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(2)?.apply {
|
||||
createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.cancelledDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(3)?.apply {
|
||||
createBadge(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.erroredDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(4)?.apply {
|
||||
tabLayout.getTabAt(3)?.apply {
|
||||
removeBadge()
|
||||
if (it > 0) createBadge(it)
|
||||
}
|
||||
|
|
@ -186,7 +197,7 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
}
|
||||
lifecycleScope.launch {
|
||||
downloadViewModel.savedDownloadsCount.collectLatest {
|
||||
tabLayout.getTabAt(5)?.apply {
|
||||
tabLayout.getTabAt(4)?.apply {
|
||||
removeBadge()
|
||||
if (it > 0) createBadge(it)
|
||||
}
|
||||
|
|
@ -264,7 +275,10 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
deleteDialog.show()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun cancelAllDownloads() {
|
||||
sharedPreferences.edit().putBoolean("paused_downloads", false).apply()
|
||||
fragmentAdapter.notifyDataSetChanged()
|
||||
workManager.cancelAllWorkByTag("download")
|
||||
lifecycleScope.launch {
|
||||
val notificationUtil = NotificationUtil(requireContext())
|
||||
|
|
@ -278,4 +292,9 @@ class DownloadQueueMainFragment : Fragment(){
|
|||
downloadViewModel.cancelActiveQueued()
|
||||
}
|
||||
}
|
||||
|
||||
fun scrollToActive(){
|
||||
tabLayout.getTabAt(0)!!.select()
|
||||
viewPager2.setCurrentItem(0, true)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,7 @@ 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.util.Extensions.enableFastScroll
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
|
@ -200,8 +201,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
initChips()
|
||||
}
|
||||
|
||||
private fun scrollToTop() {
|
||||
recyclerView!!.scrollToPosition(0)
|
||||
fun scrollToTop() {
|
||||
recyclerView.scrollToPosition(0)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
(topAppBar!!.parent as AppBarLayout).setExpanded(
|
||||
true,
|
||||
|
|
@ -239,6 +240,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
}
|
||||
})
|
||||
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 ->
|
||||
when (m.itemId) {
|
||||
R.id.remove_history -> {
|
||||
|
|
@ -402,7 +405,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
|||
val websites = mutableListOf<String>()
|
||||
val websiteFilter = historyViewModel.websiteFilter.value
|
||||
for (item in list){
|
||||
if (!websites.contains(item.website.lowercase())) websites.add(item.website.lowercase())
|
||||
if (!websites.contains(item.website)) websites.add(item.website)
|
||||
}
|
||||
websiteGroup!!.removeAllViews()
|
||||
if (websites.size <= 1) {
|
||||
|
|
|
|||
|
|
@ -112,11 +112,8 @@ class ScheduledDownloadsFragment : Fragment(), ScheduledDownloadAdapter.OnItemCl
|
|||
override fun onActionButtonClick(itemID: Long) {
|
||||
lifecycleScope.launch {
|
||||
runCatching {
|
||||
val item = withContext(Dispatchers.IO){
|
||||
downloadViewModel.getItemByID(itemID)
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.resetScheduleTimeForItemsAndStartDownload(listOf(item.id))
|
||||
downloadViewModel.resetScheduleTimeForItemsAndStartDownload(listOf(itemID))
|
||||
}
|
||||
}.onFailure {
|
||||
Toast.makeText(requireContext(), it.message, Toast.LENGTH_LONG).show()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
|
|
@ -20,6 +21,7 @@ import com.deniscerri.ytdl.database.repository.DownloadRepository
|
|||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
|
||||
import com.deniscerri.ytdl.ui.more.terminal.TerminalActivity
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -63,6 +65,9 @@ class MoreFragment : Fragment() {
|
|||
terminateApp = view.findViewById(R.id.terminate)
|
||||
settings = view.findViewById(R.id.settings)
|
||||
|
||||
val showingTerminal = NavbarUtil.getNavBarItems(requireContext()).any { n -> n.itemId == R.id.terminalActivity && n.isVisible }
|
||||
terminal.isVisible = !showingTerminal
|
||||
|
||||
terminal.setOnClickListener {
|
||||
val intent = Intent(context, TerminalActivity::class.java)
|
||||
startActivity(intent)
|
||||
|
|
@ -76,6 +81,9 @@ class MoreFragment : Fragment() {
|
|||
findNavController().navigate(R.id.commandTemplatesFragment)
|
||||
}
|
||||
|
||||
val showingDownloadQueue = NavbarUtil.getNavBarItems(requireContext()).any { n -> n.itemId == R.id.downloadQueueMainFragment && n.isVisible }
|
||||
downloadQueue.isVisible = !showingDownloadQueue
|
||||
|
||||
downloadQueue.setOnClickListener {
|
||||
findNavController().navigate(R.id.downloadQueueMainFragment)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,42 @@ package com.deniscerri.ytdl.ui.more.settings
|
|||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.databinding.NavOptionsItemBinding
|
||||
import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter
|
||||
import com.deniscerri.ytdl.util.NavbarUtil
|
||||
import com.deniscerri.ytdl.util.ThemeUtil
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
|
|
@ -36,13 +52,17 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
private var showTerminalShareIcon: SwitchPreferenceCompat? = null
|
||||
private var ignoreBatteryOptimization: Preference? = null
|
||||
private var displayOverApps: SwitchPreferenceCompat? = null
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
private var updateUtil: UpdateUtil? = null
|
||||
private var activeDownloadCount = 0
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.general_preferences, rootKey)
|
||||
NavbarUtil.init(requireContext())
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
updateUtil = UpdateUtil(requireContext())
|
||||
val editor = preferences.edit()
|
||||
|
||||
WorkManager.getInstance(requireContext()).getWorkInfosByTagLiveData("download").observe(this){
|
||||
activeDownloadCount = 0
|
||||
|
|
@ -66,6 +86,90 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("label_visibility")?.apply {
|
||||
isVisible = !resources.getBoolean(R.bool.uses_side_nav)
|
||||
setOnPreferenceChangeListener { preference, newValue ->
|
||||
restartApp()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("navigation_bar")?.apply {
|
||||
isVisible = !resources.getBoolean(R.bool.uses_side_nav)
|
||||
setOnPreferenceClickListener {
|
||||
val binding = requireActivity().layoutInflater.inflate(R.layout.simple_options_recycler, null)
|
||||
val options = NavbarUtil.getNavBarItems(requireContext())
|
||||
|
||||
val optionsRecycler = binding.findViewById<RecyclerView>(R.id.options_recycler)
|
||||
var adapter : NavBarOptionsAdapter? = null;
|
||||
|
||||
val onitemClick = object: NavBarOptionsAdapter.OnItemClickListener {
|
||||
override fun onNavBarOptionDeselected(item: NavOptionsItemBinding) {
|
||||
optionsRecycler.findViewHolderForLayoutPosition(0)?.apply {
|
||||
(this as NavBarOptionsAdapter.NavBarOptionsViewHolder).apply {
|
||||
this.binding.home.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter = NavBarOptionsAdapter(
|
||||
options.toMutableList(),
|
||||
NavbarUtil.getStartFragmentId(requireContext()),
|
||||
onitemClick
|
||||
)
|
||||
|
||||
val itemTouchCallback = object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
return makeMovementFlags(dragFlags, 0)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val itemToMove = adapter.items[viewHolder.absoluteAdapterPosition]
|
||||
adapter.items.remove(itemToMove)
|
||||
adapter.items.add(target.absoluteAdapterPosition, itemToMove)
|
||||
|
||||
adapter.notifyItemMoved(
|
||||
viewHolder.absoluteAdapterPosition,
|
||||
target.absoluteAdapterPosition
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
optionsRecycler.layoutManager = LinearLayoutManager(context)
|
||||
optionsRecycler.adapter = adapter
|
||||
|
||||
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
|
||||
itemTouchHelper.attachToRecyclerView(optionsRecycler)
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.navigation_bar)
|
||||
.setView(binding)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
NavbarUtil.setNavBarItems(adapter.items, requireContext())
|
||||
NavbarUtil.setStartFragment(adapter.selectedHomeTabId)
|
||||
restartApp()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
theme!!.summary = theme!!.entry
|
||||
theme!!.onPreferenceChangeListener =
|
||||
|
|
@ -153,6 +257,14 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("piped_instance")?.setOnPreferenceClickListener {
|
||||
UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){
|
||||
editor.putString("piped_instance", it)
|
||||
editor.apply()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
@ -164,6 +276,15 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
super.onResume()
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(requireContext(), MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finishAffinity()
|
||||
activity?.finishAffinity()
|
||||
}
|
||||
|
||||
private var displayOverAppsResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
|
|
@ -171,5 +292,4 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
|||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,17 +1,24 @@
|
|||
package com.deniscerri.ytdl.ui.more.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.util.LayoutDirection
|
||||
import android.util.Log
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.os.LocaleListCompat
|
||||
|
|
@ -24,6 +31,7 @@ import androidx.preference.PreferenceFragmentCompat
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.BuildConfig
|
||||
import com.deniscerri.ytdl.MainActivity
|
||||
import com.deniscerri.ytdl.R
|
||||
|
|
@ -31,6 +39,7 @@ import com.deniscerri.ytdl.database.models.CommandTemplate
|
|||
import com.deniscerri.ytdl.database.models.CookieItem
|
||||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||
import com.deniscerri.ytdl.database.models.RestoreAppDataItem
|
||||
import com.deniscerri.ytdl.database.models.observeSources.ObserveSourcesItem
|
||||
import com.deniscerri.ytdl.database.models.SearchHistoryItem
|
||||
import com.deniscerri.ytdl.database.models.TemplateShortcut
|
||||
|
|
@ -42,13 +51,17 @@ import com.deniscerri.ytdl.database.viewmodel.ObserveSourcesViewModel
|
|||
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
|
||||
import com.deniscerri.ytdl.util.UiUtil
|
||||
import com.deniscerri.ytdl.util.UpdateUtil
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -418,11 +431,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
runCatching {
|
||||
val ip = requireContext().contentResolver.openInputStream(result.data!!.data!!)
|
||||
val r = BufferedReader(InputStreamReader(ip))
|
||||
|
|
@ -432,308 +441,148 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
total.append(line).append('\n')
|
||||
}
|
||||
|
||||
//PARSE RESTORE JSON
|
||||
val json = Gson().fromJson(total.toString(), JsonObject::class.java)
|
||||
val finalMessage = StringBuilder("")
|
||||
val errorMessage = StringBuilder("")
|
||||
val restoreData = RestoreAppDataItem()
|
||||
val parsedDataMessage = StringBuilder()
|
||||
|
||||
//preference restore
|
||||
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")
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n PREFERENCE RESTORE FAILED: \n ${it.message}")
|
||||
if (json.has("settings")) {
|
||||
restoreData.settings = json.getAsJsonArray("settings")
|
||||
parsedDataMessage.appendLine("${getString(R.string.settings)}: ${restoreData.settings!!.size()}")
|
||||
}
|
||||
|
||||
//history restore
|
||||
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")
|
||||
if (json.has("downloads")) {
|
||||
restoreData.downloads = json.getAsJsonArray("downloads").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), HistoryItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n HISTORY RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.downloads)}: ${restoreData.downloads!!.size}")
|
||||
|
||||
}
|
||||
|
||||
//queued downloads restore
|
||||
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)}
|
||||
}
|
||||
if (json.has("queued")) {
|
||||
restoreData.queued = json.getAsJsonArray("queued").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n QUEUED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.queue)}: ${restoreData.queued!!.size}")
|
||||
}
|
||||
|
||||
//scheduled downloads restore
|
||||
kotlin.runCatching {
|
||||
if(json.has("scheduled")){
|
||||
val items = json.getAsJsonArray("scheduled")
|
||||
val scheduled = mutableListOf<DownloadItem>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
scheduled.add(item)
|
||||
}
|
||||
if (scheduled.isNotEmpty()) {
|
||||
finalMessage.append("${getString(R.string.scheduled)}: ${scheduled.count()}\n")
|
||||
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(scheduled)}
|
||||
}
|
||||
if (json.has("scheduled")) {
|
||||
restoreData.scheduled = json.getAsJsonArray("scheduled").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n SCHEDULED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.scheduled)}: ${restoreData.scheduled!!.size}")
|
||||
}
|
||||
|
||||
//cancelled downloads restore
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if(cancelled.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
|
||||
}
|
||||
if (json.has("cancelled")) {
|
||||
restoreData.cancelled = json.getAsJsonArray("cancelled").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n CANCELLED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.cancelled)}: ${restoreData.cancelled!!.size}")
|
||||
}
|
||||
|
||||
//erorred downloads restore
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if(errored.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
||||
}
|
||||
if (json.has("errored")) {
|
||||
restoreData.errored = json.getAsJsonArray("errored").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n ERRORED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.errored)}: ${restoreData.errored!!.size}")
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
}
|
||||
|
||||
if(saved.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.saved)}: ${saved.count()}\n")
|
||||
}
|
||||
if (json.has("saved")) {
|
||||
restoreData.saved = json.getAsJsonArray("saved").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n SAVED DOWNLOADS RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.saved)}: ${restoreData.saved!!.size}")
|
||||
}
|
||||
|
||||
//cookies restore
|
||||
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 (json.has("cookies")) {
|
||||
restoreData.cookies = json.getAsJsonArray("cookies").map {
|
||||
val item =
|
||||
Gson().fromJson(it.toString().replace("^\"|\"$", ""), CookieItem::class.java)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n COOKIES RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.cookies)}: ${restoreData.cookies!!.size}")
|
||||
}
|
||||
|
||||
//command template restore
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
|
||||
}
|
||||
if (json.has("templates")) {
|
||||
restoreData.templates = json.getAsJsonArray("templates").map {
|
||||
val item = Gson().fromJson(
|
||||
it.toString().replace("^\"|\"$", ""),
|
||||
CommandTemplate::class.java
|
||||
)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n COMMAND TEMPLATE RESTORE FAILED: \n ${it.message}")
|
||||
parsedDataMessage.appendLine("${getString(R.string.command_templates)}: ${restoreData.templates!!.size}")
|
||||
}
|
||||
|
||||
//shortcuts restore
|
||||
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 (json.has("shortcuts")) {
|
||||
restoreData.shortcuts = json.getAsJsonArray("shortcuts").map {
|
||||
val item = Gson().fromJson(
|
||||
it.toString().replace("^\"|\"$", ""),
|
||||
TemplateShortcut::class.java
|
||||
)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n COMMAND SHORTCUTS RESTORE FAILED: \n ${it.message}")
|
||||
|
||||
parsedDataMessage.appendLine("${getString(R.string.shortcuts)}: ${restoreData.shortcuts!!.size}")
|
||||
|
||||
}
|
||||
|
||||
|
||||
//search history restore
|
||||
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 (json.has("search_history")) {
|
||||
restoreData.searchHistory = json.getAsJsonArray("search_history").map {
|
||||
val item = Gson().fromJson(
|
||||
it.toString().replace("^\"|\"$", ""),
|
||||
SearchHistoryItem::class.java
|
||||
)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n SEARCH HISTORY RESTORE FAILED: \n ${it.message}")
|
||||
|
||||
parsedDataMessage.appendLine("${getString(R.string.search_history)}: ${restoreData.searchHistory!!.size}")
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
}
|
||||
if (json.has("observe_sources")) {
|
||||
restoreData.observeSources = json.getAsJsonArray("observe_sources").map {
|
||||
val item = Gson().fromJson(
|
||||
it.toString().replace("^\"|\"$", ""),
|
||||
ObserveSourcesItem::class.java
|
||||
)
|
||||
item.id = 0L
|
||||
item
|
||||
}
|
||||
}.onFailure {
|
||||
errorMessage.append("\n SEARCH HISTORY RESTORE FAILED: \n ${it.message}")
|
||||
|
||||
parsedDataMessage.appendLine("${getString(R.string.observe_sources)}: ${restoreData.observeSources!!.size}")
|
||||
}
|
||||
|
||||
if (finalMessage.isEmpty()) throw Exception("")
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.restore))
|
||||
builder.setMessage("${getString(R.string.restore_complete)}\n $finalMessage \n $errorMessage")
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.restart)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
val intent = Intent(requireContext(), MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finishAffinity()
|
||||
if(json.has("settings")){
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(preferences.getString("app_language", "en")))
|
||||
showAppRestoreInfoDialog(
|
||||
onMerge = {
|
||||
restoreData(restoreData,parsedDataMessage.toString())
|
||||
},
|
||||
onReset = {
|
||||
restoreData(restoreData,parsedDataMessage.toString(), true)
|
||||
}
|
||||
activity?.finishAffinity()
|
||||
}
|
||||
)
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
if(json.has("settings")){
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(preferences.getString("app_language", "en")))
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
Snackbar.make(requireView(), getString(R.string.couldnt_parse_file), Snackbar.LENGTH_LONG).show()
|
||||
|
|
@ -743,4 +592,185 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun showAppRestoreInfoDialog(onMerge: () -> Unit, onReset: () -> Unit){
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.restore))
|
||||
builder.setMessage(getString(R.string.restore_info))
|
||||
|
||||
builder.setNegativeButton(getString(R.string.cancel)) { dialog : DialogInterface?, _: Int ->
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
builder.setPositiveButton(getString(R.string.restore)) { dialog : DialogInterface?, _: Int ->
|
||||
onMerge()
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
builder.setNeutralButton(getString(R.string.reset)) { dialog : DialogInterface?, _: Int ->
|
||||
onReset()
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
|
||||
}
|
||||
|
||||
private fun restoreData(data: RestoreAppDataItem, restoreDataMessage: String, resetData: Boolean = false) = lifecycleScope.launch {
|
||||
data.settings?.apply {
|
||||
val prefs = this
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit(commit = true){
|
||||
clear()
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data.downloads?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) historyViewModel.deleteAll(false)
|
||||
data.downloads!!.forEach {
|
||||
historyViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.queued?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteQueued()
|
||||
data.queued!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
downloadViewModel.queueDownloads(listOf())
|
||||
}
|
||||
}
|
||||
|
||||
data.cancelled?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteCancelled()
|
||||
data.cancelled!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.errored?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteErrored()
|
||||
data.errored!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.saved?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) downloadViewModel.deleteSaved()
|
||||
data.saved!!.forEach {
|
||||
downloadViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.cookies?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) cookieViewModel.deleteAll()
|
||||
data.cookies!!.forEach {
|
||||
cookieViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.templates?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) commandTemplateViewModel.deleteAll()
|
||||
data.templates!!.forEach {
|
||||
commandTemplateViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.shortcuts?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) commandTemplateViewModel.deleteAllShortcuts()
|
||||
data.shortcuts!!.forEach {
|
||||
commandTemplateViewModel.insertShortcut(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.searchHistory?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) resultViewModel.deleteAllSearchQueryHistory()
|
||||
data.searchHistory!!.forEach {
|
||||
resultViewModel.addSearchQueryToHistory(it.query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.observeSources?.apply {
|
||||
withContext(Dispatchers.IO){
|
||||
if (resetData) observeSourcesViewModel.deleteAll()
|
||||
data.observeSources!!.forEach {
|
||||
observeSourcesViewModel.insert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder.setTitle(getString(R.string.restore))
|
||||
builder.setMessage("${getString(R.string.restore_complete)}\n $restoreDataMessage")
|
||||
builder.setPositiveButton(
|
||||
getString(R.string.restart)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
val intent = Intent(requireContext(), MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
requireActivity().finishAffinity()
|
||||
if(data.settings != null){
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(preferences.getString("app_language", "en")))
|
||||
}
|
||||
activity?.finishAffinity()
|
||||
}
|
||||
|
||||
// handle the negative button of the alert dialog
|
||||
builder.setNegativeButton(
|
||||
getString(R.string.cancel)
|
||||
) { _: DialogInterface?, _: Int ->
|
||||
if(data.settings != null){
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(preferences.getString("app_language", "en")))
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
|
@ -55,13 +55,6 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
|
|||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("piped_instance")?.setOnPreferenceClickListener {
|
||||
UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){
|
||||
editor.putString("piped_instance", it)
|
||||
editor.apply()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("changelog")?.setOnPreferenceClickListener {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import android.graphics.drawable.shapes.OvalShape
|
|||
import android.media.MediaMetadataRetriever
|
||||
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
|
||||
import android.net.Uri
|
||||
import android.text.Html
|
||||
import android.text.Spanned
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.MotionEvent
|
||||
|
|
@ -30,6 +32,7 @@ import android.widget.TextView
|
|||
import androidx.annotation.Px
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.withStarted
|
||||
|
|
@ -468,4 +471,9 @@ object Extensions {
|
|||
downloadStartTime = timeInMillis
|
||||
}
|
||||
|
||||
fun TextWithSubtitle(title: String, subtitle: String) : Spanned {
|
||||
return HtmlCompat.fromHtml("<b><big>" + title + "</big></b>" + "<br />" +
|
||||
"<small>" + subtitle + "</small>" + "<br />", HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
}
|
||||
|
||||
}
|
||||
155
app/src/main/java/com/deniscerri/ytdl/util/FormatSorter.kt
Normal file
155
app/src/main/java/com/deniscerri/ytdl/util/FormatSorter.kt
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.contentValuesOf
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import org.json.JSONObject
|
||||
import java.text.Normalizer.Form
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class FormatSorter(private var context: Context) {
|
||||
private val sharedPreferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private val videoFormatIDPreference : List<String> = sharedPreferences.getString("format_id", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
private val audioFormatIDPreference : List<String> = sharedPreferences.getString("format_id_audio", "").toString().split(",").filter { it.isNotEmpty() }
|
||||
private val videoQualityPreference : String = sharedPreferences.getString("video_quality", "best").toString()
|
||||
private val audioLanguagePreference : String = sharedPreferences.getString("audio_language", "").toString()
|
||||
private val audioCodecPreference : String = sharedPreferences.getString("audio_codec", "").toString()
|
||||
private val videoCodecPreference : String = sharedPreferences.getString("video_codec", "").toString()
|
||||
private val audioContainerPreference : String = sharedPreferences.getString("audio_format", "").toString()
|
||||
private val videoContainerPreference : String = sharedPreferences.getString("video_format", "").toString()
|
||||
@SuppressLint("RestrictedApi")
|
||||
private val videoResolutionOrder = context.getStringArray(R.array.video_formats_values).filter { it.contains("_") }.map{ it.split("_")[0].dropLast(1) }.reversed()
|
||||
private val preferSmallerFormats = sharedPreferences.getBoolean("prefer_smaller_formats", false)
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun getAudioFormatImportance() : Set<String> {
|
||||
val itemValues = context.getStringArray(R.array.format_importance_audio_values).toSet()
|
||||
val orderPreferences = sharedPreferences.getString("format_importance_audio", itemValues.joinToString(","))!!.split(",").toMutableSet()
|
||||
if (preferSmallerFormats) {
|
||||
orderPreferences.add("smallsize")
|
||||
}
|
||||
|
||||
return orderPreferences
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun getVideoFormatImportance() : Set<String> {
|
||||
val itemValues = context.getStringArray(R.array.format_importance_video_values).toSet()
|
||||
val orderPreferences = sharedPreferences.getString("format_importance_video", itemValues.joinToString(","))!!.split(",").toMutableSet()
|
||||
if (preferSmallerFormats) {
|
||||
orderPreferences.add("smallsize")
|
||||
}
|
||||
|
||||
return orderPreferences
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun sortAudioFormats(formats: List<Format>) : List<Format> {
|
||||
val orderPreferences = getAudioFormatImportance()
|
||||
|
||||
val fieldSorter: Comparator<Format> = Comparator { a, b ->
|
||||
for (order in orderPreferences) {
|
||||
when(order) {
|
||||
"smallsize" -> {
|
||||
(a.filesize).compareTo(b.filesize)
|
||||
}
|
||||
"id" -> {
|
||||
(audioFormatIDPreference.contains(b.format_id)).compareTo(audioFormatIDPreference.contains(a.format_id))
|
||||
}
|
||||
"language" -> {
|
||||
if (audioLanguagePreference.isBlank()) {
|
||||
0
|
||||
}
|
||||
else {
|
||||
(b.lang?.contains(audioLanguagePreference) == true).compareTo(a.lang?.contains(audioLanguagePreference) == true)
|
||||
}
|
||||
}
|
||||
"codec" -> {
|
||||
("^(${audioCodecPreference}).+$".toRegex(RegexOption.IGNORE_CASE).matches(b.acodec))
|
||||
.compareTo("^(${audioCodecPreference}).+$".toRegex(RegexOption.IGNORE_CASE).matches(a.acodec))
|
||||
}
|
||||
"container" -> {
|
||||
(audioContainerPreference == b.container).compareTo(audioContainerPreference == a.container)
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
return formats.sortedWith(fieldSorter)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun sortVideoFormats(formats: List<Format>): List<Format> {
|
||||
val orderPreferences = getVideoFormatImportance()
|
||||
|
||||
val fieldSorter = object : Comparator<Format> {
|
||||
override fun compare(a: Format, b: Format): Int {
|
||||
for (order in orderPreferences) {
|
||||
val comparison = when (order) {
|
||||
"smallsize" -> {
|
||||
val result = a.filesize.compareTo(b.filesize)
|
||||
result
|
||||
}
|
||||
"id" -> {
|
||||
videoFormatIDPreference.contains(b.format_id).compareTo(videoFormatIDPreference.contains(a.format_id))
|
||||
}
|
||||
"codec" -> {
|
||||
"^(${videoCodecPreference}).+$".toRegex(RegexOption.IGNORE_CASE).matches(b.vcodec.uppercase())
|
||||
.compareTo("^(${videoCodecPreference}).+$".toRegex(RegexOption.IGNORE_CASE).matches(a.vcodec.uppercase()))
|
||||
}
|
||||
"resolution" -> {
|
||||
when (videoQualityPreference) {
|
||||
"worst" -> {
|
||||
b.format_note.contains("worst", ignoreCase = true)
|
||||
.compareTo(a.format_note.contains("worst", ignoreCase = true))
|
||||
}
|
||||
"best" -> {
|
||||
b.format_note.contains("best", ignoreCase = true)
|
||||
.compareTo(a.format_note.contains("best", ignoreCase = true))
|
||||
}
|
||||
else -> {
|
||||
val preferenceIndex = videoResolutionOrder.indexOfFirst { videoQualityPreference.contains(it, true) }
|
||||
|
||||
val aIndex = videoResolutionOrder.indexOfFirst { a.format_note.contains(it, ignoreCase = true) }
|
||||
val bIndex = videoResolutionOrder.indexOfFirst { b.format_note.contains(it, ignoreCase = true) }
|
||||
|
||||
if (aIndex > preferenceIndex || bIndex > preferenceIndex) {
|
||||
-1
|
||||
}else if(aIndex == -1 && bIndex == -1){
|
||||
-1
|
||||
}else{
|
||||
bIndex.compareTo(aIndex)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
"no_audio" -> {
|
||||
(b.acodec == "none" || b.acodec == "").compareTo(a.acodec == "none" || a.acodec == "")
|
||||
}
|
||||
"container" -> {
|
||||
videoContainerPreference.equals(b.container, ignoreCase = true)
|
||||
.compareTo(videoContainerPreference.equals(a.container, ignoreCase = true))
|
||||
}
|
||||
else -> 0
|
||||
}
|
||||
if (comparison != 0) return comparison
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return formats.sortedWith(fieldSorter)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -439,18 +439,26 @@ class InfoUtil(private val context: Context) {
|
|||
return query!!
|
||||
}
|
||||
|
||||
fun getFormats(url: String) : List<Format> {
|
||||
fun getFormats(url: String, source : String? = null) : List<Format> {
|
||||
|
||||
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
||||
val p = Pattern.compile("((^(https?)://)?(www.)?(m.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||
val m = p.matcher(url)
|
||||
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
||||
val formatSource = source ?: sharedPreferences.getString("formats_source", "yt-dlp")
|
||||
if (m.find() && formatSource == "piped"){
|
||||
return try {
|
||||
try {
|
||||
val id = getIDFromYoutubeURL(url)
|
||||
val res = genericRequest("$pipedURL/streams/$id")
|
||||
if (res.length() == 0) getFromYTDL(url)[0]
|
||||
val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id")
|
||||
item!!.formats
|
||||
if (res.length() == 0) {
|
||||
return if (source != null) {
|
||||
listOf()
|
||||
}else {
|
||||
getFormatsFromYTDL(url)
|
||||
}
|
||||
}else {
|
||||
val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id")
|
||||
return item!!.formats
|
||||
}
|
||||
|
||||
}catch(e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
return getFormatsFromYTDL(url)
|
||||
|
|
@ -505,7 +513,7 @@ class InfoUtil(private val context: Context) {
|
|||
return parseYTDLFormats(jsonArray)
|
||||
}
|
||||
|
||||
fun getFormatsMultiple(urls: List<String>, progress: (progress: List<Format>) -> Unit){
|
||||
fun getFormatsMultiple(urls: List<String>, source: String? = null, progress: (progress: List<Format>) -> Unit){
|
||||
val urlsFile = File(context.cacheDir, "urls.txt")
|
||||
urlsFile.delete()
|
||||
urlsFile.createNewFile()
|
||||
|
|
@ -513,8 +521,8 @@ class InfoUtil(private val context: Context) {
|
|||
urlsFile.appendText(it+"\n")
|
||||
}
|
||||
|
||||
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
||||
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
||||
val formatSource = source ?: sharedPreferences.getString("formats_source", "yt-dlp")
|
||||
val p = Pattern.compile("((^(https?)://)?(www.)?(m.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
|
||||
val allYoutubeLinks = urls.any {p.matcher(it).find() }
|
||||
if (formatSource == "yt-dlp" || !allYoutubeLinks){
|
||||
try {
|
||||
|
|
@ -678,7 +686,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
var website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor")
|
||||
var website = jsonObject.getStringByAny("extractor_key", "extractor","ie_key")
|
||||
if (website == "Generic" || website == "HTML5MediaEmbed") website = jsonObject.getStringByAny("webpage_url_domain")
|
||||
var playlistTitle = jsonObject.getStringByAny("playlist_title")
|
||||
var playlistURL: String? = ""
|
||||
|
|
@ -728,8 +736,6 @@ class InfoUtil(private val context: Context) {
|
|||
val type = jsonObject.getStringByAny("_type")
|
||||
if (type == "playlist" && playlistTitle.isEmpty()) {
|
||||
playlistTitle = title
|
||||
title = ""
|
||||
author = ""
|
||||
}
|
||||
|
||||
val res = ResultItem(0,
|
||||
|
|
@ -758,32 +764,34 @@ class InfoUtil(private val context: Context) {
|
|||
if (formatsInJSON != null) {
|
||||
for (f in formatsInJSON.length() - 1 downTo 0){
|
||||
val format = formatsInJSON.getJSONObject(f)
|
||||
if (format.has("filesize")){
|
||||
if (format.get("filesize") == "None"){
|
||||
kotlin.runCatching {
|
||||
if (format.get("filesize").toString() == "None") {
|
||||
format.remove("filesize")
|
||||
if (format.has("filesize_approx") && format.get("filesize_approx") != "None"){
|
||||
format.put("filesize", format.getInt("filesize_approx"))
|
||||
}else{
|
||||
format.put("filesize", 0)
|
||||
}
|
||||
}
|
||||
try{
|
||||
val size = format.get("filesize").toString().toFloat()
|
||||
format.remove("filesize")
|
||||
format.put("filesize", size)
|
||||
}catch (ignored: Exception){}
|
||||
}
|
||||
|
||||
if (format.has("filesize_approx")){
|
||||
if (format.get("filesize_approx") == "None"){
|
||||
kotlin.runCatching {
|
||||
if (format.get("filesize_approx").toString() == "None") {
|
||||
format.remove("filesize_approx")
|
||||
format.put("filesize_approx", 0)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
if (!format.has("filesize")) {
|
||||
format.put("filesize", 0)
|
||||
}
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
if(format.get("format_note").toString() == "null"){
|
||||
format.remove("format_note")
|
||||
}
|
||||
}
|
||||
|
||||
val formatProper = Gson().fromJson(format.toString(), Format::class.java)
|
||||
|
||||
Log.e("NOTE", format.toString())
|
||||
if (formatProper.format_note == null) continue
|
||||
|
||||
if (format.has("format_note")){
|
||||
if (!formatProper!!.format_note.contains("audio only", true)) {
|
||||
formatProper.format_note = format.getString("format_note")
|
||||
|
|
@ -947,14 +955,17 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
|
||||
val request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank()){
|
||||
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest {
|
||||
lateinit var request : YoutubeDLRequest
|
||||
|
||||
val matchFilter = mutableListOf<String>()
|
||||
request = if (downloadItem.playlistURL.isNullOrBlank() || downloadItem.playlistTitle.isBlank()){
|
||||
YoutubeDLRequest(downloadItem.url)
|
||||
}else{
|
||||
YoutubeDLRequest(downloadItem.playlistURL!!).apply {
|
||||
if(downloadItem.playlistIndex == null){
|
||||
val matchPortion = downloadItem.url.split("/").last().split("=").last().split("&").first()
|
||||
addOption("--match-filter", "id~='${matchPortion}'")
|
||||
matchFilter.add("id~='${matchPortion}'")
|
||||
}else{
|
||||
addOption("-I", "${downloadItem.playlistIndex!!}:${downloadItem.playlistIndex}")
|
||||
}
|
||||
|
|
@ -964,7 +975,8 @@ class InfoUtil(private val context: Context) {
|
|||
val type = downloadItem.type
|
||||
|
||||
val downDir : File
|
||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite()){
|
||||
val canWrite = File(FileUtil.formatPath(downloadItem.downloadPath)).canWrite() || sharedPreferences.getBoolean("access_all_files", false)
|
||||
if (!sharedPreferences.getBoolean("cache_downloads", true) && canWrite){
|
||||
downDir = File(FileUtil.formatPath(downloadItem.downloadPath))
|
||||
request.addOption("--no-quiet")
|
||||
request.addOption("--no-simulate")
|
||||
|
|
@ -1066,7 +1078,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
if(downloadItem.title.isNotBlank()){
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(120)))
|
||||
request.addCommands(listOf("--replace-in-metadata", "video:title", ".+", downloadItem.title.take(180)))
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1092,7 +1104,7 @@ class InfoUtil(private val context: Context) {
|
|||
filenameTemplate = if (filenameTemplate.isBlank()){
|
||||
"%(section_title&{} |)s%(title).170B"
|
||||
}else{
|
||||
"%(section_title&{} |)s$filenameTemplate"
|
||||
"%(section_title&{} |)s $filenameTemplate"
|
||||
}
|
||||
if (downloadItem.downloadSections.split(";").size > 1){
|
||||
filenameTemplate = "%(autonumber)d. $filenameTemplate [%(section_start>%H∶%M∶%S)s]"
|
||||
|
|
@ -1126,8 +1138,10 @@ class InfoUtil(private val context: Context) {
|
|||
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
|
||||
audioQualityId = "wa/w"
|
||||
}else if(audioQualityId.contains("kbps_ytdlnisgeneric")){
|
||||
request.addOption("--match-filter", "abr<=${audioQualityId.split("kbps")[0]}")
|
||||
matchFilter.add("abr<=${audioQualityId.split("kbps")[0]}")
|
||||
audioQualityId = ""
|
||||
}else{
|
||||
audioQualityId += "/ba/b"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1271,7 +1285,7 @@ class InfoUtil(private val context: Context) {
|
|||
if (downloadItem.videoPreferences.removeAudio) audioF = ""
|
||||
|
||||
if(audioF.contains("kbps_ytdlnisgeneric")){
|
||||
request.addOption("--match-filter", "abr<=${audioF.split("kbps")[0]}")
|
||||
matchFilter.add("abr<=${audioF.split("kbps")[0]}")
|
||||
audioF = ""
|
||||
}
|
||||
|
||||
|
|
@ -1452,6 +1466,10 @@ class InfoUtil(private val context: Context) {
|
|||
else -> {}
|
||||
}
|
||||
|
||||
if (matchFilter.isNotEmpty()) {
|
||||
request.addOption("--match-filter", matchFilter.joinToString(" & "))
|
||||
}
|
||||
|
||||
if (downloadItem.extraCommands.isNotBlank() && downloadItem.type != DownloadViewModel.Type.command){
|
||||
val cache = File(FileUtil.getCachePath(context))
|
||||
cache.mkdirs()
|
||||
|
|
|
|||
136
app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt
Normal file
136
app/src/main/java/com/deniscerri/ytdl/util/NavbarUtil.kt
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package com.deniscerri.ytdl.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.PopupMenu
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.isGone
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.deniscerri.ytdl.R
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import java.util.*
|
||||
|
||||
|
||||
object NavbarUtil {
|
||||
|
||||
lateinit var settings : SharedPreferences
|
||||
|
||||
fun init(context: Context){
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
private var navItems = mapOf(
|
||||
"Home" to R.id.homeFragment,
|
||||
"History" to R.id.historyFragment,
|
||||
"Downloads" to R.id.downloadQueueMainFragment,
|
||||
"Terminal" to R.id.terminalActivity,
|
||||
"More" to R.id.moreFragment
|
||||
)
|
||||
|
||||
fun getStartFragmentId(context: Context) : Int {
|
||||
val pref = settings.getString("start_destination", "")!!
|
||||
val items = getDefaultNavBarItems(context)
|
||||
return if (pref == "") {
|
||||
R.id.homeFragment
|
||||
}else {
|
||||
items.firstOrNull { it.itemId == navItems[pref] }!!.itemId
|
||||
}
|
||||
}
|
||||
|
||||
fun setNavBarItems(items: List<MenuItem>, context: Context) {
|
||||
val prefString = mutableListOf<String>()
|
||||
val defaultNavBarItems = getDefaultNavBarItems(context)
|
||||
items.forEach { newItem ->
|
||||
val index = defaultNavBarItems.indexOfFirst { newItem.itemId == it.itemId }
|
||||
prefString.add(if (newItem.isVisible) index.toString() else "-$index")
|
||||
}
|
||||
settings.edit().putString("navigation_bar", prefString.joinToString(",")).apply()
|
||||
}
|
||||
|
||||
fun setStartFragment(itemId: Int) {
|
||||
settings.edit().putString("start_destination", navItems.filter { it.value == itemId }.keys.first()).apply()
|
||||
}
|
||||
|
||||
fun getNavBarItems(context: Context): List<MenuItem> {
|
||||
val prefItems = try {
|
||||
getNavBarPrefs()
|
||||
} catch (e: Exception) {
|
||||
Log.e("fail to parse nav items", e.toString())
|
||||
return getDefaultNavBarItems(context)
|
||||
}
|
||||
val p = PopupMenu(context, null)
|
||||
MenuInflater(context).inflate(R.menu.bottom_nav_menu, p.menu)
|
||||
|
||||
if (prefItems.size == p.menu.size()) {
|
||||
val navBarItems = mutableListOf<MenuItem>()
|
||||
prefItems.forEach {
|
||||
navBarItems.add(
|
||||
p.menu[it.replace("-", "").toInt()].apply {
|
||||
this.isVisible = !it.contains("-")
|
||||
}
|
||||
)
|
||||
}
|
||||
return navBarItems
|
||||
}
|
||||
return getDefaultNavBarItems(context)
|
||||
}
|
||||
|
||||
private fun getNavBarPrefs(): List<String> {
|
||||
return settings
|
||||
.getString("navigation_bar", "Home,History,More")!!
|
||||
.split(",")
|
||||
}
|
||||
|
||||
private fun getDefaultNavBarItems(context: Context): List<MenuItem> {
|
||||
val p = PopupMenu(context, null)
|
||||
MenuInflater(context).inflate(R.menu.bottom_nav_menu, p.menu)
|
||||
val navBarItems = mutableListOf<MenuItem>()
|
||||
p.menu.forEach {
|
||||
navBarItems.add(it)
|
||||
}
|
||||
return navBarItems
|
||||
}
|
||||
|
||||
fun NavigationBarView.applyNavBarStyle(): Int {
|
||||
val labelVisibilityMode = when (
|
||||
settings.getString("label_visibility", "always")
|
||||
) {
|
||||
"always" -> NavigationBarView.LABEL_VISIBILITY_LABELED
|
||||
"selected" -> NavigationBarView.LABEL_VISIBILITY_SELECTED
|
||||
"never" -> NavigationBarView.LABEL_VISIBILITY_UNLABELED
|
||||
else -> NavigationBarView.LABEL_VISIBILITY_AUTO
|
||||
}
|
||||
this.labelVisibilityMode = labelVisibilityMode
|
||||
|
||||
val navBarItems = getNavBarItems(this.context)
|
||||
|
||||
val menuItems = mutableListOf<MenuItem>()
|
||||
// remove the old items
|
||||
navBarItems.forEach {
|
||||
menuItems.add(
|
||||
this.menu.findItem(it.itemId)
|
||||
)
|
||||
this.menu.removeItem(it.itemId)
|
||||
}
|
||||
|
||||
navBarItems.forEach { navBarItem ->
|
||||
if (navBarItem.isVisible) {
|
||||
val menuItem = menuItems.first { it.itemId == navBarItem.itemId }
|
||||
|
||||
this.menu.add(
|
||||
menuItem.groupId,
|
||||
menuItem.itemId,
|
||||
Menu.NONE,
|
||||
menuItem.title
|
||||
).icon = menuItem.icon
|
||||
}
|
||||
}
|
||||
return getStartFragmentId(this.context)
|
||||
}
|
||||
}
|
||||
|
|
@ -424,7 +424,7 @@ class NotificationUtil(var context: Context) {
|
|||
.setContentTitle(title)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
.clearActions()
|
||||
.addAction(0, resources.getString(R.string.pause), pauseNotificationPendingIntent)
|
||||
//.addAction(0, resources.getString(R.string.pause), pauseNotificationPendingIntent)
|
||||
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
|
||||
notificationManager.notify(id, notificationBuilder.build())
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -469,6 +469,11 @@ class NotificationUtil(var context: Context) {
|
|||
fun cancelDownloadNotification(id: Int) {
|
||||
notificationManager.cancel(id)
|
||||
}
|
||||
|
||||
fun cancelErroredNotification(id: Int) {
|
||||
notificationManager.cancel(DOWNLOAD_ERRORED_NOTIFICATION_ID + id)
|
||||
}
|
||||
|
||||
fun createMoveCacheFilesNotification(pendingIntent: PendingIntent?, downloadMiscChannelId: String): Notification {
|
||||
val notificationBuilder = getBuilder(downloadMiscChannelId)
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import com.deniscerri.ytdl.database.models.CommandTemplate
|
|||
import com.deniscerri.ytdl.database.models.DownloadItem
|
||||
import com.deniscerri.ytdl.database.models.Format
|
||||
import com.deniscerri.ytdl.database.models.HistoryItem
|
||||
import com.deniscerri.ytdl.database.models.RestoreAppDataItem
|
||||
import com.deniscerri.ytdl.database.models.TemplateShortcut
|
||||
import com.deniscerri.ytdl.database.repository.DownloadRepository
|
||||
import com.deniscerri.ytdl.database.viewmodel.CommandTemplateViewModel
|
||||
|
|
@ -96,6 +97,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
|
|
@ -580,15 +582,15 @@ object UiUtil {
|
|||
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
||||
text = item.title.ifEmpty { item.playlistTitle.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } }
|
||||
setOnLongClickListener{
|
||||
text = item.title.ifEmpty { item.url.ifEmpty { item.playlistTitle.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } } }
|
||||
setOnLongClickListener {
|
||||
showFullTextDialog(context, text.toString(), context.getString(R.string.title))
|
||||
true
|
||||
}
|
||||
}
|
||||
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
|
||||
text = item.author
|
||||
setOnLongClickListener{
|
||||
setOnLongClickListener {
|
||||
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
|
||||
true
|
||||
}
|
||||
|
|
@ -761,15 +763,17 @@ object UiUtil {
|
|||
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||
|
||||
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
|
||||
text = item!!.title
|
||||
setOnClickListener{
|
||||
text = item!!.title.ifEmpty { item.url }
|
||||
setOnLongClickListener{
|
||||
showFullTextDialog(context, text.toString(), context.getString(R.string.title))
|
||||
true
|
||||
}
|
||||
}
|
||||
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
|
||||
text = item!!.author
|
||||
setOnClickListener{
|
||||
setOnLongClickListener{
|
||||
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class DownloadWorker(
|
|||
if (currentWork.count{it.state == WorkInfo.State.RUNNING} > 1) return Result.success()
|
||||
|
||||
// this is needed for observe sources call, so it wont create result items
|
||||
val createResultItem = inputData.getBoolean("createResultItem", true)
|
||||
//val createResultItem = inputData.getBoolean("createResultItem", true)
|
||||
|
||||
val confTmp = Configuration(context.resources.configuration)
|
||||
val currLang = sharedPreferences.getString("app_language", "")!!.ifEmpty { Locale.getDefault().language }.split("-")
|
||||
|
|
@ -134,7 +134,7 @@ class DownloadWorker(
|
|||
|
||||
val downloadLocation = downloadItem.downloadPath
|
||||
val keepCache = sharedPreferences.getBoolean("keep_cache", false)
|
||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
|
||||
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !downloadItem.incognito
|
||||
|
||||
|
||||
val commandString = infoUtil.parseYTDLRequestString(request)
|
||||
|
|
@ -162,6 +162,7 @@ class DownloadWorker(
|
|||
val eventBus = EventBus.getDefault()
|
||||
|
||||
runCatching {
|
||||
YoutubeDL.getInstance().destroyProcessById(downloadItem.id.toString())
|
||||
YoutubeDL.getInstance().execute(request, downloadItem.id.toString()){ progress, _, line ->
|
||||
eventBus.post(WorkerProgress(progress.toInt(), line, downloadItem.id))
|
||||
val title: String = downloadItem.title.ifEmpty { downloadItem.url }
|
||||
|
|
@ -242,8 +243,7 @@ class DownloadWorker(
|
|||
FileUtil.deleteConfigFiles(request)
|
||||
|
||||
//put download in history
|
||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
if (!incognito) {
|
||||
if (!downloadItem.incognito) {
|
||||
if (request.hasOption("--download-archive") && finalPaths == listOf(context.getString(R.string.unfound_file))) {
|
||||
handler.postDelayed({
|
||||
Toast.makeText(context, resources.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
||||
|
|
@ -264,7 +264,7 @@ class DownloadWorker(
|
|||
|
||||
val historyItem = HistoryItem(0,
|
||||
downloadItem.url,
|
||||
downloadItem.title.ifEmpty { downloadItem.playlistTitle },
|
||||
downloadItem.title,
|
||||
downloadItem.author,
|
||||
downloadItem.duration,
|
||||
downloadItem.thumb,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.deniscerri.ytdl.work
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
|
|
@ -54,7 +55,27 @@ class ObserveSourceWorker(
|
|||
val foregroundInfo = ForegroundInfo(workerID, notification)
|
||||
setForegroundAsync(foregroundInfo)
|
||||
|
||||
val res = runCatching { infoUtil.getFromYTDL(item.url).toList() }.getOrElse { listOf() }
|
||||
val list = kotlin.runCatching {
|
||||
infoUtil.getFromYTDL(item.url)
|
||||
}.onFailure {
|
||||
Log.e("observe", it.toString())
|
||||
}.getOrElse { listOf() }
|
||||
|
||||
if (list.isNotEmpty() && item.syncWithSource && item.alreadyProcessedLinks.isNotEmpty()){
|
||||
val processedLinks = item.alreadyProcessedLinks
|
||||
val incomingLinks = list.map { it.url }
|
||||
|
||||
val linksNotPresentAnymore = processedLinks.filter { !incomingLinks.contains(it) }
|
||||
linksNotPresentAnymore.forEach {
|
||||
val historyItems = historyRepo.getAllByURL(it)
|
||||
historyItems.filter { h -> h.type == item.downloadItemTemplate.type }.forEach { h ->
|
||||
historyRepo.delete(h, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val res = list
|
||||
.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){
|
||||
|
|
@ -83,7 +104,6 @@ class ObserveSourceWorker(
|
|||
}
|
||||
|
||||
val items = mutableListOf<DownloadItem>()
|
||||
|
||||
res.forEach {
|
||||
val string = Gson().toJson(item.downloadItemTemplate, DownloadItem::class.java)
|
||||
val downloadItem = Gson().fromJson(string, DownloadItem::class.java)
|
||||
|
|
@ -116,9 +136,9 @@ class ObserveSourceWorker(
|
|||
alarmScheduler.schedule()
|
||||
}
|
||||
|
||||
if (items.any { it.playlistTitle.isEmpty() } && items.size > 1){
|
||||
items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
||||
}
|
||||
// if (items.any { it.playlistTitle.isEmpty() } && items.size > 1){
|
||||
// items.forEachIndexed { index, it -> it.playlistTitle = "Various[${index+1}]" }
|
||||
// }
|
||||
|
||||
items.forEach {
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class TerminalDownloadWorker(
|
|||
val noCache = !sharedPreferences.getBoolean("cache_downloads", true) && File(FileUtil.formatPath(commandPath)).canWrite()
|
||||
|
||||
if (!noCache){
|
||||
request.addOption("-P", FileUtil.getCachePath(context) + "/TERMINAL/" + itemId)
|
||||
request.addOption("-P", FileUtil.getCachePath(context) + "TERMINAL/" + itemId)
|
||||
}else if (!request.hasOption("-P")){
|
||||
request.addOption("-P", FileUtil.formatPath(commandPath))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M21.99,12.34C22,12.23 22,12.11 22,12c0,-5.52 -4.48,-10 -10,-10S2,6.48 2,12c0,5.17 3.93,9.43 8.96,9.95c-0.93,-0.73 -1.72,-1.64 -2.32,-2.68C5.9,18 4,15.22 4,12c0,-1.85 0.63,-3.55 1.69,-4.9l5.66,5.66c0.56,-0.4 1.17,-0.73 1.82,-1L7.1,5.69C8.45,4.63 10.15,4 12,4c4.24,0 7.7,3.29 7.98,7.45C20.69,11.67 21.37,11.97 21.99,12.34zM17,13c-3.18,0 -5.9,1.87 -7,4.5c1.1,2.63 3.82,4.5 7,4.5s5.9,-1.87 7,-4.5C22.9,14.87 20.18,13 17,13zM17,20c-1.38,0 -2.5,-1.12 -2.5,-2.5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5C19.5,18.88 18.38,20 17,20zM18.5,17.5c0,0.83 -0.67,1.5 -1.5,1.5s-1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5S18.5,16.67 18.5,17.5z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_home_filled_24.xml
Normal file
5
app/src/main/res/drawable/baseline_home_filled_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="?android:colorAccent" android:pathData="M12,3L4,9v12h5v-7h6v7h5V9z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM12.47,14L9,10.5l1.4,-1.41 2.07,2.08L17.6,6 19,7.41 12.47,14zM4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6z"/>
|
||||
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_history.xml
Normal file
5
app/src/main/res/drawable/ic_history.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
||||
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_home_outlined.xml
Normal file
10
app/src/main/res/drawable/ic_home_outlined.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="?attr/colorAccent"
|
||||
android:pathData="M11,39h7.5L18.5,26.5h11L29.5,39L37,39L37,19.5L24,9.75 11,19.5ZM8,42L8,18L24,6l16,12v24L26.5,42L26.5,29.5h-5L21.5,42ZM24,24.35Z" />
|
||||
</vector>
|
||||
|
|
@ -165,22 +165,21 @@
|
|||
app:layout_constraintVertical_bias="0.51"
|
||||
>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_resume"
|
||||
style="?attr/materialIconButtonFilledStyle"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:contentDescription="@string/resume"
|
||||
android:layout_height="wrap_content"
|
||||
app:backgroundTint="?attr/colorSurface"
|
||||
app:cornerRadius="15dp"
|
||||
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||
app:iconSize="30dp"
|
||||
app:iconTint="?android:textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/output"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
|
||||
app:layout_constraintVertical_bias="0.51" />
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/active_download_resume"-->
|
||||
<!-- style="?attr/materialIconButtonFilledStyle"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:contentDescription="@string/resume"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- app:backgroundTint="?attr/colorSurface"-->
|
||||
<!-- app:cornerRadius="15dp"-->
|
||||
<!-- app:icon="@drawable/exomedia_ic_play_arrow_white"-->
|
||||
<!-- app:iconSize="30dp"-->
|
||||
<!-- app:iconTint="?android:textColorPrimary"-->
|
||||
<!-- app:layout_constraintBottom_toTopOf="@+id/output"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"-->
|
||||
<!-- app:layout_constraintVertical_bias="0.51" />-->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/active_download_delete"
|
||||
|
|
|
|||
|
|
@ -88,17 +88,40 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_sheet_link"
|
||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:textAlignment="textStart"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="15sp"
|
||||
app:icon="@drawable/ic_link" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_sheet_link"
|
||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="15sp"
|
||||
app:icon="@drawable/ic_link"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_incognito"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottomsheet_incognito"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:contentDescription="@string/incognito"
|
||||
app:icon="@drawable/ic_nightly"
|
||||
android:alpha="0.3"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/video_view"
|
||||
android:keepScreenOn="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
|
|||
|
|
@ -158,17 +158,42 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_sheet_link"
|
||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:textAlignment="textStart"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="15sp"
|
||||
app:icon="@drawable/ic_link" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottom_sheet_link"
|
||||
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/app_name"
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="15sp"
|
||||
app:icon="@drawable/ic_link"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_incognito"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/bottomsheet_incognito"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:contentDescription="@string/incognito"
|
||||
app:icon="@drawable/ic_nightly"
|
||||
android:alpha="0.3"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView android:id="@+id/download_card_view"
|
||||
<com.google.android.material.card.MaterialCardView xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/download_card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
|
|
@ -39,6 +40,23 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/incognitoLabel"
|
||||
style="?attr/materialIconButtonStyle"
|
||||
android:layout_width="20dp"
|
||||
android:height="20dp"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:padding="0dp"
|
||||
app:cornerRadius="10dp"
|
||||
app:icon="@drawable/ic_nightly"
|
||||
app:iconTint="?attr/colorAccent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/downloads_image_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/downloads_image_view" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
|
|
|
|||
|
|
@ -166,6 +166,44 @@
|
|||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/formatSourceLinear"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/format_source_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:singleLine="false"
|
||||
android:text="@string/format_source"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/format_source_group"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:checkedButton="@+id/radio_button_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
@ -198,6 +236,8 @@
|
|||
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -2,26 +2,107 @@
|
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/download_recyclerview"
|
||||
<LinearLayout
|
||||
android:id="@+id/active_queued_linear"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:scrollbars="none"
|
||||
tools:listitem="@layout/active_download_card"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
android:paddingBottom="100dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/active_linear"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/active_no_results"
|
||||
layout="@layout/no_results" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/download_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pause_resume" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/queue_linear"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="200dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/in_queue_label"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="5dp"
|
||||
android:textColor="?android:colorPrimary"
|
||||
android:text="@string/in_queue"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/drag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="10dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
app:drawableStartCompat="@drawable/ic_drag_handle"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filesize"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/file_size"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/queued_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:scrollbars="none"
|
||||
android:paddingBottom="100dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/logs_toolbar"
|
||||
android:id="@+id/downloads_toolbar"
|
||||
android:elevation="0dp"
|
||||
app:title="@string/download_queue"
|
||||
app:menu="@menu/download_queue_menu"
|
||||
|
|
|
|||
59
app/src/main/res/layout/nav_options_item.xml
Normal file
59
app/src/main/res/layout/nav_options_item.xml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:focusable="true"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="0dp"
|
||||
tools:gravity="start|center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/home"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:src="@drawable/ic_home_outlined"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/checkbox"
|
||||
style="@style/Widget.Material3.CompoundButton.CheckBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
tools:text="Item" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/drag_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:padding="16dp"
|
||||
android:tintMode="src_in"
|
||||
app:srcCompat="@drawable/ic_drag_handle"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -493,6 +493,13 @@
|
|||
android:checked="false"
|
||||
android:text="@string/get_new_uploads"/>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/sync_with_source"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="false"
|
||||
android:text="@string/sync_with_source"/>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/retry_missing_downloads"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
android:id="@+id/video_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:resize_mode="fixed_height"
|
||||
app:useDefaultControls="true"
|
||||
|
|
|
|||
12
app/src/main/res/layout/simple_options_recycler.xml
Normal file
12
app/src/main/res/layout/simple_options_recycler.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="20dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/options_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -4,10 +4,22 @@
|
|||
<item android:title="@string/home"
|
||||
android:icon="@drawable/ic_home"
|
||||
android:id="@+id/homeFragment"/>
|
||||
|
||||
<item android:title="@string/downloads"
|
||||
android:icon="@drawable/ic_downloads"
|
||||
android:id="@+id/historyFragment"/>
|
||||
|
||||
<item android:title="@string/more"
|
||||
android:icon="@drawable/ic_more"
|
||||
android:id="@+id/moreFragment"/>
|
||||
|
||||
<item android:title="@string/queue"
|
||||
android:icon="@drawable/baseline_downloading_24"
|
||||
android:visible="false"
|
||||
android:id="@+id/downloadQueueMainFragment"/>
|
||||
|
||||
<item android:title="@string/terminal"
|
||||
android:icon="@drawable/ic_terminal"
|
||||
android:visible="false"
|
||||
android:id="@+id/terminalActivity"/>
|
||||
</menu>
|
||||
|
|
@ -22,6 +22,12 @@
|
|||
android:icon="@drawable/baseline_folder_24"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/incognito"
|
||||
android:title="@string/incognito"
|
||||
android:icon="@drawable/ic_nightly"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/more"
|
||||
|
|
|
|||
|
|
@ -28,13 +28,6 @@
|
|||
app:showAsAction="ifRoom"
|
||||
android:title="@string/move_bottom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/download"
|
||||
android:title="@string/download"
|
||||
android:icon="@drawable/baseline_download_24"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/select_all"
|
||||
android:title="@string/select_all"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@
|
|||
<action
|
||||
android:id="@+id/action_homeFragment_to_downloadsAlreadyExistDialog"
|
||||
app:destination="@id/downloadsAlreadyExistDialog" />
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_terminalActivity"
|
||||
app:destination="@id/terminalActivity" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/historyFragment"
|
||||
|
|
|
|||
|
|
@ -384,8 +384,7 @@
|
|||
<string name="minute">Menit</string>
|
||||
<string name="second">Detik</string>
|
||||
<string name="format_importance_note">Sesuaikan kepentingan elemen format. Urutan ini hanya akan digunakan ketika aplikasi mengambil format dalam kartu unduhan dan otomatis memilih format!</string>
|
||||
<string name="format_importance_audio">Urutan Prioritas Format (Audio)</string>
|
||||
<string name="format_importance_video">Urutan Prioritas Format (Video)</string>
|
||||
<string name="format_importance">Urutan Prioritas Format</string>
|
||||
<string name="no_audio">Video Tanpa Audio</string>
|
||||
<string name="download_archive_folder">Folder Arsip Unduhan</string>
|
||||
<string name="location">Lokasi</string>
|
||||
|
|
|
|||
|
|
@ -397,4 +397,5 @@
|
|||
<string name="location">Vendi</string>
|
||||
<string name="enable_alarm_permission">Duhet të aktivizoni lejen SCHEDULE_EXACT_ALARM në cilësimet e aplikacionit.</string>
|
||||
<string name="process_downloads_background">Shkarkimet janë ende duke u ngarkuar. Vazhdoni t\'i përpunoni ato në sfond dhe të filloni shkarkimin?</string>
|
||||
<string name="queue">Rradha</string>
|
||||
</resources>
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="grid_size">3</integer>
|
||||
<bool name="uses_side_nav">true</bool>
|
||||
</resources>
|
||||
|
|
@ -1463,4 +1463,16 @@
|
|||
<item>container</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="label_visibility">
|
||||
<item>@string/always</item>
|
||||
<item>@string/selected</item>
|
||||
<item>@string/never</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="label_visibility_values">
|
||||
<item>always</item>
|
||||
<item>selected</item>
|
||||
<item>never</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
@ -2,4 +2,5 @@
|
|||
<integer name="grid_size">1</integer>
|
||||
<bool name="isTablet">false</bool>
|
||||
<bool name="is_right_to_left">false</bool>
|
||||
<bool name="uses_side_nav">false</bool>
|
||||
</resources>
|
||||
|
|
@ -401,4 +401,11 @@
|
|||
<string name="location">Location</string>
|
||||
<string name="enable_alarm_permission">You need to enable the SCHEDULE_EXACT_ALARM permission in the app settings.</string>
|
||||
<string name="process_downloads_background">"Downloads are still loading. Continue processing them in the background and start downloading? "</string>
|
||||
<string name="navigation_bar">Navigation Bar</string>
|
||||
<string name="label_visibility">Label Visibility</string>
|
||||
<string name="always">Always</string>
|
||||
<string name="sync_with_source">Sync With Source</string>
|
||||
<string name="queue">Queue</string>
|
||||
<string name="reset">Reset</string>
|
||||
<string name="restore_info">Press \'Restore\' to merge the saved data with your current data.\nPress \'Reset\' to erase and only use the saved data in the file.</string>
|
||||
</resources>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
android:icon="@drawable/ic_incognito"
|
||||
android:icon="@drawable/ic_nightly"
|
||||
android:key="incognito"
|
||||
app:summary="@string/incognito_summary"
|
||||
app:title="@string/incognito" />
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/misc">
|
||||
<PreferenceCategory android:title="YouTube">
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
|
|
@ -84,7 +84,24 @@
|
|||
android:summary="@string/api_key_summary"
|
||||
app:title="@string/api_key" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="yt-dlp"
|
||||
android:entries="@array/formats_source"
|
||||
android:entryValues="@array/formats_source_values"
|
||||
android:icon="@drawable/baseline_manage_search_24"
|
||||
app:key="formats_source"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_source" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_dns"
|
||||
app:key="piped_instance"
|
||||
app:defaultValue=""
|
||||
android:summary="@string/piped_instance_summary"
|
||||
app:title="@string/piped_instance" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/misc">
|
||||
<ListPreference
|
||||
android:defaultValue=""
|
||||
android:entries="@array/countries"
|
||||
|
|
@ -95,15 +112,29 @@
|
|||
app:key="locale"
|
||||
app:title="@string/preferred_locale" />
|
||||
|
||||
<ListPreference
|
||||
<Preference
|
||||
android:defaultValue="Home"
|
||||
android:entries="@array/start_destination"
|
||||
android:entryValues="@array/start_destination_values"
|
||||
app:icon="@drawable/baseline_add_to_home_screen_24"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:key="start_destination"
|
||||
app:isPreferenceVisible="false"
|
||||
app:title="@string/preferred_home_screen" />
|
||||
|
||||
<Preference
|
||||
android:defaultValue="Home,History,More"
|
||||
android:icon="@drawable/baseline_home_filled_24"
|
||||
android:key="navigation_bar"
|
||||
android:title="@string/preferred_home_screen" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="always"
|
||||
android:entries="@array/label_visibility"
|
||||
android:entryValues="@array/label_visibility_values"
|
||||
android:icon="@drawable/baseline_disabled_visible_24"
|
||||
app:key="label_visibility"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/label_visibility" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="ytsearch"
|
||||
android:entries="@array/search_engines"
|
||||
|
|
@ -157,4 +188,5 @@
|
|||
app:key="ignore_battery"
|
||||
app:title="@string/ignore_battery_optimization" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
@ -33,15 +33,9 @@
|
|||
android:key="auto_update_ytdlp"
|
||||
app:title="@string/auto_update_ytdlp" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="yt-dlp"
|
||||
android:entries="@array/formats_source"
|
||||
android:entryValues="@array/formats_source_values"
|
||||
android:icon="@drawable/baseline_manage_search_24"
|
||||
app:key="formats_source"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:title="@string/format_source" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/format">
|
||||
<ListPreference
|
||||
android:defaultValue="filesize"
|
||||
android:entries="@array/format_ordering"
|
||||
|
|
@ -67,14 +61,6 @@
|
|||
android:key="update_formats"
|
||||
app:summary="@string/update_formats_summary"
|
||||
app:title="@string/update_formats" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_dns"
|
||||
app:key="piped_instance"
|
||||
app:defaultValue=""
|
||||
android:summary="@string/piped_instance_summary"
|
||||
app:title="@string/piped_instance" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/app">
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ buildscript {
|
|||
retrofitVer = "2.9.0"
|
||||
kodeinVer = "7.16.0"
|
||||
navVer = "2.7.7"
|
||||
media3_version = "1.2.1"
|
||||
media3_version = "1.3.1"
|
||||
agp_version = '8.1.0'
|
||||
agp_version1 = '7.4.2'
|
||||
roomVer = '2.5.2'
|
||||
|
|
|
|||
Loading…
Reference in a new issue