way too many stuff
- long press download button to save it for later and not schedule it. Also works when u try to queue multiple items - change app icon background based on theme - fixed bug when trying to redownload a history item and wanting to use a different type - made the scheduled item auto-update after its queued so the app doesnt have to update when it begins downloading - some fixes with output templates - fixed container and vcodec being saved with translated strings and not constants - added download thumbnail functionality. Click the result card in the middle and use the video player. Also observe running and queued downloads for that item - removed api key - fixed recommendations sometimes not showing in that location - added feature to disable thumbnails on certain screens. U can choose - added feature to convert subs to different formats - added youtube music search provider - made app name have a color depending on the theme - fixed format card not showing a translated string on best and worst strings - added ability to hide the format intent filter from the share menu - fixed app killing active downloads when removing a queued item - fixed filename template not showing uploader on odysee - other small stuff
This commit is contained in:
parent
f303a4f879
commit
41306565ab
61 changed files with 2975 additions and 385 deletions
|
|
@ -10,7 +10,7 @@ def properties = new Properties()
|
||||||
def versionMajor = 1
|
def versionMajor = 1
|
||||||
def versionMinor = 6
|
def versionMinor = 6
|
||||||
def versionPatch = 3
|
def versionPatch = 3
|
||||||
def versionBuild = 4 // bump for dogfood builds, public betas, etc.
|
def versionBuild = 5 // bump for dogfood builds, public betas, etc.
|
||||||
def versionExt = ""
|
def versionExt = ""
|
||||||
|
|
||||||
if (versionBuild > 0){
|
if (versionBuild > 0){
|
||||||
|
|
|
||||||
517
app/schemas/com.deniscerri.ytdlnis.database.DBManager/9.json
Normal file
517
app/schemas/com.deniscerri.ytdlnis.database.DBManager/9.json
Normal file
|
|
@ -0,0 +1,517 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 9,
|
||||||
|
"identityHash": "bcd20f64b54fbc6e470be0d15059a66e",
|
||||||
|
"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, `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": "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)",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, `workID` BLOB)",
|
||||||
|
"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": "workID",
|
||||||
|
"columnName": "workID",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"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)",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, 'bcd20f64b54fbc6e470be0d15059a66e')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.software.leanback"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
@ -31,7 +38,8 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/BaseTheme"
|
android:theme="@style/BaseTheme"
|
||||||
tools:ignore="DataExtractionRules"
|
tools:ignore="DataExtractionRules"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu"
|
||||||
|
android:banner="@mipmap/ic_launcher">
|
||||||
<profileable
|
<profileable
|
||||||
android:shell="true"
|
android:shell="true"
|
||||||
tools:targetApi="29" />
|
tools:targetApi="29" />
|
||||||
|
|
@ -101,14 +109,71 @@
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".terminalShareAlias"
|
||||||
|
android:targetActivity=".ui.more.TerminalActivity"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".Default"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:windowSoftInputMode="adjustPan">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".DarkIcon"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="true"
|
||||||
|
android:icon="@mipmap/ic_launcher_dark"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round_dark"
|
||||||
|
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:windowSoftInputMode="adjustPan">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".LightIcon"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="true"
|
||||||
|
android:icon="@mipmap/ic_launcher_light"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round_light"
|
||||||
|
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||||
|
android:targetActivity=".MainActivity"
|
||||||
|
android:windowSoftInputMode="adjustPan">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.more.WebViewActivity"
|
android:name=".ui.more.WebViewActivity"
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,17 @@ import android.widget.Toast
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.work.Configuration
|
import androidx.work.Configuration
|
||||||
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.NetworkType
|
||||||
|
import androidx.work.PeriodicWorkRequest
|
||||||
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkQuery
|
||||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
|
import com.deniscerri.ytdlnis.work.UpdateYTDLWorker
|
||||||
|
import com.google.android.gms.common.internal.Constants
|
||||||
import com.yausername.aria2c.Aria2c
|
import com.yausername.aria2c.Aria2c
|
||||||
import com.yausername.ffmpeg.FFmpeg
|
import com.yausername.ffmpeg.FFmpeg
|
||||||
import com.yausername.youtubedl_android.YoutubeDL
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
|
@ -18,6 +26,7 @@ import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
class App : Application() {
|
class App : Application() {
|
||||||
|
|
@ -39,6 +48,23 @@ class App : Application() {
|
||||||
putString("version", BuildConfig.VERSION_NAME)
|
putString("version", BuildConfig.VERSION_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//init yt-dlp auto update with work request
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||||
|
.setRequiresDeviceIdle(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val periodicWorkRequest = PeriodicWorkRequestBuilder<UpdateYTDLWorker>(1, TimeUnit.DAYS)
|
||||||
|
.setConstraints(constraints)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(this@App).enqueueUniquePeriodicWork(
|
||||||
|
"ytdlp-Updater",
|
||||||
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
periodicWorkRequest
|
||||||
|
)
|
||||||
|
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@App, e.message, Toast.LENGTH_SHORT).show()
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,11 @@ import androidx.fragment.app.FragmentContainerView
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
|
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.navigation.navOptions
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.deniscerri.ytdlnis.adapter
|
package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
|
@ -9,6 +10,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
|
@ -29,10 +31,12 @@ import java.lang.StringBuilder
|
||||||
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
@ -52,20 +56,25 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
// THUMBNAIL ----------------------------------
|
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
|
||||||
val imageURL = item!!.thumb
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
if (imageURL.isNotEmpty()) {
|
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
|
||||||
} else {
|
// THUMBNAIL ----------------------------------
|
||||||
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
|
||||||
|
val imageURL = item!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
||||||
|
}else{
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
|
||||||
|
|
||||||
// PROGRESS BAR ----------------------------------------------------
|
// PROGRESS BAR ----------------------------------------------------
|
||||||
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
|
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
|
||||||
progressBar.tag = "${item.id}##progress"
|
progressBar.tag = "${item!!.id}##progress"
|
||||||
progressBar.progress = 0
|
progressBar.progress = 0
|
||||||
progressBar.isIndeterminate = true
|
progressBar.isIndeterminate = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
import com.google.android.material.shape.CornerFamily
|
||||||
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import java.lang.StringBuilder
|
||||||
|
|
||||||
|
class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadMinifiedAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
|
private val onItemClickListener: OnItemClickListener
|
||||||
|
private val activity: Activity
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.onItemClickListener = onItemClickListener
|
||||||
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
val cardView: MaterialCardView
|
||||||
|
|
||||||
|
init {
|
||||||
|
cardView = itemView.findViewById(R.id.active_download_card_view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val cardView = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.active_download_card_minified, parent, false)
|
||||||
|
return ViewHolder(cardView, onItemClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val item = getItem(position)
|
||||||
|
val card = holder.cardView
|
||||||
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
val thumbnail = card.findViewById<ImageView>(R.id.image_view)
|
||||||
|
|
||||||
|
// THUMBNAIL ----------------------------------
|
||||||
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
|
||||||
|
val imageURL = item!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
||||||
|
}else{
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// PROGRESS BAR ----------------------------------------------------
|
||||||
|
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
|
||||||
|
progressBar.tag = "${item!!.id}##progress"
|
||||||
|
progressBar.progress = 0
|
||||||
|
progressBar.isIndeterminate = true
|
||||||
|
|
||||||
|
// TITLE ----------------------------------
|
||||||
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
|
var title = item.title
|
||||||
|
if (title.length > 100) {
|
||||||
|
title = title.substring(0, 40) + "..."
|
||||||
|
}
|
||||||
|
itemTitle.text = title.ifEmpty { item.url }
|
||||||
|
|
||||||
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
|
duration.text = item.duration
|
||||||
|
|
||||||
|
|
||||||
|
val formatDetailsChip = card.findViewById<TextView>(R.id.format_note)
|
||||||
|
val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
|
||||||
|
|
||||||
|
val codecText =
|
||||||
|
if (item.format.encoding != "") {
|
||||||
|
item.format.encoding.uppercase()
|
||||||
|
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||||
|
item.format.vcodec.uppercase()
|
||||||
|
} else {
|
||||||
|
item.format.acodec.uppercase()
|
||||||
|
}
|
||||||
|
if (codecText != "" && codecText != "none"){
|
||||||
|
formatDetailsText.append(" \t •\t $codecText")
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileSize = FileUtil.convertFileSize(item.format.filesize)
|
||||||
|
if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize")
|
||||||
|
|
||||||
|
formatDetailsChip.text = formatDetailsText
|
||||||
|
|
||||||
|
// CANCEL BUTTON ----------------------------------
|
||||||
|
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_cancel)
|
||||||
|
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null)
|
||||||
|
|
||||||
|
cancelButton.setOnClickListener {
|
||||||
|
onItemClickListener.onCancelClick(item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
card.setOnClickListener {
|
||||||
|
onItemClickListener.onCardClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface OnItemClickListener {
|
||||||
|
fun onCancelClick(itemID: Long)
|
||||||
|
fun onCardClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
|
||||||
|
val ranged = arrayListOf(oldItem.id, newItem.id)
|
||||||
|
return ranged[0] == ranged[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
|
||||||
|
return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.deniscerri.ytdlnis.adapter
|
package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -9,6 +11,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
|
@ -24,10 +27,12 @@ import java.util.Locale
|
||||||
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
|
private val sharedPreferences : SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
@ -47,18 +52,25 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
// THUMBNAIL ----------------------------------
|
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
|
||||||
val imageURL = item!!.thumb
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
if (imageURL.isNotEmpty()) {
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
|
||||||
} else {
|
// THUMBNAIL ----------------------------------
|
||||||
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||||
|
val imageURL = item!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
||||||
|
}else{
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
duration.text = item.duration
|
duration.text = item!!.duration
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
|
@ -9,6 +11,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
|
@ -25,11 +28,13 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
private val checkedItems: ArrayList<Long>
|
private val checkedItems: ArrayList<Long>
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkedItems = ArrayList()
|
checkedItems = ArrayList()
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
@ -49,18 +54,23 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
|
|
||||||
// THUMBNAIL ----------------------------------
|
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
|
||||||
val imageURL = item!!.thumb
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
if (imageURL.isNotEmpty()) {
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
|
||||||
} else {
|
// THUMBNAIL ----------------------------------
|
||||||
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
|
||||||
|
val imageURL = item!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
}else{
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val duration = card.findViewById<TextView>(R.id.duration)
|
val duration = card.findViewById<TextView>(R.id.duration)
|
||||||
duration.text = item.duration
|
duration.text = item!!.duration
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
|
|
@ -102,6 +112,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
|
||||||
|
|
||||||
when(item.status){
|
when(item.status){
|
||||||
DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh)
|
DownloadRepository.Status.Cancelled.toString() -> actionButton.setIconResource(R.drawable.ic_refresh)
|
||||||
|
DownloadRepository.Status.Saved.toString() -> actionButton.setIconResource(R.drawable.ic_downloads)
|
||||||
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
|
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
|
||||||
else -> {
|
else -> {
|
||||||
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
|
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.ColorMatrix
|
import android.graphics.ColorMatrix
|
||||||
import android.graphics.ColorMatrixColorFilter
|
import android.graphics.ColorMatrixColorFilter
|
||||||
|
|
@ -13,6 +14,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
|
@ -32,11 +34,13 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
||||||
private val checkedItems: ArrayList<Long>
|
private val checkedItems: ArrayList<Long>
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkedItems = ArrayList()
|
checkedItems = ArrayList()
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
@ -56,20 +60,26 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
// THUMBNAIL ----------------------------------
|
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
|
||||||
val imageURL = item!!.thumb
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
if (imageURL.isNotEmpty()) {
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
|
||||||
} else {
|
// THUMBNAIL ----------------------------------
|
||||||
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("downloads")){
|
||||||
|
val imageURL = item!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
||||||
|
}else{
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.downloads_title)
|
val itemTitle = card.findViewById<TextView>(R.id.downloads_title)
|
||||||
var title = item.title
|
var title = item!!.title
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.deniscerri.ytdlnis.adapter
|
package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
|
@ -10,6 +11,8 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
|
@ -26,11 +29,13 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
||||||
private val checkedVideos: ArrayList<String>
|
private val checkedVideos: ArrayList<String>
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkedVideos = ArrayList()
|
checkedVideos = ArrayList()
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
@ -50,22 +55,26 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val video = getItem(position)
|
val video = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
// THUMBNAIL ----------------------------------
|
|
||||||
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
|
val thumbnail = card.findViewById<ImageView>(R.id.result_image_view)
|
||||||
val imageURL = video!!.thumb
|
|
||||||
if (imageURL.isNotEmpty()) {
|
// THUMBNAIL ----------------------------------
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
val imageURL = video!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
||||||
} else {
|
}else{
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
thumbnail.setColorFilter(Color.argb(20, 0, 0, 0))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val videoTitle = card.findViewById<TextView>(R.id.result_title)
|
val videoTitle = card.findViewById<TextView>(R.id.result_title)
|
||||||
var title = video.title
|
var title = video!!.title
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +154,8 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
||||||
card.setOnClickListener {
|
card.setOnClickListener {
|
||||||
if (checkedVideos.size > 0) {
|
if (checkedVideos.size > 0) {
|
||||||
checkCard(card, videoURL)
|
checkCard(card, videoURL)
|
||||||
|
}else{
|
||||||
|
onItemClickListener.onCardDetailsClick(videoURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,6 +176,7 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
|
||||||
fun onButtonClick(videoURL: String, type: DownloadViewModel.Type?)
|
fun onButtonClick(videoURL: String, type: DownloadViewModel.Type?)
|
||||||
fun onLongButtonClick(videoURL: String, type: DownloadViewModel.Type?)
|
fun onLongButtonClick(videoURL: String, type: DownloadViewModel.Type?)
|
||||||
fun onCardClick(videoURL: String, add: Boolean)
|
fun onCardClick(videoURL: String, add: Boolean)
|
||||||
|
fun onCardDetailsClick(videoURL: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkAll(items: List<ResultItem?>?){
|
fun checkAll(items: List<ResultItem?>?){
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.adapter
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
|
@ -12,6 +13,7 @@ import android.widget.CheckBox
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
|
@ -26,11 +28,13 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
private val checkedItems: ArrayList<String>
|
private val checkedItems: ArrayList<String>
|
||||||
private val onItemClickListener: OnItemClickListener
|
private val onItemClickListener: OnItemClickListener
|
||||||
private val activity: Activity
|
private val activity: Activity
|
||||||
|
private val sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
init {
|
init {
|
||||||
checkedItems = ArrayList()
|
checkedItems = ArrayList()
|
||||||
this.onItemClickListener = onItemClickListener
|
this.onItemClickListener = onItemClickListener
|
||||||
this.activity = activity
|
this.activity = activity
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
@ -50,20 +54,25 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
val card = holder.cardView
|
val card = holder.cardView
|
||||||
// THUMBNAIL ----------------------------------
|
|
||||||
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
|
||||||
val imageURL = item!!.thumb
|
|
||||||
val uiHandler = Handler(Looper.getMainLooper())
|
val uiHandler = Handler(Looper.getMainLooper())
|
||||||
if (imageURL.isNotEmpty()) {
|
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
|
||||||
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
|
||||||
} else {
|
// THUMBNAIL ----------------------------------
|
||||||
|
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("home")){
|
||||||
|
val imageURL = item!!.thumb
|
||||||
|
if (imageURL.isNotEmpty()) {
|
||||||
|
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
|
||||||
|
} else {
|
||||||
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
|
}
|
||||||
|
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
|
||||||
|
}else{
|
||||||
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
|
||||||
}
|
}
|
||||||
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
|
|
||||||
|
|
||||||
// TITLE ----------------------------------
|
// TITLE ----------------------------------
|
||||||
val itemTitle = card.findViewById<TextView>(R.id.title)
|
val itemTitle = card.findViewById<TextView>(R.id.title)
|
||||||
var title = item.title
|
var title = item!!.title
|
||||||
if (title.length > 100) {
|
if (title.length > 100) {
|
||||||
title = title.substring(0, 40) + "..."
|
title = title.substring(0, 40) + "..."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ interface DownloadDao {
|
||||||
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
|
||||||
fun getErroredDownloadsList() : List<DownloadItem>
|
fun getErroredDownloadsList() : List<DownloadItem>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE status='Processing' ORDER BY id DESC")
|
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
|
||||||
fun getProcessingDownloads() : Flow<List<DownloadItem>>
|
fun getSavedDownloads() : Flow<List<DownloadItem>>
|
||||||
|
|
||||||
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
@Query("SELECT * FROM downloads WHERE id=:id LIMIT 1")
|
||||||
fun getDownloadById(id: Long) : DownloadItem
|
fun getDownloadById(id: Long) : DownloadItem
|
||||||
|
|
@ -70,6 +70,9 @@ interface DownloadDao {
|
||||||
@Query("DELETE FROM downloads WHERE status='Error'")
|
@Query("DELETE FROM downloads WHERE status='Error'")
|
||||||
suspend fun deleteErrored()
|
suspend fun deleteErrored()
|
||||||
|
|
||||||
|
@Query("DELETE FROM downloads WHERE status='Saved'")
|
||||||
|
suspend fun deleteSaved()
|
||||||
|
|
||||||
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued'")
|
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued'")
|
||||||
suspend fun cancelQueued()
|
suspend fun cancelQueued()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
val queuedDownloads : Flow<List<DownloadItem>> = downloadDao.getQueuedDownloads()
|
val queuedDownloads : Flow<List<DownloadItem>> = downloadDao.getQueuedDownloads()
|
||||||
val cancelledDownloads : Flow<List<DownloadItem>> = downloadDao.getCancelledDownloads()
|
val cancelledDownloads : Flow<List<DownloadItem>> = downloadDao.getCancelledDownloads()
|
||||||
val erroredDownloads : Flow<List<DownloadItem>> = downloadDao.getErroredDownloads()
|
val erroredDownloads : Flow<List<DownloadItem>> = downloadDao.getErroredDownloads()
|
||||||
val processingDownloads : Flow<List<DownloadItem>> = downloadDao.getProcessingDownloads()
|
val savedDownloads : Flow<List<DownloadItem>> = downloadDao.getSavedDownloads()
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
Active, Queued, Error, Processing, Cancelled
|
Active, Queued, Error, Processing, Cancelled, Saved
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun insert(item: DownloadItem) : Long {
|
suspend fun insert(item: DownloadItem) : Long {
|
||||||
|
|
@ -73,6 +73,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
|
||||||
downloadDao.deleteErrored()
|
downloadDao.deleteErrored()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteSaved(){
|
||||||
|
downloadDao.deleteSaved()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun cancelQueued(){
|
suspend fun cancelQueued(){
|
||||||
downloadDao.cancelQueued()
|
downloadDao.cancelQueued()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
|
||||||
suspend fun updateTrending(){
|
suspend fun updateTrending(){
|
||||||
deleteAll()
|
deleteAll()
|
||||||
val infoUtil = InfoUtil(context)
|
val infoUtil = InfoUtil(context)
|
||||||
val items = infoUtil.getTrending(context)
|
val items = infoUtil.getTrending()
|
||||||
itemCount.value = items.size
|
itemCount.value = items.size
|
||||||
for (i in items){
|
for (i in items){
|
||||||
resultDao.insert(i!!)
|
resultDao.insert(i!!)
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,13 @@ import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.workDataOf
|
||||||
import com.deniscerri.ytdlnis.App
|
import com.deniscerri.ytdlnis.App
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.DBManager
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
import com.deniscerri.ytdlnis.database.models.AudioPreferences
|
||||||
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
import com.deniscerri.ytdlnis.database.models.CommandTemplate
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
|
@ -40,13 +43,14 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
|
class DownloadViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val dbManager: DBManager
|
||||||
private val repository : DownloadRepository
|
private val repository : DownloadRepository
|
||||||
private val sharedPreferences: SharedPreferences
|
private val sharedPreferences: SharedPreferences
|
||||||
private val commandTemplateDao: CommandTemplateDao
|
private val commandTemplateDao: CommandTemplateDao
|
||||||
|
|
@ -57,7 +61,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
val activeDownloadsCount : LiveData<Int>
|
val activeDownloadsCount : LiveData<Int>
|
||||||
val cancelledDownloads : LiveData<List<DownloadItem>>
|
val cancelledDownloads : LiveData<List<DownloadItem>>
|
||||||
val erroredDownloads : LiveData<List<DownloadItem>>
|
val erroredDownloads : LiveData<List<DownloadItem>>
|
||||||
val processingDownloads : LiveData<List<DownloadItem>>
|
val savedDownloads : LiveData<List<DownloadItem>>
|
||||||
|
|
||||||
private var bestVideoFormat : Format
|
private var bestVideoFormat : Format
|
||||||
private var bestAudioFormat : Format
|
private var bestAudioFormat : Format
|
||||||
|
|
@ -72,7 +76,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val dao = DBManager.getInstance(application).downloadDao
|
dbManager = DBManager.getInstance(application)
|
||||||
|
val dao = dbManager.downloadDao
|
||||||
repository = DownloadRepository(dao)
|
repository = DownloadRepository(dao)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
|
||||||
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
commandTemplateDao = DBManager.getInstance(application).commandTemplateDao
|
||||||
|
|
@ -82,7 +87,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
queuedDownloads = repository.queuedDownloads.asLiveData()
|
queuedDownloads = repository.queuedDownloads.asLiveData()
|
||||||
activeDownloads = repository.activeDownloads.asLiveData()
|
activeDownloads = repository.activeDownloads.asLiveData()
|
||||||
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
|
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
|
||||||
processingDownloads = repository.processingDownloads.asLiveData()
|
savedDownloads = repository.savedDownloads.asLiveData()
|
||||||
cancelledDownloads = repository.cancelledDownloads.asLiveData()
|
cancelledDownloads = repository.cancelledDownloads.asLiveData()
|
||||||
erroredDownloads = repository.erroredDownloads.asLiveData()
|
erroredDownloads = repository.erroredDownloads.asLiveData()
|
||||||
|
|
||||||
|
|
@ -97,8 +102,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
|
|
||||||
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
|
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
|
||||||
var videoContainer = sharedPreferences.getString("video_format", "Default")
|
val videoContainer = sharedPreferences.getString("video_format", "Default")
|
||||||
if (videoContainer == "Default") videoContainer = App.instance.getString(R.string.defaultValue)
|
|
||||||
|
|
||||||
defaultVideoFormats = mutableListOf()
|
defaultVideoFormats = mutableListOf()
|
||||||
videoFormatValues.forEach {
|
videoFormatValues.forEach {
|
||||||
|
|
@ -116,8 +120,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
|
|
||||||
bestVideoFormat = defaultVideoFormats.last()
|
bestVideoFormat = defaultVideoFormats.last()
|
||||||
|
|
||||||
var audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
val audioContainer = sharedPreferences.getString("audio_format", "mp3")
|
||||||
if (audioContainer == "Default") audioContainer = App.instance.getString(R.string.defaultValue)
|
|
||||||
bestAudioFormat = Format(
|
bestAudioFormat = Format(
|
||||||
"best",
|
"best",
|
||||||
audioContainer!!,
|
audioContainer!!,
|
||||||
|
|
@ -374,8 +377,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
fun getGenericAudioFormats() : MutableList<Format>{
|
fun getGenericAudioFormats() : MutableList<Format>{
|
||||||
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
val audioFormats = resources.getStringArray(R.array.audio_formats)
|
||||||
val formats = mutableListOf<Format>()
|
val formats = mutableListOf<Format>()
|
||||||
var containerPreference = sharedPreferences.getString("audio_format", "Default")
|
val containerPreference = sharedPreferences.getString("audio_format", "")
|
||||||
if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue)
|
|
||||||
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
@ -383,9 +385,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
fun getGenericVideoFormats() : MutableList<Format>{
|
fun getGenericVideoFormats() : MutableList<Format>{
|
||||||
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
val videoFormats = resources.getStringArray(R.array.video_formats_values)
|
||||||
val formats = mutableListOf<Format>()
|
val formats = mutableListOf<Format>()
|
||||||
var containerPreference = sharedPreferences.getString("video_format", "Default")
|
val containerPreference = sharedPreferences.getString("video_format", "")
|
||||||
if (containerPreference == "Default") containerPreference = resources.getString(R.string.defaultValue)
|
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
|
||||||
videoFormats.forEach { formats.add(Format(it, containerPreference!!,resources.getString(R.string.defaultValue),"", "",0, it)) }
|
|
||||||
return formats
|
return formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,6 +416,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
repository.deleteErrored()
|
repository.deleteErrored()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteSaved() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
repository.deleteSaved()
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelQueued() = viewModelScope.launch(Dispatchers.IO) {
|
fun cancelQueued() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
repository.cancelQueued()
|
repository.cancelQueued()
|
||||||
}
|
}
|
||||||
|
|
@ -450,6 +455,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||||
val queuedItems = mutableListOf<DownloadItem>()
|
val queuedItems = mutableListOf<DownloadItem>()
|
||||||
var lastDownloadId = repository.getLastDownloadId()
|
var lastDownloadId = repository.getLastDownloadId()
|
||||||
|
|
||||||
|
var exists = false
|
||||||
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
lastDownloadId++
|
lastDownloadId++
|
||||||
it.status = DownloadRepository.Status.Queued.toString()
|
it.status = DownloadRepository.Status.Queued.toString()
|
||||||
|
|
@ -458,9 +466,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
d.status = DownloadRepository.Status.Queued.toString()
|
d.status = DownloadRepository.Status.Queued.toString()
|
||||||
d.toString() == it.toString()
|
d.toString() == it.toString()
|
||||||
} != null) {
|
} != null) {
|
||||||
Looper.prepare().run {
|
exists = true
|
||||||
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
if (it.id == 0L){
|
if (it.id == 0L){
|
||||||
it.id = lastDownloadId
|
it.id = lastDownloadId
|
||||||
|
|
@ -475,6 +481,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exists){
|
||||||
|
Looper.prepare().run {
|
||||||
|
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
queuedItems.forEach {
|
queuedItems.forEach {
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
var delay = if (it.downloadStartTime != 0L){
|
var delay = if (it.downloadStartTime != 0L){
|
||||||
|
|
@ -498,12 +510,6 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
workRequest
|
workRequest
|
||||||
).enqueue()
|
).enqueue()
|
||||||
|
|
||||||
it.id = withContext(Dispatchers.IO){
|
|
||||||
repository.checkIfReDownloadingErroredOrCancelled(it)
|
|
||||||
}
|
|
||||||
if (it.id != 0L){
|
|
||||||
repository.delete(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
|
||||||
|
|
@ -512,6 +518,35 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
||||||
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.filter { it.downloadStartTime != 0L && (it.title.isEmpty() || it.author.isEmpty() || it.thumb.isEmpty()) }.forEach {
|
||||||
|
try{
|
||||||
|
updateDownloadItem(it, infoUtil, dbManager.downloadDao, dbManager.resultDao)
|
||||||
|
}catch (ignored: Exception){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun updateDownloadItem(
|
||||||
|
downloadItem: DownloadItem,
|
||||||
|
infoUtil: InfoUtil,
|
||||||
|
dao: DownloadDao,
|
||||||
|
resultDao: ResultDao
|
||||||
|
) : Boolean {
|
||||||
|
var wasQuickDownloaded = false
|
||||||
|
if (downloadItem.title.isEmpty() || downloadItem.author.isEmpty() || downloadItem.thumb.isEmpty()){
|
||||||
|
runCatching {
|
||||||
|
val info = infoUtil.getMissingInfo(downloadItem.url)
|
||||||
|
if (downloadItem.title.isEmpty()) downloadItem.title = info?.title.toString()
|
||||||
|
if (downloadItem.author.isEmpty()) downloadItem.author = info?.author.toString()
|
||||||
|
downloadItem.duration = info?.duration.toString()
|
||||||
|
downloadItem.website = info?.website.toString()
|
||||||
|
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
|
||||||
|
runBlocking {
|
||||||
|
wasQuickDownloaded = resultDao.getCountInt() == 0
|
||||||
|
dao.update(downloadItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wasQuickDownloaded
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -5,12 +5,14 @@ import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.Context.CLIPBOARD_SERVICE
|
import android.content.Context.CLIPBOARD_SERVICE
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.content.res.Resources.Theme
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.View.*
|
import android.view.View.*
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
|
@ -37,7 +39,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||||
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog
|
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog
|
||||||
|
import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
import com.facebook.shimmer.ShimmerFrameLayout
|
import com.facebook.shimmer.ShimmerFrameLayout
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
|
|
@ -133,6 +137,8 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
searchHistory = view.findViewById(R.id.search_history_scroll_view)
|
searchHistory = view.findViewById(R.id.search_history_scroll_view)
|
||||||
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
|
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
|
||||||
|
|
||||||
|
materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext())
|
||||||
|
|
||||||
homeAdapter =
|
homeAdapter =
|
||||||
HomeAdapter(
|
HomeAdapter(
|
||||||
this,
|
this,
|
||||||
|
|
@ -567,6 +573,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCardDetailsClick(videoURL: String) {
|
||||||
|
if (parentFragmentManager.findFragmentByTag("resultDetails") == null && resultsList != null && resultsList!!.isNotEmpty()){
|
||||||
|
val bottomSheet = ResultCardDetailsDialog(resultsList!!.first{it!!.url == videoURL}!!)
|
||||||
|
bottomSheet.show(parentFragmentManager, "cutVideoSheet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
val viewIdName: String = try {
|
val viewIdName: String = try {
|
||||||
v.tag.toString()
|
v.tag.toString()
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
import com.deniscerri.ytdlnis.database.models.ChapterItem
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.util.InfoUtil
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
|
@ -104,64 +105,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
|
||||||
behavior.peekHeight = displayMetrics.heightPixels
|
behavior.peekHeight = displayMetrics.heightPixels
|
||||||
}
|
}
|
||||||
|
|
||||||
// val renderersFactory = DefaultRenderersFactory(requireContext().applicationContext)
|
player = VideoPlayerUtil.buildPlayer(requireContext())
|
||||||
// .setEnableDecoderFallback(true)
|
|
||||||
// .setExtensionRendererMode(EXTENSION_RENDERER_MODE_PREFER)
|
|
||||||
// .setMediaCodecSelector { mimeType, requiresSecureDecoder, requiresTunnelingDecoder ->
|
|
||||||
// var decoderInfo: MutableList<MediaCodecInfo> =
|
|
||||||
// MediaCodecSelector.DEFAULT
|
|
||||||
// .getDecoderInfos(
|
|
||||||
// mimeType,
|
|
||||||
// requiresSecureDecoder,
|
|
||||||
// requiresTunnelingDecoder
|
|
||||||
// )
|
|
||||||
// if (MimeTypes.VIDEO_H264 == mimeType) {
|
|
||||||
// // copy the list because MediaCodecSelector.DEFAULT returns an unmodifiable list
|
|
||||||
// decoderInfo = ArrayList(decoderInfo)
|
|
||||||
// decoderInfo.reverse()
|
|
||||||
// }
|
|
||||||
// decoderInfo
|
|
||||||
// }
|
|
||||||
|
|
||||||
val cronetEngine: CronetEngine = CronetEngine.Builder(App.instance)
|
|
||||||
.enableHttp2(true)
|
|
||||||
.enableQuic(true)
|
|
||||||
.enableBrotli(true)
|
|
||||||
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 1024L * 1024L) // 1MiB
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val trackSelector = DefaultTrackSelector(requireContext())
|
|
||||||
val loadControl = DefaultLoadControl.Builder()
|
|
||||||
// cache the last three minutes
|
|
||||||
.setBackBuffer(1000 * 60 * 3, true)
|
|
||||||
.setBufferDurationsMs(
|
|
||||||
1000 * 10, // exo default is 50s
|
|
||||||
50000,
|
|
||||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
|
||||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val cronetDataSourceFactory = CronetDataSource.Factory(
|
|
||||||
cronetEngine,
|
|
||||||
Executors.newCachedThreadPool()
|
|
||||||
)
|
|
||||||
val dataSourceFactory = DefaultDataSource.Factory(requireContext(), cronetDataSourceFactory)
|
|
||||||
|
|
||||||
player = ExoPlayer.Builder(requireContext())
|
|
||||||
.setUsePlatformDiagnostics(false)
|
|
||||||
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
|
||||||
.setTrackSelector(trackSelector)
|
|
||||||
.setLoadControl(loadControl)
|
|
||||||
.setHandleAudioBecomingNoisy(true)
|
|
||||||
.setAudioAttributes(
|
|
||||||
AudioAttributes.Builder()
|
|
||||||
.setUsage(C.USAGE_MEDIA)
|
|
||||||
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
|
||||||
.build()
|
|
||||||
,
|
|
||||||
true)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val frame = view.findViewById<MaterialCardView>(R.id.frame_layout)
|
val frame = view.findViewById<MaterialCardView>(R.id.frame_layout)
|
||||||
val videoView = view.findViewById<PlayerView>(R.id.video_view)
|
val videoView = view.findViewById<PlayerView>(R.id.video_view)
|
||||||
|
|
|
||||||
|
|
@ -142,9 +142,13 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
//if its updating a present downloaditem and its the wrong category
|
//if its updating a present downloaditem and its the wrong category
|
||||||
if (currentDownloadItem!!.type != Type.audio){
|
if (currentDownloadItem!!.type != Type.audio){
|
||||||
downloadItem.type = Type.audio
|
downloadItem.type = Type.audio
|
||||||
downloadItem.format =
|
runCatching {
|
||||||
downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
downloadItem.format =
|
||||||
.maxByOrNull { it.filesize }!!
|
downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
|
||||||
|
.maxByOrNull { it.filesize }!!
|
||||||
|
}.onFailure {
|
||||||
|
downloadItem.format = downloadViewModel.getGenericAudioFormats().last()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats.filter { it.format_note.contains("audio", ignoreCase = true) })
|
||||||
|
|
@ -160,11 +164,11 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
|
||||||
|
|
||||||
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||||
val chosenFormat = downloadItem.format
|
val chosenFormat = downloadItem.format
|
||||||
UiUtil.populateFormatCard(formatCard, chosenFormat, null)
|
UiUtil.populateFormatCard(requireContext(), formatCard, chosenFormat, null)
|
||||||
val listener = object : OnFormatClickListener {
|
val listener = object : OnFormatClickListener {
|
||||||
override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
|
override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
|
||||||
downloadItem.format = item.first().format
|
downloadItem.format = item.first().format
|
||||||
UiUtil.populateFormatCard(formatCard, item.first().format, null)
|
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format, null)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
withContext(Dispatchers.IO){
|
||||||
resultItem.formats.removeAll(formats.toSet())
|
resultItem.formats.removeAll(formats.toSet())
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import com.deniscerri.ytdlnis.database.DBManager
|
||||||
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
|
|
@ -33,6 +34,7 @@ import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
@ -201,6 +203,22 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download.setOnLongClickListener {
|
||||||
|
val dd = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
dd.setTitle(getString(R.string.save_for_later))
|
||||||
|
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
|
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
val item = getDownloadItem()
|
||||||
|
item.status = DownloadRepository.Status.Saved.toString()
|
||||||
|
downloadViewModel.updateDownload(item)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dd.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
val link = view.findViewById<Button>(R.id.bottom_sheet_link)
|
||||||
link.text = resultItem.url
|
link.text = resultItem.url
|
||||||
link.setOnClickListener{
|
link.setOnClickListener{
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
|
||||||
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
import com.deniscerri.ytdlnis.database.models.Format
|
import com.deniscerri.ytdlnis.database.models.Format
|
||||||
import com.deniscerri.ytdlnis.database.models.ResultItem
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
|
|
@ -139,6 +140,23 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download.setOnLongClickListener {
|
||||||
|
val dd = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
dd.setTitle(getString(R.string.save_for_later))
|
||||||
|
dd.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
|
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
|
lifecycleScope.launch(Dispatchers.IO){
|
||||||
|
items.forEach {
|
||||||
|
it.status = DownloadRepository.Status.Saved.toString()
|
||||||
|
downloadViewModel.updateDownload(it)
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dd.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
bottomAppBar = view.findViewById(R.id.bottomAppBar)
|
||||||
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
val preferredDownloadType = bottomAppBar.menu.findItem(R.id.preferred_download_type)
|
||||||
when(items.first().type){
|
when(items.first().type){
|
||||||
|
|
|
||||||
|
|
@ -147,9 +147,13 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
//if its updating a present downloaditem and its the wrong category
|
//if its updating a present downloaditem and its the wrong category
|
||||||
if (currentDownloadItem!!.type != Type.video){
|
if (currentDownloadItem!!.type != Type.video){
|
||||||
downloadItem.type = Type.video
|
downloadItem.type = Type.video
|
||||||
downloadItem.format =
|
runCatching {
|
||||||
downloadItem.allFormats.filter { it.vcodec.isNotEmpty() }
|
downloadItem.format =
|
||||||
.maxByOrNull { it.filesize }!!
|
downloadItem.allFormats.filter { it.vcodec.isNotEmpty() }
|
||||||
|
.maxByOrNull { it.filesize }!!
|
||||||
|
}.onFailure {
|
||||||
|
downloadItem.format = downloadViewModel.getGenericVideoFormats().last()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats)
|
if (formats.isEmpty()) formats.addAll(downloadItem.allFormats)
|
||||||
|
|
@ -166,7 +170,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
val formatCard = view.findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
|
||||||
|
|
||||||
val chosenFormat = downloadItem.format
|
val chosenFormat = downloadItem.format
|
||||||
UiUtil.populateFormatCard(formatCard, chosenFormat, downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
|
UiUtil.populateFormatCard(requireContext(), formatCard, chosenFormat, downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
|
||||||
val listener = object : OnFormatClickListener {
|
val listener = object : OnFormatClickListener {
|
||||||
override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
|
override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
|
||||||
downloadItem.format = item.first().format
|
downloadItem.format = item.first().format
|
||||||
|
|
@ -180,7 +184,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formats = allFormats.first().toMutableList()
|
formats = allFormats.first().toMutableList()
|
||||||
UiUtil.populateFormatCard(formatCard, item.first().format, item.first().audioFormats)
|
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format, item.first().audioFormats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
formatCard.setOnClickListener{
|
formatCard.setOnClickListener{
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
||||||
val format = finalFormats[i]
|
val format = finalFormats[i]
|
||||||
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
|
||||||
formatItem.tag = "${format.format_id}${format.format_note}"
|
formatItem.tag = "${format.format_id}${format.format_note}"
|
||||||
UiUtil.populateFormatCard(formatItem as MaterialCardView, format, null)
|
UiUtil.populateFormatCard(requireContext(), formatItem as MaterialCardView, format, null)
|
||||||
formatItem.setOnClickListener{ clickedformat ->
|
formatItem.setOnClickListener{ clickedformat ->
|
||||||
//if the context is behind a video or playlist, allow the ability to multiselect audio formats
|
//if the context is behind a video or playlist, allow the ability to multiselect audio formats
|
||||||
if (canMultiSelectAudio){
|
if (canMultiSelectAudio){
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,560 @@
|
||||||
|
package com.deniscerri.ytdlnis.ui.downloadcard
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
|
import android.widget.*
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.map
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
import androidx.media3.exoplayer.source.MergingMediaSource
|
||||||
|
import androidx.media3.ui.PlayerView
|
||||||
|
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.WorkManager
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter
|
||||||
|
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.ResultItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
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.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogFragment(), GenericDownloadAdapter.OnItemClickListener, ActiveDownloadMinifiedAdapter.OnItemClickListener {
|
||||||
|
private lateinit var infoUtil: InfoUtil
|
||||||
|
private lateinit var notificationUtil: NotificationUtil
|
||||||
|
private lateinit var player: ExoPlayer
|
||||||
|
private lateinit var videoView: PlayerView
|
||||||
|
private lateinit var downloadViewModel: DownloadViewModel
|
||||||
|
private lateinit var resultViewModel: ResultViewModel
|
||||||
|
|
||||||
|
private lateinit var activeDownloads: ActiveDownloadMinifiedAdapter
|
||||||
|
private lateinit var queuedDownloads: GenericDownloadAdapter
|
||||||
|
|
||||||
|
private lateinit var queuedItems : List<DownloadItem>
|
||||||
|
private lateinit var activeItems : List<DownloadItem>
|
||||||
|
private lateinit var downloadManager: DownloadManager
|
||||||
|
|
||||||
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
infoUtil = InfoUtil(requireActivity().applicationContext)
|
||||||
|
notificationUtil = NotificationUtil(requireActivity().applicationContext)
|
||||||
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
|
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
|
||||||
|
queuedItems = listOf()
|
||||||
|
activeItems = listOf()
|
||||||
|
downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
// Inflate the layout to use as dialog or embedded fragment
|
||||||
|
return inflater.inflate(R.layout.result_card_details, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val dialog = super.onCreateDialog(savedInstanceState)
|
||||||
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi", "SetTextI18n", "UseGetLayoutInflater")
|
||||||
|
override fun setupDialog(dialog: Dialog, style: Int) {
|
||||||
|
super.setupDialog(dialog, style)
|
||||||
|
val view = LayoutInflater.from(context).inflate(R.layout.result_card_details, null)
|
||||||
|
dialog.setContentView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
activeDownloads = ActiveDownloadMinifiedAdapter(this,requireActivity())
|
||||||
|
queuedDownloads = GenericDownloadAdapter(this,requireActivity())
|
||||||
|
|
||||||
|
val bottomSheetLink = view.findViewById<MaterialButton>(R.id.bottom_sheet_link)
|
||||||
|
val downloadThumb = view.findViewById<MaterialButton>(R.id.download_thumb)
|
||||||
|
val title = view.findViewById<TextView>(R.id.title)
|
||||||
|
val bottomInfo = view.findViewById<TextView>(R.id.bottom_info)
|
||||||
|
val downloadMusic = view.findViewById<Button>(R.id.download_music)
|
||||||
|
val downloadVideo = view.findViewById<Button>(R.id.download_video)
|
||||||
|
|
||||||
|
val runningRecycler = view.findViewById<RecyclerView>(R.id.running_recycler)
|
||||||
|
val running = view.findViewById<TextView>(R.id.running)
|
||||||
|
val queuedRecycler = view.findViewById<RecyclerView>(R.id.queued_recycler)
|
||||||
|
val queued = view.findViewById<TextView>(R.id.queued)
|
||||||
|
|
||||||
|
runningRecycler.adapter = activeDownloads
|
||||||
|
runningRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
itemTouchHelper.attachToRecyclerView(queuedRecycler)
|
||||||
|
}
|
||||||
|
|
||||||
|
queuedRecycler.adapter = queuedDownloads
|
||||||
|
queuedRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
|
downloadViewModel.activeDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
running.visibility = View.GONE
|
||||||
|
runningRecycler.visibility = View.GONE
|
||||||
|
}else{
|
||||||
|
running.visibility = View.VISIBLE
|
||||||
|
runningRecycler.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
it.forEach{item ->
|
||||||
|
WorkManager.getInstance(requireContext())
|
||||||
|
.getWorkInfosForUniqueWorkLiveData(item.id.toString())
|
||||||
|
.observe(viewLifecycleOwner){ list ->
|
||||||
|
list.forEach {work ->
|
||||||
|
if (work == null) return@observe
|
||||||
|
val id = work.progress.getLong("id", 0L)
|
||||||
|
if(id == 0L) return@observe
|
||||||
|
|
||||||
|
val progress = work.progress.getInt("progress", 0)
|
||||||
|
|
||||||
|
val progressBar = view.findViewWithTag<LinearProgressIndicator>("$id##progress")
|
||||||
|
|
||||||
|
requireActivity().runOnUiThread {
|
||||||
|
try {
|
||||||
|
progressBar?.setProgressCompat(progress, true)
|
||||||
|
}catch (ignored: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeItems = it
|
||||||
|
activeDownloads.submitList(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
downloadViewModel.queuedDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
queued.visibility = View.GONE
|
||||||
|
queuedRecycler.visibility = View.GONE
|
||||||
|
}else{
|
||||||
|
queued.visibility = View.VISIBLE
|
||||||
|
queuedRecycler.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
queuedItems = it
|
||||||
|
queuedDownloads.submitList(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bottomSheetLink.text = item.url
|
||||||
|
bottomSheetLink.setOnClickListener{
|
||||||
|
UiUtil.openLinkIntent(requireContext(), item.url, null)
|
||||||
|
}
|
||||||
|
bottomSheetLink.setOnLongClickListener{
|
||||||
|
UiUtil.copyLinkToClipBoard(requireContext(), item.url, null)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadThumb.setOnClickListener {
|
||||||
|
downloadManager.enqueue(
|
||||||
|
DownloadManager.Request(Uri.parse(item.thumb))
|
||||||
|
.setAllowedNetworkTypes(
|
||||||
|
DownloadManager.Request.NETWORK_WIFI or
|
||||||
|
DownloadManager.Request.NETWORK_MOBILE
|
||||||
|
)
|
||||||
|
.setAllowedOverRoaming(true)
|
||||||
|
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
.setTitle(requireContext().getString(R.string.app_name))
|
||||||
|
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "YTDLnis"))
|
||||||
|
}
|
||||||
|
|
||||||
|
title.text = item.title
|
||||||
|
bottomInfo.text = item.author
|
||||||
|
|
||||||
|
downloadMusic.setOnClickListener {
|
||||||
|
onButtonClick(DownloadViewModel.Type.audio)
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadVideo.setOnClickListener {
|
||||||
|
onButtonClick(DownloadViewModel.Type.video)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
videoView = view.findViewById<PlayerView>(R.id.video_view)
|
||||||
|
val pause = view.findViewById<MaterialButton>(R.id.pause)
|
||||||
|
val frameLayout = view.findViewById<FrameLayout>(R.id.frame_layout)
|
||||||
|
|
||||||
|
val player = VideoPlayerUtil.buildPlayer(requireContext())
|
||||||
|
videoView.player = player
|
||||||
|
|
||||||
|
videoView.setOnClickListener {
|
||||||
|
if (player.isPlaying){
|
||||||
|
player.pause()
|
||||||
|
pause.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
player.play()
|
||||||
|
pause.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val data : MutableList<String?> = withContext(Dispatchers.IO){
|
||||||
|
if (item.urls.isEmpty()) {
|
||||||
|
infoUtil.getStreamingUrlAndChapters(item.url)
|
||||||
|
}else {
|
||||||
|
item.urls.split("\n").toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.removeFirst()
|
||||||
|
|
||||||
|
if (data.isEmpty()) throw Exception("No Streaming URL found!")
|
||||||
|
if (data.size == 2){
|
||||||
|
val audioSource : MediaSource =
|
||||||
|
DefaultMediaSourceFactory(requireContext())
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.parse(data[0])))
|
||||||
|
val videoSource: MediaSource =
|
||||||
|
DefaultMediaSourceFactory(requireContext())
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.parse(data[1])))
|
||||||
|
player.setMediaSource(MergingMediaSource(videoSource, audioSource))
|
||||||
|
}else{
|
||||||
|
player.addMediaItem(MediaItem.fromUri(Uri.parse(data[0])))
|
||||||
|
}
|
||||||
|
player.addMediaItem(MediaItem.fromUri(Uri.parse(data[0])))
|
||||||
|
|
||||||
|
player.prepare()
|
||||||
|
player.play()
|
||||||
|
}catch (e: Exception){
|
||||||
|
frameLayout.visibility = View.GONE
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onButtonClick(type: DownloadViewModel.Type){
|
||||||
|
this.dismiss()
|
||||||
|
if (sharedPreferences.getBoolean("download_card", true)) {
|
||||||
|
val bottomSheet = DownloadBottomSheetDialog(item, type, null, false)
|
||||||
|
bottomSheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||||
|
} else {
|
||||||
|
lifecycleScope.launch{
|
||||||
|
val downloadItem = withContext(Dispatchers.IO){
|
||||||
|
downloadViewModel.createDownloadItemFromResult(item, type)
|
||||||
|
}
|
||||||
|
downloadViewModel.queueDownloads(listOf(downloadItem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(dialog: DialogInterface) {
|
||||||
|
super.onCancel(dialog)
|
||||||
|
cleanUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
super.onDismiss(dialog)
|
||||||
|
cleanUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun cleanUp(){
|
||||||
|
kotlin.runCatching {
|
||||||
|
videoView.player?.stop()
|
||||||
|
videoView.player?.release()
|
||||||
|
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("resultDetails")!!).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeQueuedItem(id: Long){
|
||||||
|
val item = queuedItems.find { it.id == id } ?: return
|
||||||
|
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()
|
||||||
|
downloadViewModel.updateDownload(item)
|
||||||
|
|
||||||
|
Snackbar.make(requireView().rootView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(getString(R.string.undo)) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
downloadViewModel.deleteDownload(item)
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
|
||||||
|
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
val position = viewHolder.bindingAdapterPosition
|
||||||
|
when (direction) {
|
||||||
|
ItemTouchHelper.LEFT -> {
|
||||||
|
val deletedItem = queuedItems[position]
|
||||||
|
queuedDownloads.notifyItemChanged(position)
|
||||||
|
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 onActionButtonClick(itemID: Long) {
|
||||||
|
removeQueuedItem(itemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCardClick(itemID: Long) {
|
||||||
|
val item = queuedItems.find { it.id == itemID } ?: return
|
||||||
|
|
||||||
|
val bottomSheet = BottomSheetDialog(requireContext())
|
||||||
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||||
|
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||||
|
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||||
|
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||||
|
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||||
|
|
||||||
|
// BUTTON ----------------------------------
|
||||||
|
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||||
|
|
||||||
|
when (item.type) {
|
||||||
|
DownloadViewModel.Type.audio -> {
|
||||||
|
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.video -> {
|
||||||
|
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||||
|
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||||
|
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||||
|
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||||
|
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||||
|
|
||||||
|
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) time!!.visibility = View.GONE
|
||||||
|
else {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
calendar.timeInMillis = item.downloadStartTime
|
||||||
|
time!!.text = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(calendar.time)
|
||||||
|
|
||||||
|
time.setOnClickListener {
|
||||||
|
UiUtil.showDatePicker(parentFragmentManager) {
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + it.time, Toast.LENGTH_LONG).show()
|
||||||
|
downloadViewModel.deleteDownload(item)
|
||||||
|
item.downloadStartTime = it.timeInMillis
|
||||||
|
WorkManager.getInstance(requireContext()).cancelUniqueWork(item.id.toString())
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||||
|
View.GONE
|
||||||
|
else formatNote!!.text = item.format.format_note
|
||||||
|
|
||||||
|
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||||
|
else container!!.visibility = View.GONE
|
||||||
|
|
||||||
|
val codecText =
|
||||||
|
if (item.format.encoding != "") {
|
||||||
|
item.format.encoding.uppercase()
|
||||||
|
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||||
|
item.format.vcodec.uppercase()
|
||||||
|
} else {
|
||||||
|
item.format.acodec.uppercase()
|
||||||
|
}
|
||||||
|
if (codecText == "" || codecText == "none"){
|
||||||
|
codec!!.visibility = View.GONE
|
||||||
|
}else{
|
||||||
|
codec!!.visibility = View.VISIBLE
|
||||||
|
codec.text = codecText
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||||
|
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||||
|
else fileSize!!.text = fileSizeReadable
|
||||||
|
|
||||||
|
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||||
|
val url = item.url
|
||||||
|
link!!.text = url
|
||||||
|
link.tag = itemID
|
||||||
|
link.setOnClickListener{
|
||||||
|
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||||
|
}
|
||||||
|
link.setOnLongClickListener{
|
||||||
|
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||||
|
remove!!.tag = itemID
|
||||||
|
remove.setOnClickListener{
|
||||||
|
bottomSheet.hide()
|
||||||
|
removeQueuedItem(itemID)
|
||||||
|
}
|
||||||
|
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||||
|
openFile!!.visibility = View.GONE
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val downloadNow = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||||
|
if (item.downloadStartTime <= System.currentTimeMillis() / 1000) downloadNow!!.visibility = View.GONE
|
||||||
|
else{
|
||||||
|
downloadNow!!.text = getString(R.string.download_now)
|
||||||
|
downloadNow.setOnClickListener {
|
||||||
|
bottomSheet.dismiss()
|
||||||
|
downloadViewModel.deleteDownload(item)
|
||||||
|
item.downloadStartTime = 0
|
||||||
|
WorkManager.getInstance(requireContext()).cancelUniqueWork(item.id.toString())
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheet.show()
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||||
|
bottomSheet.window!!.setLayout(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancelClick(itemID: Long) {
|
||||||
|
cancelActiveDownload(itemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCardClick() {
|
||||||
|
this.dismiss()
|
||||||
|
findNavController().navigate(
|
||||||
|
R.id.downloadQueueMainFragment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelActiveDownload(itemID: Long){
|
||||||
|
val id = itemID.toInt()
|
||||||
|
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
||||||
|
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString())
|
||||||
|
notificationUtil.cancelDownloadNotification(id)
|
||||||
|
|
||||||
|
activeItems.find { it.id == itemID }?.let {
|
||||||
|
it.status = DownloadRepository.Status.Cancelled.toString()
|
||||||
|
downloadViewModel.updateDownload(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -63,7 +63,7 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
overScrollMode = View.OVER_SCROLL_NEVER
|
overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
}
|
}
|
||||||
|
|
||||||
val fragments = mutableListOf(ActiveDownloadsFragment(), QueuedDownloadsFragment(), CancelledDownloadsFragment(), ErroredDownloadsFragment())
|
val fragments = mutableListOf(ActiveDownloadsFragment(), QueuedDownloadsFragment(), CancelledDownloadsFragment(), ErroredDownloadsFragment(), SavedDownloadsFragment())
|
||||||
|
|
||||||
fragmentAdapter = DownloadListFragmentAdapter(
|
fragmentAdapter = DownloadListFragmentAdapter(
|
||||||
childFragmentManager,
|
childFragmentManager,
|
||||||
|
|
@ -80,6 +80,7 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
1 -> tab.text = getString(R.string.in_queue)
|
1 -> tab.text = getString(R.string.in_queue)
|
||||||
2 -> tab.text = getString(R.string.cancelled)
|
2 -> tab.text = getString(R.string.cancelled)
|
||||||
3 -> tab.text = getString(R.string.errored)
|
3 -> tab.text = getString(R.string.errored)
|
||||||
|
4 -> tab.text = getString(R.string.saved)
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
|
|
@ -113,21 +114,26 @@ class DownloadQueueMainFragment : Fragment(){
|
||||||
try{
|
try{
|
||||||
when(m.itemId){
|
when(m.itemId){
|
||||||
R.id.clear_queue -> {
|
R.id.clear_queue -> {
|
||||||
showDeleteDialog() {
|
showDeleteDialog {
|
||||||
cancelAllDownloads()
|
cancelAllDownloads()
|
||||||
downloadViewModel.cancelQueued()
|
downloadViewModel.cancelQueued()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.clear_cancelled -> {
|
R.id.clear_cancelled -> {
|
||||||
showDeleteDialog() {
|
showDeleteDialog {
|
||||||
downloadViewModel.deleteCancelled()
|
downloadViewModel.deleteCancelled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.clear_errored -> {
|
R.id.clear_errored -> {
|
||||||
showDeleteDialog() {
|
showDeleteDialog {
|
||||||
downloadViewModel.deleteErrored()
|
downloadViewModel.deleteErrored()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
R.id.clear_saved -> {
|
||||||
|
showDeleteDialog {
|
||||||
|
downloadViewModel.deleteSaved()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
private var _binding : FragmentHistoryBinding? = null
|
private var _binding : FragmentHistoryBinding? = null
|
||||||
private var actionMode : ActionMode? = null
|
private var actionMode : ActionMode? = null
|
||||||
|
|
||||||
|
private lateinit var sortChip: Chip
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
|
|
@ -111,6 +113,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
selectionChips = view.findViewById(R.id.history_selection_chips)
|
selectionChips = view.findViewById(R.id.history_selection_chips)
|
||||||
websiteGroup = view.findViewById(R.id.website_chip_group)
|
websiteGroup = view.findViewById(R.id.website_chip_group)
|
||||||
uiHandler = Handler(Looper.getMainLooper())
|
uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
sortChip = view.findViewById(R.id.sortChip)
|
||||||
selectedObjects = ArrayList()
|
selectedObjects = ArrayList()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -155,6 +158,27 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
|
||||||
scrollToTop()
|
scrollToTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
historyViewModel.sortOrder.observe(viewLifecycleOwner){
|
||||||
|
if (it != null){
|
||||||
|
when(it){
|
||||||
|
HistorySort.ASC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_down)
|
||||||
|
HistorySort.DESC -> sortChip.chipIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_up)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
historyViewModel.sortType.observe(viewLifecycleOwner){
|
||||||
|
if(it != null){
|
||||||
|
when(it){
|
||||||
|
HistoryRepository.HistorySortType.AUTHOR -> sortChip.text = getString(R.string.author)
|
||||||
|
HistoryRepository.HistorySortType.DATE -> sortChip.text = getString(R.string.date_added)
|
||||||
|
HistoryRepository.HistorySortType.TITLE -> sortChip.text = getString(R.string.title)
|
||||||
|
HistoryRepository.HistorySortType.FILESIZE -> sortChip.text = getString(R.string.file_size)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
initMenu()
|
initMenu()
|
||||||
initChips()
|
initChips()
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import kotlinx.coroutines.runBlocking
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||||
|
|
@ -102,9 +103,13 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
items = it.toMutableList()
|
items = it.toMutableList()
|
||||||
if (it.isEmpty()) fileSize.visibility = View.GONE
|
if (it.isEmpty()) fileSize.visibility = View.GONE
|
||||||
else{
|
else{
|
||||||
fileSize.visibility = View.VISIBLE
|
val size = FileUtil.convertFileSize(it.sumOf { i -> i.format.filesize })
|
||||||
fileSize.text = "${getString(R.string.file_size)}: ~ ${FileUtil.convertFileSize(it.sumOf { i -> i.format.filesize })}"
|
if (size == "?") fileSize.visibility = View.GONE
|
||||||
}
|
else {
|
||||||
|
fileSize.visibility = View.VISIBLE
|
||||||
|
fileSize.text = "${getString(R.string.file_size)}: ~ $size"
|
||||||
|
}
|
||||||
|
}
|
||||||
queuedDownloads.submitList(it)
|
queuedDownloads.submitList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,17 +267,13 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeItem(itemID: Long){
|
private fun removeItem(id: Long){
|
||||||
val item = items.find { it.id == itemID }
|
val item = items.find { it.id == id } ?: return
|
||||||
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item?.title + "\"!")
|
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
|
||||||
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
|
||||||
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
|
||||||
val id = itemID.toInt()
|
item.status = DownloadRepository.Status.Cancelled.toString()
|
||||||
YoutubeDL.getInstance().destroyProcessById(id.toString())
|
|
||||||
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString())
|
|
||||||
notificationUtil.cancelDownloadNotification(id)
|
|
||||||
item!!.status = DownloadRepository.Status.Cancelled.toString()
|
|
||||||
downloadViewModel.updateDownload(item)
|
downloadViewModel.updateDownload(item)
|
||||||
|
|
||||||
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
Snackbar.make(queuedRecyclerView, getString(R.string.cancelled) + ": " + item.title, Snackbar.LENGTH_LONG)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,385 @@
|
||||||
|
package com.deniscerri.ytdlnis.ui.downloads
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
|
||||||
|
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UiUtil
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
|
||||||
|
class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickListener {
|
||||||
|
private var fragmentView: View? = null
|
||||||
|
private var activity: Activity? = null
|
||||||
|
private lateinit var downloadViewModel : DownloadViewModel
|
||||||
|
private lateinit var savedRecyclerView: RecyclerView
|
||||||
|
private lateinit var savedDownloads: GenericDownloadAdapter
|
||||||
|
private var selectedObjects: ArrayList<DownloadItem>? = null
|
||||||
|
private var actionMode : ActionMode? = null
|
||||||
|
private lateinit var items : MutableList<DownloadItem>
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false)
|
||||||
|
activity = getActivity()
|
||||||
|
selectedObjects = arrayListOf()
|
||||||
|
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
|
items = mutableListOf()
|
||||||
|
return fragmentView
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
savedDownloads =
|
||||||
|
GenericDownloadAdapter(
|
||||||
|
this,
|
||||||
|
requireActivity()
|
||||||
|
)
|
||||||
|
|
||||||
|
savedRecyclerView = view.findViewById(R.id.download_recyclerview)
|
||||||
|
savedRecyclerView.adapter = savedDownloads
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
if (preferences.getBoolean("swipe_gestures", true)){
|
||||||
|
val itemTouchHelper = ItemTouchHelper(simpleCallback)
|
||||||
|
itemTouchHelper.attachToRecyclerView(savedRecyclerView)
|
||||||
|
}
|
||||||
|
savedRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
|
||||||
|
|
||||||
|
|
||||||
|
downloadViewModel.savedDownloads.observe(viewLifecycleOwner) {
|
||||||
|
items = it.toMutableList()
|
||||||
|
savedDownloads.submitList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionButtonClick(itemID: Long) {
|
||||||
|
val item = items.find { it.id == itemID }
|
||||||
|
if (item != null){
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.error_restarting_download), Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCardClick(itemID: Long) {
|
||||||
|
val item = items.find { it.id == itemID } ?: return
|
||||||
|
|
||||||
|
val bottomSheet = BottomSheetDialog(requireContext())
|
||||||
|
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
|
||||||
|
val title = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)
|
||||||
|
title!!.text = item.title.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||||
|
val author = bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)
|
||||||
|
author!!.text = item.author.ifEmpty { "`${requireContext().getString(R.string.defaultValue)}`" }
|
||||||
|
|
||||||
|
// BUTTON ----------------------------------
|
||||||
|
val btn = bottomSheet.findViewById<MaterialButton>(R.id.downloads_download_button_type)
|
||||||
|
|
||||||
|
when (item.type) {
|
||||||
|
DownloadViewModel.Type.audio -> {
|
||||||
|
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)
|
||||||
|
}
|
||||||
|
DownloadViewModel.Type.video -> {
|
||||||
|
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
btn!!.icon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_terminal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val time = bottomSheet.findViewById<Chip>(R.id.time)
|
||||||
|
val formatNote = bottomSheet.findViewById<Chip>(R.id.format_note)
|
||||||
|
val container = bottomSheet.findViewById<Chip>(R.id.container_chip)
|
||||||
|
val codec = bottomSheet.findViewById<Chip>(R.id.codec)
|
||||||
|
val fileSize = bottomSheet.findViewById<Chip>(R.id.file_size)
|
||||||
|
|
||||||
|
time!!.visibility = View.GONE
|
||||||
|
|
||||||
|
if (item.format.format_note == "?" || item.format.format_note == "") formatNote!!.visibility =
|
||||||
|
View.GONE
|
||||||
|
else formatNote!!.text = item.format.format_note
|
||||||
|
|
||||||
|
if (item.format.container != "") container!!.text = item.format.container.uppercase()
|
||||||
|
else container!!.visibility = View.GONE
|
||||||
|
|
||||||
|
val codecText =
|
||||||
|
if (item.format.encoding != "") {
|
||||||
|
item.format.encoding.uppercase()
|
||||||
|
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
|
||||||
|
item.format.vcodec.uppercase()
|
||||||
|
} else {
|
||||||
|
item.format.acodec.uppercase()
|
||||||
|
}
|
||||||
|
if (codecText == "" || codecText == "none"){
|
||||||
|
codec!!.visibility = View.GONE
|
||||||
|
}else{
|
||||||
|
codec!!.visibility = View.VISIBLE
|
||||||
|
codec.text = codecText
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize)
|
||||||
|
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
|
||||||
|
else fileSize!!.text = fileSizeReadable
|
||||||
|
|
||||||
|
val link = bottomSheet.findViewById<Button>(R.id.bottom_sheet_link)
|
||||||
|
val url = item.url
|
||||||
|
link!!.text = url
|
||||||
|
link.tag = itemID
|
||||||
|
link.setOnClickListener{
|
||||||
|
UiUtil.openLinkIntent(requireContext(), item.url, bottomSheet)
|
||||||
|
}
|
||||||
|
link.setOnLongClickListener{
|
||||||
|
UiUtil.copyLinkToClipBoard(requireContext(), item.url, bottomSheet)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
val remove = bottomSheet.findViewById<Button>(R.id.bottomsheet_remove_button)
|
||||||
|
remove!!.tag = itemID
|
||||||
|
remove.setOnClickListener{
|
||||||
|
removeItem(item, bottomSheet)
|
||||||
|
}
|
||||||
|
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
|
||||||
|
openFile!!.visibility = View.GONE
|
||||||
|
|
||||||
|
val download = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
|
||||||
|
download!!.text = getString(R.string.download)
|
||||||
|
download.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_downloads, 0, 0, 0);
|
||||||
|
download.tag = itemID
|
||||||
|
download.setOnClickListener{
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(listOf(item))
|
||||||
|
}
|
||||||
|
bottomSheet.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
download.setOnLongClickListener {
|
||||||
|
bottomSheet.cancel()
|
||||||
|
val sheet = DownloadBottomSheetDialog(downloadViewModel.createResultItemFromDownload(item), item.type, item, false)
|
||||||
|
sheet.show(parentFragmentManager, "downloadSingleSheet")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
openFile.visibility = View.GONE
|
||||||
|
|
||||||
|
bottomSheet.show()
|
||||||
|
val displayMetrics = DisplayMetrics()
|
||||||
|
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||||
|
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
|
||||||
|
bottomSheet.window!!.setLayout(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCardSelect(itemID: Long, isChecked: Boolean) {
|
||||||
|
val item = items.find { it.id == itemID }
|
||||||
|
if (isChecked) {
|
||||||
|
selectedObjects!!.add(item!!)
|
||||||
|
if (actionMode == null){
|
||||||
|
actionMode = (getActivity() as AppCompatActivity?)!!.startSupportActionMode(contextualActionBar)
|
||||||
|
|
||||||
|
}else{
|
||||||
|
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectedObjects!!.remove(item)
|
||||||
|
actionMode?.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
if (selectedObjects!!.isEmpty()){
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun removeItem(item: DownloadItem, bottomSheet: BottomSheetDialog?){
|
||||||
|
bottomSheet?.hide()
|
||||||
|
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 ->
|
||||||
|
downloadViewModel.deleteDownload(item)
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val contextualActionBar = object : ActionMode.Callback {
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
mode!!.menuInflater.inflate(R.menu.saved_downloads_menu_context, menu)
|
||||||
|
mode.title = "${selectedObjects!!.size} ${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.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 ->
|
||||||
|
for (obj in selectedObjects!!){
|
||||||
|
downloadViewModel.deleteDownload(obj)
|
||||||
|
}
|
||||||
|
clearCheckedItems()
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
deleteDialog.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.download -> {
|
||||||
|
runBlocking {
|
||||||
|
downloadViewModel.queueDownloads(selectedObjects!!.toMutableList())
|
||||||
|
actionMode?.finish()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.select_all -> {
|
||||||
|
savedDownloads.checkAll(items)
|
||||||
|
selectedObjects?.clear()
|
||||||
|
items.forEach { selectedObjects?.add(it) }
|
||||||
|
mode?.title = getString(R.string.all_items_selected)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.invert_selected -> {
|
||||||
|
savedDownloads.invertSelected(items)
|
||||||
|
val invertedList = arrayListOf<DownloadItem>()
|
||||||
|
items.forEach {
|
||||||
|
if (!selectedObjects?.contains(it)!!) invertedList.add(it)
|
||||||
|
}
|
||||||
|
selectedObjects?.clear()
|
||||||
|
selectedObjects?.addAll(invertedList)
|
||||||
|
actionMode!!.title = "${selectedObjects!!.size} ${getString(R.string.selected)}"
|
||||||
|
if (invertedList.isEmpty()) actionMode?.finish()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
actionMode = null
|
||||||
|
clearCheckedItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearCheckedItems(){
|
||||||
|
savedDownloads.clearCheckeditems()
|
||||||
|
selectedObjects?.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var simpleCallback: ItemTouchHelper.SimpleCallback =
|
||||||
|
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
|
||||||
|
override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
val position = viewHolder.bindingAdapterPosition
|
||||||
|
when (direction) {
|
||||||
|
ItemTouchHelper.LEFT -> {
|
||||||
|
val deletedItem = items[position]
|
||||||
|
downloadViewModel.deleteDownload(deletedItem)
|
||||||
|
Snackbar.make(savedRecyclerView, getString(R.string.you_are_going_to_delete) + ": " + deletedItem.title, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(getString(R.string.undo)) {
|
||||||
|
downloadViewModel.insert(deletedItem)
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
ItemTouchHelper.RIGHT -> {
|
||||||
|
onActionButtonClick(items[position].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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addSwipeRightActionIcon(R.drawable.ic_downloads)
|
||||||
|
.create()
|
||||||
|
.decorate()
|
||||||
|
super.onChildDraw(
|
||||||
|
c,
|
||||||
|
recyclerView,
|
||||||
|
viewHolder!!,
|
||||||
|
dX,
|
||||||
|
dY,
|
||||||
|
actionState,
|
||||||
|
isCurrentlyActive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.ListView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package com.deniscerri.ytdlnis.ui.more.settings
|
package com.deniscerri.ytdlnis.ui.more.settings
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build.VERSION
|
import android.os.Build.VERSION
|
||||||
import android.os.Build.VERSION_CODES
|
import android.os.Build.VERSION_CODES
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
@ -14,9 +18,10 @@ import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.deniscerri.ytdlnis.MainActivity
|
import com.deniscerri.ytdlnis.MainActivity
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.deniscerri.ytdlnis.util.FileUtil
|
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
|
||||||
import com.deniscerri.ytdlnis.util.ThemeUtil
|
import com.deniscerri.ytdlnis.util.ThemeUtil
|
||||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
|
import com.google.android.gms.common.wrappers.Wrappers.packageManager
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -28,6 +33,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
private var accent: ListPreference? = null
|
private var accent: ListPreference? = null
|
||||||
private var highContrast: SwitchPreferenceCompat? = null
|
private var highContrast: SwitchPreferenceCompat? = null
|
||||||
private var locale: ListPreference? = null
|
private var locale: ListPreference? = null
|
||||||
|
private var hideTerminal: SwitchPreferenceCompat? = null
|
||||||
|
|
||||||
private var updateUtil: UpdateUtil? = null
|
private var updateUtil: UpdateUtil? = null
|
||||||
private var activeDownloadCount = 0
|
private var activeDownloadCount = 0
|
||||||
|
|
@ -48,6 +54,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
accent = findPreference("theme_accent")
|
accent = findPreference("theme_accent")
|
||||||
highContrast = findPreference("high_contrast")
|
highContrast = findPreference("high_contrast")
|
||||||
locale = findPreference("locale")
|
locale = findPreference("locale")
|
||||||
|
hideTerminal = findPreference("hide_terminal")
|
||||||
|
|
||||||
if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){
|
if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){
|
||||||
val values = resources.getStringArray(R.array.language_values)
|
val values = resources.getStringArray(R.array.language_values)
|
||||||
|
|
@ -108,5 +115,21 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hideTerminal!!.onPreferenceChangeListener =
|
||||||
|
Preference.OnPreferenceChangeListener { pref: Preference?, _: Any ->
|
||||||
|
val packageManager = requireContext().packageManager
|
||||||
|
val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdlnis.terminalShareAlias")
|
||||||
|
|
||||||
|
if ((pref as SwitchPreferenceCompat).isChecked){
|
||||||
|
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP)
|
||||||
|
}else{
|
||||||
|
packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
|
PackageManager.DONT_KILL_APP)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +64,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
private lateinit var cookieViewModel: CookieViewModel
|
private lateinit var cookieViewModel: CookieViewModel
|
||||||
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
private lateinit var commandTemplateViewModel: CommandTemplateViewModel
|
||||||
|
|
||||||
private var arch: Preference? = null
|
|
||||||
private var version: Preference? = null
|
private var version: Preference? = null
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
|
@ -201,9 +200,8 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
version = findPreference("version")
|
version = findPreference("version")
|
||||||
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
|
val nativeLibraryDir = context?.applicationInfo?.nativeLibraryDir
|
||||||
val installedABI = Build.SUPPORTED_ABIS.first { nativeLibraryDir!!.contains(it) }
|
|
||||||
|
|
||||||
version!!.summary = "${BuildConfig.VERSION_NAME} [${installedABI}] ${BuildConfig.BUILD_TYPE}"
|
version!!.summary = "${BuildConfig.VERSION_NAME} [${nativeLibraryDir?.split("/lib/")?.get(1)}] ${BuildConfig.BUILD_TYPE}"
|
||||||
|
|
||||||
version!!.onPreferenceClickListener =
|
version!!.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,10 @@ import java.util.regex.Pattern
|
||||||
class InfoUtil(private val context: Context) {
|
class InfoUtil(private val context: Context) {
|
||||||
private var items: ArrayList<ResultItem?>
|
private var items: ArrayList<ResultItem?>
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private var key: String? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
key = sharedPreferences.getString("api_key", "")
|
|
||||||
countryCODE = sharedPreferences.getString("locale", "")!!
|
countryCODE = sharedPreferences.getString("locale", "")!!
|
||||||
if (countryCODE.isEmpty()) countryCODE = "US"
|
if (countryCODE.isEmpty()) countryCODE = "US"
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -51,70 +49,44 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
fun search(query: String): ArrayList<ResultItem?> {
|
fun search(query: String): ArrayList<ResultItem?> {
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
return when(sharedPreferences.getString("search_engine", "ytsearch")){
|
||||||
return if (searchEngine == "ytsearch"){
|
"ytsearch" -> try{
|
||||||
if (key!!.isNotEmpty()) searchFromKey(query) else {
|
searchFromPiped(query)
|
||||||
try{
|
}catch (e: Exception){
|
||||||
searchFromPiped(query)
|
getFromYTDL(query)
|
||||||
}catch (e: Exception){
|
|
||||||
getFromYTDL(query)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}else getFromYTDL(query)
|
|
||||||
|
"ytsearchmusic" -> try{
|
||||||
|
searchFromPipedMusic(query)
|
||||||
|
}catch (e: Exception){
|
||||||
|
getFromYTDL(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> getFromYTDL(query)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun searchFromKey(query: String): ArrayList<ResultItem?> {
|
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
||||||
//short data
|
val data = genericRequest("$pipedURL/search?q=$query&filter=videos")
|
||||||
val res =
|
val dataArray = data.getJSONArray("items")
|
||||||
genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q=$query&maxResults=25®ionCode=$countryCODE&key=$key")
|
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||||
if (!res.has("items")) return getFromYTDL(query)
|
|
||||||
val dataArray = res.getJSONArray("items")
|
|
||||||
|
|
||||||
//extra data
|
|
||||||
var url2 = "https://www.googleapis.com/youtube/v3/videos?id="
|
|
||||||
//getting all ids, for the extra data request
|
|
||||||
for (i in 0 until dataArray.length()) {
|
for (i in 0 until dataArray.length()) {
|
||||||
val element = dataArray.getJSONObject(i)
|
val element = dataArray.getJSONObject(i)
|
||||||
val snippet = element.getJSONObject("snippet")
|
if (element.getInt("duration") == -1) continue
|
||||||
if (element.getJSONObject("id").getString("kind") == "youtube#video") {
|
element.put("uploader", element.getString("uploaderName"))
|
||||||
val videoID = element.getJSONObject("id").getString("videoId")
|
val v = createVideoFromPipedJSON(element, element.getString("url").removePrefix("/watch?v="))
|
||||||
url2 = "$url2$videoID,"
|
if (v == null || v.thumb.isEmpty()) {
|
||||||
snippet.put("videoID", videoID)
|
continue
|
||||||
}
|
|
||||||
}
|
|
||||||
url2 = url2.substring(
|
|
||||||
0,
|
|
||||||
url2.length - 1
|
|
||||||
) + "&part=contentDetails®ionCode=" + countryCODE + "&key=" + key
|
|
||||||
val extra = genericRequest(url2)
|
|
||||||
var j = 0
|
|
||||||
for (i in 0 until dataArray.length()) {
|
|
||||||
val element = dataArray.getJSONObject(i)
|
|
||||||
val snippet = element.getJSONObject("snippet")
|
|
||||||
if (element.getJSONObject("id").getString("kind") == "youtube#video") {
|
|
||||||
var duration =
|
|
||||||
extra.getJSONArray("items").getJSONObject(j++).getJSONObject("contentDetails")
|
|
||||||
.getString("duration")
|
|
||||||
duration = formatDuration(duration)
|
|
||||||
if (duration == "0:00") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
snippet.put("duration", duration)
|
|
||||||
fixThumbnail(snippet)
|
|
||||||
val v = createVideofromJSON(snippet)
|
|
||||||
if (v == null || v.thumb.isEmpty()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
items.add(createVideofromJSON(snippet))
|
|
||||||
}
|
}
|
||||||
|
items.add(v)
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
fun searchFromPipedMusic(query: String): ArrayList<ResultItem?> {
|
||||||
val data = genericRequest("$pipedURL/search?q=$query?filter=videos")
|
val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs")
|
||||||
val dataArray = data.getJSONArray("items")
|
val dataArray = data.getJSONArray("items")
|
||||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||||
for (i in 0 until dataArray.length()) {
|
for (i in 0 until dataArray.length()) {
|
||||||
|
|
@ -134,77 +106,24 @@ class InfoUtil(private val context: Context) {
|
||||||
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
||||||
try{
|
try{
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
if (key!!.isEmpty()) {
|
// -------------- PIPED API FUNCTION -------------------
|
||||||
// -------------- PIPED API FUNCTION -------------------
|
var url = ""
|
||||||
var url = ""
|
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
||||||
url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id"
|
else """$pipedURL/nextpage/playlists/$id?nextpage=${nextPageToken.replace("&prettyPrint", "%26prettyPrint")}"""
|
||||||
else """$pipedURL/nextpage/playlists/$id?nextpage=${nextPageToken.replace("&prettyPrint", "%26prettyPrint")}"""
|
|
||||||
|
|
||||||
val res = genericRequest(url)
|
|
||||||
if (!res.has("relatedStreams")) throw Exception()
|
|
||||||
|
|
||||||
val dataArray = res.getJSONArray("relatedStreams")
|
|
||||||
var nextpage = res.getString("nextpage")
|
|
||||||
for (i in 0 until dataArray.length()){
|
|
||||||
val obj = dataArray.getJSONObject(i)
|
|
||||||
val itm = createVideoFromPipedJSON(obj, obj.getString("url").removePrefix("/watch?v="))
|
|
||||||
itm?.playlistTitle = "YTDLnis"
|
|
||||||
items.add(itm)
|
|
||||||
}
|
|
||||||
if (nextpage == "null") nextpage = ""
|
|
||||||
return PlaylistTuple(nextpage, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------- YOUTUBE API FUNCTION --------------------------------
|
|
||||||
val url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken=$nextPageToken&maxResults=50®ionCode=$countryCODE&playlistId=$id&key=$key"
|
|
||||||
//short data
|
|
||||||
val res = genericRequest(url)
|
val res = genericRequest(url)
|
||||||
if (!res.has("items")) return PlaylistTuple(
|
if (!res.has("relatedStreams")) throw Exception()
|
||||||
"",
|
|
||||||
getFromYTDL("https://www.youtube.com/playlist?list=$id")
|
|
||||||
)
|
|
||||||
val dataArray = res.getJSONArray("items")
|
|
||||||
|
|
||||||
//extra data
|
val dataArray = res.getJSONArray("relatedStreams")
|
||||||
var url2 = "https://www.googleapis.com/youtube/v3/videos?id="
|
var nextpage = res.getString("nextpage")
|
||||||
//getting all ids, for the extra data request
|
for (i in 0 until dataArray.length()){
|
||||||
for (i in 0 until dataArray.length()) {
|
val obj = dataArray.getJSONObject(i)
|
||||||
val element = dataArray.getJSONObject(i)
|
val itm = createVideoFromPipedJSON(obj, obj.getString("url").removePrefix("/watch?v="))
|
||||||
val snippet = element.getJSONObject("snippet")
|
itm?.playlistTitle = "YTDLnis"
|
||||||
val videoID = snippet.getJSONObject("resourceId").getString("videoId")
|
items.add(itm)
|
||||||
url2 = "$url2$videoID,"
|
|
||||||
snippet.put("videoID", videoID)
|
|
||||||
}
|
}
|
||||||
url2 = url2.substring(
|
if (nextpage == "null") nextpage = ""
|
||||||
0,
|
return PlaylistTuple(nextpage, items)
|
||||||
url2.length - 1
|
|
||||||
) + "&part=contentDetails®ionCode=" + countryCODE + "&key=" + key
|
|
||||||
val extra = genericRequest(url2)
|
|
||||||
val extraArray = extra.getJSONArray("items")
|
|
||||||
var j = 0
|
|
||||||
var i = 0
|
|
||||||
while (i < extraArray.length()) {
|
|
||||||
val element = dataArray.getJSONObject(i)
|
|
||||||
val snippet = element.getJSONObject("snippet")
|
|
||||||
var duration =
|
|
||||||
extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails")
|
|
||||||
.getString("duration")
|
|
||||||
duration = formatDuration(duration)
|
|
||||||
snippet.put("duration", duration)
|
|
||||||
fixThumbnail(snippet)
|
|
||||||
val v = createVideofromJSON(snippet)
|
|
||||||
if (v == null || v.thumb.isEmpty()) {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
v.playlistTitle = "YTDLnis"
|
|
||||||
items.add(v)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
val next = res.optString("nextPageToken")
|
|
||||||
return PlaylistTuple(next, items)
|
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
return PlaylistTuple(
|
return PlaylistTuple(
|
||||||
"",
|
"",
|
||||||
|
|
@ -215,29 +134,14 @@ class InfoUtil(private val context: Context) {
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getVideo(url: String): List<ResultItem?> {
|
fun getVideo(url: String): List<ResultItem?> {
|
||||||
try {
|
return try {
|
||||||
val id = getIDFromYoutubeURL(url)
|
val id = getIDFromYoutubeURL(url)
|
||||||
if (key!!.isEmpty()) {
|
val res = genericRequest("$pipedURL/streams/$id")
|
||||||
val res = genericRequest("$pipedURL/streams/$id")
|
if (res.length() == 0) getFromYTDL(url) else listOf(createVideoFromPipedJSON(
|
||||||
return if (res.length() == 0) getFromYTDL(url) else listOf(createVideoFromPipedJSON(
|
res, id
|
||||||
res, id
|
))
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
//short data
|
|
||||||
var res =
|
|
||||||
genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id=$id&key=$key")
|
|
||||||
if (!res.has("items")) return getFromYTDL(url)
|
|
||||||
var duration = res.getJSONArray("items").getJSONObject(0).getJSONObject("contentDetails")
|
|
||||||
.getString("duration")
|
|
||||||
duration = formatDuration(duration)
|
|
||||||
res = res.getJSONArray("items").getJSONObject(0).getJSONObject("snippet")
|
|
||||||
res.put("videoID", id)
|
|
||||||
res.put("duration", duration)
|
|
||||||
fixThumbnail(res)
|
|
||||||
return listOf(createVideofromJSON(res))
|
|
||||||
}catch (e: Exception){
|
}catch (e: Exception){
|
||||||
return getFromYTDL(url)
|
getFromYTDL(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,6 +207,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
formats.add(formatObj)
|
formats.add(formatObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj.has("videoStreams")){
|
if (obj.has("videoStreams")){
|
||||||
|
|
@ -318,6 +223,7 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
formats.add(formatObj)
|
formats.add(formatObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
formats.sortBy { it.filesize }
|
formats.sortBy { it.filesize }
|
||||||
formats.groupBy { it.format_id }.forEach {
|
formats.groupBy { it.format_id }.forEach {
|
||||||
|
|
@ -442,42 +348,9 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
@Throws(JSONException::class)
|
||||||
fun getTrending(context: Context): ArrayList<ResultItem?> {
|
fun getTrending(): ArrayList<ResultItem?> {
|
||||||
|
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
return if (key!!.isEmpty()) {
|
return getTrendingFromPiped()
|
||||||
getTrendingFromPiped()
|
|
||||||
} else getTrendingFromKey(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
|
||||||
fun getTrendingFromKey(context: Context): ArrayList<ResultItem?> {
|
|
||||||
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10®ionCode=$countryCODE&maxResults=25&key=$key"
|
|
||||||
//short data
|
|
||||||
val res = genericRequest(url)
|
|
||||||
//extra data from the same videos
|
|
||||||
val contentDetails =
|
|
||||||
genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10®ionCode=$countryCODE&maxResults=25&key=$key")
|
|
||||||
if (!contentDetails.has("items")) return ArrayList()
|
|
||||||
val dataArray = res.getJSONArray("items")
|
|
||||||
val extraDataArray = contentDetails.getJSONArray("items")
|
|
||||||
for (i in 0 until dataArray.length()) {
|
|
||||||
val element = dataArray.getJSONObject(i)
|
|
||||||
val snippet = element.getJSONObject("snippet")
|
|
||||||
var duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails")
|
|
||||||
.getString("duration")
|
|
||||||
duration = formatDuration(duration)
|
|
||||||
snippet.put("videoID", element.getString("id"))
|
|
||||||
snippet.put("duration", duration)
|
|
||||||
fixThumbnail(snippet)
|
|
||||||
val v = createVideofromJSON(snippet)
|
|
||||||
if (v == null || v.thumb.isEmpty()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
v.playlistTitle = context.getString(R.string.trendingPlaylist)
|
|
||||||
items.add(v)
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTrendingFromPiped(): ArrayList<ResultItem?> {
|
private fun getTrendingFromPiped(): ArrayList<ResultItem?> {
|
||||||
|
|
@ -521,13 +394,17 @@ class InfoUtil(private val context: Context) {
|
||||||
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
val p = Pattern.compile("^(https?)://(www.)?youtu(.be)?")
|
||||||
val m = p.matcher(url)
|
val m = p.matcher(url)
|
||||||
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
val formatSource = sharedPreferences.getString("formats_source", "yt-dlp")
|
||||||
return if(m.find() && formatSource == "piped"){
|
return try {
|
||||||
val id = getIDFromYoutubeURL(url)
|
if(m.find() && formatSource == "piped"){
|
||||||
val res = genericRequest(pipedURL + "/streams/" + id)
|
val id = getIDFromYoutubeURL(url)
|
||||||
if (res.length() == 0) getFromYTDL(url)[0]!!
|
val res = genericRequest("$pipedURL/streams/$id")
|
||||||
val item = createVideoFromPipedJSON(res, id)
|
if (res.length() == 0) getFromYTDL(url)[0]!!
|
||||||
item!!.formats
|
val item = createVideoFromPipedJSON(res, id)
|
||||||
}else{
|
item!!.formats
|
||||||
|
}else{
|
||||||
|
getFormatsFromYTDL(url)
|
||||||
|
}
|
||||||
|
}catch (e: Exception){
|
||||||
getFormatsFromYTDL(url)
|
getFormatsFromYTDL(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -683,13 +560,24 @@ class InfoUtil(private val context: Context) {
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
|
||||||
try {
|
try {
|
||||||
val request = YoutubeDLRequest(query)
|
val request : YoutubeDLRequest
|
||||||
|
if (query.contains("http")){
|
||||||
|
request = YoutubeDLRequest(query)
|
||||||
|
}else{
|
||||||
|
request = YoutubeDLRequest(emptyList())
|
||||||
|
if (searchEngine == "ytsearchmusic"){
|
||||||
|
request.addOption("--default-search", "https://music.youtube.com/search?q=")
|
||||||
|
request.addOption("ytsearch25:\"${query}\"")
|
||||||
|
}else{
|
||||||
|
request.addOption("${searchEngine}25:\"${query}\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
request.addOption("--flat-playlist")
|
request.addOption("--flat-playlist")
|
||||||
request.addOption("-j")
|
request.addOption("-j")
|
||||||
request.addOption("--skip-download")
|
request.addOption("--skip-download")
|
||||||
request.addOption("-R", "1")
|
request.addOption("-R", "1")
|
||||||
request.addOption("--socket-timeout", "5")
|
request.addOption("--socket-timeout", "5")
|
||||||
if (!query.contains("http")) request.addOption("--default-search", "${searchEngine}25")
|
|
||||||
|
|
||||||
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
val cookiesFile = File(context.cacheDir, "cookies.txt")
|
||||||
if (cookiesFile.exists()){
|
if (cookiesFile.exists()){
|
||||||
|
|
@ -1047,7 +935,8 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
|
||||||
}
|
}
|
||||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||||
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader)s - %(title)s"
|
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader|${downloadItem.author})s - %(title)s"
|
||||||
|
downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s")
|
||||||
|
|
||||||
if (downloadItem.downloadSections.isNotBlank()){
|
if (downloadItem.downloadSections.isNotBlank()){
|
||||||
downloadItem.downloadSections.split(";").forEach {
|
downloadItem.downloadSections.split(";").forEach {
|
||||||
|
|
@ -1142,11 +1031,12 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request.addOption("-P", tempFileDir.absolutePath)
|
||||||
|
|
||||||
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||||
request.addOption("--split-chapters")
|
request.addOption("--split-chapters")
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
|
||||||
}else{
|
}else{
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1194,9 +1084,11 @@ class InfoUtil(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.writeSubs){
|
if (downloadItem.videoPreferences.writeSubs){
|
||||||
|
val subFormat = sharedPreferences.getString("sub_format", "srt")
|
||||||
|
|
||||||
request.addOption("--write-subs")
|
request.addOption("--write-subs")
|
||||||
request.addOption("--write-auto-subs")
|
request.addOption("--write-auto-subs")
|
||||||
request.addOption("--sub-format", "str/ass/best")
|
request.addOption("--sub-format", "${subFormat}/best")
|
||||||
request.addOption("--convert-subtitles", "srt")
|
request.addOption("--convert-subtitles", "srt")
|
||||||
if (!downloadItem.videoPreferences.embedSubs) {
|
if (!downloadItem.videoPreferences.embedSubs) {
|
||||||
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages)
|
||||||
|
|
@ -1207,11 +1099,12 @@ class InfoUtil(private val context: Context) {
|
||||||
request.addOption("--ppa", "ffmpeg:-an")
|
request.addOption("--ppa", "ffmpeg:-an")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request.addOption("-P", tempFileDir.absolutePath)
|
||||||
|
|
||||||
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
|
||||||
request.addOption("--split-chapters")
|
request.addOption("--split-chapters")
|
||||||
request.addOption("-P", tempFileDir.absolutePath)
|
|
||||||
}else{
|
}else{
|
||||||
request.addOption("-o", tempFileDir.absolutePath + "/${downloadItem.customFileNameTemplate}.%(ext)s")
|
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,39 @@
|
||||||
package com.deniscerri.ytdlnis.util
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.util.TypedValue
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.deniscerri.ytdlnis.R
|
import com.deniscerri.ytdlnis.R
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
|
||||||
|
|
||||||
object ThemeUtil {
|
object ThemeUtil {
|
||||||
|
|
||||||
|
sealed class AppIcon(
|
||||||
|
@DrawableRes val iconResource: Int,
|
||||||
|
val activityAlias: String
|
||||||
|
) {
|
||||||
|
object Default : AppIcon(R.mipmap.ic_launcher, "Default")
|
||||||
|
object Light : AppIcon(R.mipmap.ic_launcher_light, "LightIcon")
|
||||||
|
object Dark : AppIcon(R.mipmap.ic_launcher_dark, "DarkIcon")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val availableIcons = listOf(
|
||||||
|
AppIcon.Default,
|
||||||
|
AppIcon.Light,
|
||||||
|
AppIcon.Dark
|
||||||
|
)
|
||||||
|
|
||||||
fun updateTheme(activity: AppCompatActivity) {
|
fun updateTheme(activity: AppCompatActivity) {
|
||||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
|
||||||
|
|
@ -31,12 +56,79 @@ object ThemeUtil {
|
||||||
activity.theme.applyStyle(R.style.Pure, true)
|
activity.theme.applyStyle(R.style.Pure, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//disable old icons
|
||||||
|
for (appIcon in availableIcons) {
|
||||||
|
val activityClass = "com.deniscerri.ytdlnis." + appIcon.activityAlias
|
||||||
|
|
||||||
|
// remove old icons
|
||||||
|
activity.packageManager.setComponentEnabledSetting(
|
||||||
|
ComponentName(activity.packageName, activityClass),
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
|
PackageManager.DONT_KILL_APP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
when (sharedPreferences.getString("ytdlnis_theme", "System")!!) {
|
when (sharedPreferences.getString("ytdlnis_theme", "System")!!) {
|
||||||
"System" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
"System" -> {
|
||||||
"Light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
//set dynamic icon
|
||||||
"Dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.Default")
|
||||||
else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP)
|
||||||
|
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
|
}
|
||||||
|
"Light" -> {
|
||||||
|
//set light icon
|
||||||
|
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.LightIcon")
|
||||||
|
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP)
|
||||||
|
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||||
|
}
|
||||||
|
"Dark" -> {
|
||||||
|
//set dark icon
|
||||||
|
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.DarkIcon")
|
||||||
|
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP)
|
||||||
|
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
//set dynamic icon
|
||||||
|
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.Default")
|
||||||
|
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
PackageManager.DONT_KILL_APP)
|
||||||
|
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getThemeColor(context: Context, colorCode: Int): Int {
|
||||||
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val accent = sharedPreferences.getString("theme_accent", "blue")
|
||||||
|
return if (accent == "Default" || accent == "blue"){
|
||||||
|
"c22727".toInt(16)
|
||||||
|
}else{
|
||||||
|
val value = TypedValue()
|
||||||
|
context.theme.resolveAttribute(colorCode, value, true)
|
||||||
|
value.data
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the styled app name
|
||||||
|
*/
|
||||||
|
fun getStyledAppName(context: Context): Spanned {
|
||||||
|
val colorPrimary = getThemeColor(context, androidx.appcompat.R.attr.colorPrimaryDark)
|
||||||
|
val hexColor = "#%06X".format(0xFFFFFF and colorPrimary)
|
||||||
|
return "<span style='color:$hexColor';>YTDL</span>nis"
|
||||||
|
.parseAsHtml(HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,12 +54,20 @@ import java.util.Calendar
|
||||||
|
|
||||||
object UiUtil {
|
object UiUtil {
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun populateFormatCard(formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<Format>?){
|
fun populateFormatCard(context: Context, formatCard : MaterialCardView, chosenFormat: Format, audioFormats: List<Format>?){
|
||||||
formatCard.findViewById<TextView>(R.id.container).text = chosenFormat.container.uppercase()
|
var formatNote = chosenFormat.format_note
|
||||||
|
if (formatNote.isEmpty()) formatNote = context.getString(R.string.defaultValue)
|
||||||
|
else if (formatNote == "best") formatNote = context.getString(R.string.best_quality)
|
||||||
|
else if (formatNote == "worst") formatNote = context.getString(R.string.worst_quality)
|
||||||
|
|
||||||
|
var container = chosenFormat.container
|
||||||
|
if (container == "Default") container = context.getString(R.string.defaultValue)
|
||||||
|
|
||||||
|
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
|
||||||
if (audioFormats.isNullOrEmpty()){
|
if (audioFormats.isNullOrEmpty()){
|
||||||
formatCard.findViewById<TextView>(R.id.format_note).text = chosenFormat.format_note.uppercase()
|
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
|
||||||
}else{
|
}else{
|
||||||
val title = "${chosenFormat.format_note.uppercase()} + [${audioFormats.joinToString("/") { it.format_note }}]"
|
val title = "${formatNote.uppercase()} + [${audioFormats.joinToString("/") { it.format_note }}]"
|
||||||
formatCard.findViewById<TextView>(R.id.format_note).text = title
|
formatCard.findViewById<TextView>(R.id.format_note).text = title
|
||||||
}
|
}
|
||||||
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.deniscerri.ytdlnis.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.media3.common.AudioAttributes
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.datasource.DefaultDataSource
|
||||||
|
import androidx.media3.datasource.cronet.CronetDataSource
|
||||||
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||||
|
import com.deniscerri.ytdlnis.App
|
||||||
|
import org.chromium.net.CronetEngine
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
object VideoPlayerUtil {
|
||||||
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
|
fun buildPlayer(context: Context) : ExoPlayer {
|
||||||
|
val player: ExoPlayer
|
||||||
|
|
||||||
|
val cronetEngine: CronetEngine = CronetEngine.Builder(App.instance)
|
||||||
|
.enableHttp2(true)
|
||||||
|
.enableQuic(true)
|
||||||
|
.enableBrotli(true)
|
||||||
|
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 1024L * 1024L) // 1MiB
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val trackSelector = DefaultTrackSelector(context)
|
||||||
|
val loadControl = DefaultLoadControl.Builder()
|
||||||
|
// cache the last three minutes
|
||||||
|
.setBackBuffer(1000 * 60 * 3, true)
|
||||||
|
.setBufferDurationsMs(
|
||||||
|
1000 * 10, // exo default is 50s
|
||||||
|
50000,
|
||||||
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||||
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val cronetDataSourceFactory = CronetDataSource.Factory(
|
||||||
|
cronetEngine,
|
||||||
|
Executors.newCachedThreadPool()
|
||||||
|
)
|
||||||
|
val dataSourceFactory = DefaultDataSource.Factory(context, cronetDataSourceFactory)
|
||||||
|
|
||||||
|
player = ExoPlayer.Builder(context)
|
||||||
|
.setUsePlatformDiagnostics(false)
|
||||||
|
.setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory))
|
||||||
|
.setTrackSelector(trackSelector)
|
||||||
|
.setLoadControl(loadControl)
|
||||||
|
.setHandleAudioBecomingNoisy(true)
|
||||||
|
.setAudioAttributes(
|
||||||
|
AudioAttributes.Builder()
|
||||||
|
.setUsage(C.USAGE_MEDIA)
|
||||||
|
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
||||||
|
.build()
|
||||||
|
,
|
||||||
|
true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return player
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.deniscerri.ytdlnis.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.workDataOf
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.database.Converters
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePlaylistFormatsWorker(
|
||||||
|
private val context: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : CoroutineWorker(context, workerParams) {
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val dbManager = DBManager.getInstance(context)
|
||||||
|
val resultDao = dbManager.resultDao
|
||||||
|
val infoUtil = InfoUtil(context)
|
||||||
|
val converters = Converters()
|
||||||
|
val urls = inputData.getStringArray("urls")!!.toMutableList()
|
||||||
|
|
||||||
|
infoUtil.getFormatsMultiple(urls){
|
||||||
|
val formatStrings = mutableListOf<String>()
|
||||||
|
it.forEach { i -> formatStrings.add(converters.formatToString(i)) }
|
||||||
|
setProgressAsync(workDataOf("formats" to formatStrings.toTypedArray()))
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val result = resultDao.getResultByURL(urls.removeFirst())
|
||||||
|
val formats = infoUtil.getFormats(result.url)
|
||||||
|
result.formats = ArrayList(formats)
|
||||||
|
resultDao.update(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.deniscerri.ytdlnis.work
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.navigation.NavDeepLinkBuilder
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.workDataOf
|
||||||
|
import com.deniscerri.ytdlnis.R
|
||||||
|
import com.deniscerri.ytdlnis.database.DBManager
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.DownloadDao
|
||||||
|
import com.deniscerri.ytdlnis.database.dao.ResultDao
|
||||||
|
import com.deniscerri.ytdlnis.database.models.DownloadItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.HistoryItem
|
||||||
|
import com.deniscerri.ytdlnis.database.models.LogItem
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
||||||
|
import com.deniscerri.ytdlnis.database.repository.LogRepository
|
||||||
|
import com.deniscerri.ytdlnis.util.FileUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.InfoUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||||
|
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||||
|
import com.yausername.youtubedl_android.YoutubeDL
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateYTDLWorker(
|
||||||
|
private val context: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : CoroutineWorker(context, workerParams) {
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
UpdateUtil(context).updateYoutubeDL()
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
14
app/src/main/res/anim/fade_in.xml
Normal file
14
app/src/main/res/anim/fade_in.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:fillAfter="true" >
|
||||||
|
|
||||||
|
<scale
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXScale="0.8"
|
||||||
|
android:fromYScale="0.8"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:toXScale="1"
|
||||||
|
android:toYScale="1" >
|
||||||
|
</scale>
|
||||||
|
|
||||||
|
</set>
|
||||||
5
app/src/main/res/drawable/baseline_format_quote_24.xml
Normal file
5
app/src/main/res/drawable/baseline_format_quote_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M6,17h3l2,-4L11,7L5,7v6h3zM14,17h3l2,-4L19,7h-6v6h3z"/>
|
||||||
|
</vector>
|
||||||
6
app/src/main/res/drawable/baseline_hide_image_24.xml
Normal file
6
app/src/main/res/drawable/baseline_hide_image_24.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M21,5c0,-1.1 -0.9,-2 -2,-2H5.83L21,18.17V5z"/>
|
||||||
|
<path android:fillColor="?android:colorAccent" android:pathData="M2.81,2.81L1.39,4.22L3,5.83V19c0,1.1 0.9,2 2,2h13.17l1.61,1.61l1.41,-1.41L2.81,2.81zM6,17l3,-4l2.25,3l0.82,-1.1l2.1,2.1H6z"/>
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_launcher_background_dark.xml
Normal file
10
app/src/main/res/drawable/ic_launcher_background_dark.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M 0 0 H 192 V 192 H 0 V 0 Z" />
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/ic_launcher_background_light.xml
Normal file
10
app/src/main/res/drawable/ic_launcher_background_light.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M 0 0 H 192 V 192 H 0 V 0 Z" />
|
||||||
|
</vector>
|
||||||
167
app/src/main/res/layout/active_download_card_minified.xml
Normal file
167
app/src/main/res/layout/active_download_card_minified.xml
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/active_download_card_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checkable="true"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:checkedIcon="@null"
|
||||||
|
app:strokeColor="?attr/colorPrimary"
|
||||||
|
app:cardPreventCornerOverlap="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||||
|
app:strokeWidth="0dp"
|
||||||
|
app:cardElevation="10dp"
|
||||||
|
app:cardMaxElevation="12dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:alpha="0.3"
|
||||||
|
android:scaleY="200"
|
||||||
|
app:trackColor="#000" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/image_view"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:background="?attr/colorSurfaceVariant"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="H,16:9"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
|
||||||
|
app:layout_constraintHorizontal_weight="0.3"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/duration"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:clickable="false"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLength="17"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/image_view"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/image_view" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/download_item_data"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/active_download_cancel"
|
||||||
|
app:layout_constraintHorizontal_weight="0.7"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/image_view"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:scrollbars="none"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/format_note"
|
||||||
|
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@drawable/rounded_corner"
|
||||||
|
android:backgroundTint="?attr/colorSecondary"
|
||||||
|
android:clickable="false"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLength="17"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:minWidth="30dp"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/active_download_cancel"
|
||||||
|
style="?attr/materialIconButtonFilledStyle"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:backgroundTint="?attr/colorSurface"
|
||||||
|
app:cornerRadius="15dp"
|
||||||
|
app:icon="@drawable/ic_cancel"
|
||||||
|
app:iconSize="30dp"
|
||||||
|
app:iconTint="?android:textColorPrimary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="download_item_data,active_download_cancel" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="200dp"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
android:textSize="15sp"
|
android:textSize="15sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/action_button"
|
app:layout_constraintEnd_toStartOf="@+id/download_type"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:barrierDirection="bottom"
|
app:barrierDirection="bottom"
|
||||||
app:constraint_referenced_ids="title,action_button" />
|
app:constraint_referenced_ids="title,download_type" />
|
||||||
|
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
||||||
225
app/src/main/res/layout/result_card_details.xml
Normal file
225
app/src/main/res/layout/result_card_details.xml
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/frame_layout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:cardCornerRadius="0dp"
|
||||||
|
app:layout_constraintDimensionRatio="H,16:9"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.media3.ui.PlayerView
|
||||||
|
android:id="@+id/video_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:resize_mode="fixed_height"
|
||||||
|
app:useDefaultControls="true"
|
||||||
|
app:use_controller="true" />
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/download_thumb"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:icon="@drawable/ic_image"
|
||||||
|
app:iconSize="30dp"
|
||||||
|
app:iconTint="?android:colorAccent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/frame_layout"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pause"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:clickable="false"
|
||||||
|
app:iconSize="50dp"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
|
app:iconTint="?android:colorAccent"
|
||||||
|
app:icon="@drawable/exomedia_ic_play_arrow_white"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:translationZ="10dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/frame_layout"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:scrollbars="none"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/frame_layout"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/list_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginVertical="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:singleLine="false"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textSize="17sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/download_button_layout"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_info"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="false"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/download_button_layout"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/download_button_layout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/download_music"
|
||||||
|
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||||
|
android:layout_width="55dp"
|
||||||
|
android:layout_height="55dp"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
app:icon="@drawable/ic_music" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/download_video"
|
||||||
|
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
|
||||||
|
android:layout_width="55dp"
|
||||||
|
android:layout_height="55dp"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
app:icon="@drawable/ic_video" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/title_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="bottom_info,download_button_layout" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/title_barrier">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/running"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
android:text="@string/running"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/running_recycler"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/running" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/queued"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
android:text="@string/download_queue"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/running_recycler" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/queued_recycler"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/queued" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/bottom_sheet_link"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton.Icon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textAlignment="textStart"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:icon="@drawable/ic_link"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/queued_recycler" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -21,4 +21,10 @@
|
||||||
android:icon="@drawable/ic_delete_all"
|
android:icon="@drawable/ic_delete_all"
|
||||||
android:title="@string/clear_errored"
|
android:title="@string/clear_errored"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/clear_saved"
|
||||||
|
android:icon="@drawable/ic_delete_all"
|
||||||
|
android:title="@string/clear_saved"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
29
app/src/main/res/menu/saved_downloads_menu_context.xml
Normal file
29
app/src/main/res/menu/saved_downloads_menu_context.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<menu 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"
|
||||||
|
tools:context=".MainActivity" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/delete_results"
|
||||||
|
android:title="@string/remove_results"
|
||||||
|
android:icon="@drawable/baseline_delete_24"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<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"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/invert_selected"
|
||||||
|
android:title="@string/invert_selected"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
|
||||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_dark.xml
Normal file
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_dark.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background_dark"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_light.xml
Normal file
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_light.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background_light"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background_dark"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background_light"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
|
@ -28,18 +28,23 @@
|
||||||
android:label="MainSettingsFragment" >
|
android:label="MainSettingsFragment" >
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
|
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
|
||||||
app:destination="@id/appearanceSettingsFragment" />
|
app:destination="@id/appearanceSettingsFragment"
|
||||||
|
app:enterAnim="@anim/fade_in" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
|
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
|
||||||
app:destination="@id/folderSettingsFragment" />
|
app:destination="@id/folderSettingsFragment"
|
||||||
|
app:enterAnim="@anim/fade_in" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
|
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
|
||||||
app:destination="@id/downloadSettingsFragment" />
|
app:destination="@id/downloadSettingsFragment"
|
||||||
|
app:enterAnim="@anim/fade_in" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
|
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
|
||||||
app:destination="@id/processingSettingsFragment" />
|
app:destination="@id/processingSettingsFragment"
|
||||||
|
app:enterAnim="@anim/fade_in" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
|
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
|
||||||
app:destination="@id/updateSettingsFragment" />
|
app:destination="@id/updateSettingsFragment"
|
||||||
|
app:enterAnim="@anim/fade_in" />
|
||||||
</fragment>
|
</fragment>
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
@ -271,7 +271,7 @@
|
||||||
<string name="move_temporary_files_summary">Transfero skedarët e përkohshëm në dosjen e shkarkimeve</string>
|
<string name="move_temporary_files_summary">Transfero skedarët e përkohshëm në dosjen e shkarkimeve</string>
|
||||||
<string name="format_filtering_hint_2">Të gjithë artikujt duhet të jenë audio ose video për të përdorur këtë opsion</string>
|
<string name="format_filtering_hint_2">Të gjithë artikujt duhet të jenë audio ose video për të përdorur këtë opsion</string>
|
||||||
<string name="custom_audio_quality">Përdor cilësinë e personalizuar të audios</string>
|
<string name="custom_audio_quality">Përdor cilësinë e personalizuar të audios</string>
|
||||||
<string name="piped_instance">Shembull me tubacione</string>
|
<string name="piped_instance">Instanca e PIPED</string>
|
||||||
<string name="piped_instance_summary">Shkruani një Server API PIPED, aplikacioni mund të përdorë për kërkimet dhe formatet në youtube</string>
|
<string name="piped_instance_summary">Shkruani një Server API PIPED, aplikacioni mund të përdorë për kërkimet dhe formatet në youtube</string>
|
||||||
<string name="retries">Riprovimet</string>
|
<string name="retries">Riprovimet</string>
|
||||||
<string name="fragment_retries">Riprovimet e fragmentit</string>
|
<string name="fragment_retries">Riprovimet e fragmentit</string>
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@
|
||||||
|
|
||||||
<array name="search_engines">
|
<array name="search_engines">
|
||||||
<item>YouTube</item>
|
<item>YouTube</item>
|
||||||
|
<item>YouTube Music</item>
|
||||||
<item>Nico video</item>
|
<item>Nico video</item>
|
||||||
<item>PRX Series</item>
|
<item>PRX Series</item>
|
||||||
<item>PRX Stories</item>
|
<item>PRX Stories</item>
|
||||||
|
|
@ -170,10 +171,14 @@
|
||||||
<item>Google Video</item>
|
<item>Google Video</item>
|
||||||
<item>Bilibili</item>
|
<item>Bilibili</item>
|
||||||
<item>Netverse</item>
|
<item>Netverse</item>
|
||||||
|
<item>All Clips of a trovo.live channel</item>
|
||||||
|
<item>All VODs of a trovo.live channel</item>
|
||||||
|
<item>web.archive.org saved youtube videos</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="search_engines_values">
|
<array name="search_engines_values">
|
||||||
<item>ytsearch</item>
|
<item>ytsearch</item>
|
||||||
|
<item>ytsearchmusic</item>
|
||||||
<item>nicosearch</item>
|
<item>nicosearch</item>
|
||||||
<item>PRX Series</item>
|
<item>PRX Series</item>
|
||||||
<item>PRX Stories</item>
|
<item>PRX Stories</item>
|
||||||
|
|
@ -183,6 +188,9 @@
|
||||||
<item>gvsearch</item>
|
<item>gvsearch</item>
|
||||||
<item>bilisearch</item>
|
<item>bilisearch</item>
|
||||||
<item>netsearch</item>
|
<item>netsearch</item>
|
||||||
|
<item>trovoclip</item>
|
||||||
|
<item>trovovod</item>
|
||||||
|
<item>ytarchive</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<array name="start_destination">
|
<array name="start_destination">
|
||||||
|
|
@ -740,4 +748,23 @@
|
||||||
<item>searchHistory</item>
|
<item>searchHistory</item>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
|
<string-array name="sub_formats">
|
||||||
|
<item>srt</item>
|
||||||
|
<item>ass</item>
|
||||||
|
<item>lrc</item>
|
||||||
|
<item>vtt</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="hide_thumbnail">
|
||||||
|
<item>@string/home</item>
|
||||||
|
<item>@string/downloads</item>
|
||||||
|
<item>@string/download_queue</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="hide_thumbnail_values">
|
||||||
|
<item>home</item>
|
||||||
|
<item>downloads</item>
|
||||||
|
<item>queue</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -208,7 +208,7 @@
|
||||||
<string name="subtitle_languages">Subtitle languages</string>
|
<string name="subtitle_languages">Subtitle languages</string>
|
||||||
<string name="format_source">Formats Source</string>
|
<string name="format_source">Formats Source</string>
|
||||||
<string name="video_recommendations">Video Recommendations</string>
|
<string name="video_recommendations">Video Recommendations</string>
|
||||||
<string name="video_recommendations_summary">Get recommended YouTube videos on the home screen from Invidious</string>
|
<string name="video_recommendations_summary">Get recommended YouTube videos on the home screen from PIPED API</string>
|
||||||
<string name="preferred_search_engine">Preferred Search Engine</string>
|
<string name="preferred_search_engine">Preferred Search Engine</string>
|
||||||
<string name="preferred_search_engine_summary">The search engine to use for in-app searches</string>
|
<string name="preferred_search_engine_summary">The search engine to use for in-app searches</string>
|
||||||
<string name="format_filtering_hint">All items must be of the same type to use this option</string>
|
<string name="format_filtering_hint">All items must be of the same type to use this option</string>
|
||||||
|
|
@ -283,8 +283,14 @@
|
||||||
<string name="fragment_retries">Fragment Retries</string>
|
<string name="fragment_retries">Fragment Retries</string>
|
||||||
<string name="format_order">Format Order</string>
|
<string name="format_order">Format Order</string>
|
||||||
<string name="force_keyframes">Force Keyframes At Cuts</string>
|
<string name="force_keyframes">Force Keyframes At Cuts</string>
|
||||||
<string name="force_keyframes_summary">Accurate Video Cuts. Slower</string>
|
<string name="force_keyframes_summary">Slower process, but more accurate cuts</string>
|
||||||
<string name="use_extra_commands">Add Extra Commands</string>
|
<string name="use_extra_commands">Add Extra Commands</string>
|
||||||
<string name="use_extra_commands_summary">For Audio / Video downloads. Add extra commands along with the GUI configuration</string>
|
<string name="use_extra_commands_summary">For Audio / Video downloads. Add extra commands along with the GUI configuration</string>
|
||||||
<string name="current">Current Command</string>
|
<string name="current">Current Command</string>
|
||||||
|
<string name="subtitle_format">Subtitle Format\n</string>
|
||||||
|
<string name="hide_thumbnails">Hide Thumbnails</string>
|
||||||
|
<string name="hide_terminal">Hide Terminal from share menu</string>
|
||||||
|
<string name="saved">Saved</string>
|
||||||
|
<string name="clear_saved">Clear Saved</string>
|
||||||
|
<string name="save_for_later">Save Download for Later?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -55,4 +55,8 @@
|
||||||
<item name="maxLines">2</item>
|
<item name="maxLines">2</item>
|
||||||
<item name="animationMode">slide</item>
|
<item name="animationMode">slide</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="FullScreenDialogTheme" parent="MaterialAlertDialog.Material3">
|
||||||
|
<item name="android:windowIsFloating">false</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,15 @@
|
||||||
app:summary="@string/download_over_metered_networks_summary"
|
app:summary="@string/download_over_metered_networks_summary"
|
||||||
app:title="@string/download_over_metered_networks" />
|
app:title="@string/download_over_metered_networks" />
|
||||||
|
|
||||||
|
<MultiSelectListPreference
|
||||||
|
app:icon="@drawable/baseline_hide_image_24"
|
||||||
|
app:dialogTitle="@string/hide_thumbnails"
|
||||||
|
app:entries="@array/hide_thumbnail"
|
||||||
|
app:entryValues="@array/hide_thumbnail_values"
|
||||||
|
app:key="hide_thumbnails"
|
||||||
|
app:title="@string/hide_thumbnails" />
|
||||||
|
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:icon="@drawable/baseline_network_locked_24"
|
android:icon="@drawable/baseline_network_locked_24"
|
||||||
app:key="proxy"
|
app:key="proxy"
|
||||||
|
|
@ -52,13 +61,6 @@
|
||||||
app:summary="@string/preferred_download_type_summary"
|
app:summary="@string/preferred_download_type_summary"
|
||||||
app:title="@string/preferred_download_type" />
|
app:title="@string/preferred_download_type" />
|
||||||
|
|
||||||
<EditTextPreference
|
|
||||||
android:icon="@drawable/ic_key"
|
|
||||||
app:key="api_key"
|
|
||||||
app:defaultValue=""
|
|
||||||
app:summary="@string/api_key_summary"
|
|
||||||
app:title="@string/api_key" />
|
|
||||||
|
|
||||||
<SeekBarPreference
|
<SeekBarPreference
|
||||||
app:dependency="aria2"
|
app:dependency="aria2"
|
||||||
android:defaultValue="3"
|
android:defaultValue="3"
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,16 @@
|
||||||
app:summary="@string/pure_theme_summary"
|
app:summary="@string/pure_theme_summary"
|
||||||
app:title="@string/high_contrast" />
|
app:title="@string/high_contrast" />
|
||||||
|
|
||||||
|
|
||||||
|
<MultiSelectListPreference
|
||||||
|
app:icon="@drawable/baseline_hide_image_24"
|
||||||
|
app:dialogTitle="@string/hide_thumbnails"
|
||||||
|
app:entries="@array/hide_thumbnail"
|
||||||
|
app:entryValues="@array/hide_thumbnail_values"
|
||||||
|
app:key="hide_thumbnails"
|
||||||
|
app:title="@string/hide_thumbnails" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/misc">
|
<PreferenceCategory android:title="@string/misc">
|
||||||
|
|
@ -93,5 +103,12 @@
|
||||||
android:key="swipe_gestures"
|
android:key="swipe_gestures"
|
||||||
app:summary="@string/swipe_gestures_summary"
|
app:summary="@string/swipe_gestures_summary"
|
||||||
app:title="@string/swipe_gestures" />
|
app:title="@string/swipe_gestures" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
|
app:defaultValue="false"
|
||||||
|
android:icon="@drawable/baseline_share_24"
|
||||||
|
android:key="hide_terminal"
|
||||||
|
app:title="@string/hide_terminal" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -68,6 +68,15 @@
|
||||||
app:summary="@string/force_keyframes_summary"
|
app:summary="@string/force_keyframes_summary"
|
||||||
app:title="@string/force_keyframes" />
|
app:title="@string/force_keyframes" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="srt"
|
||||||
|
android:entries="@array/sub_formats"
|
||||||
|
android:entryValues="@array/sub_formats"
|
||||||
|
android:icon="@drawable/baseline_format_quote_24"
|
||||||
|
app:key="sub_format"
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
|
app:title="@string/subtitle_format" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:widgetLayout="@layout/preferece_material_switch"
|
android:widgetLayout="@layout/preferece_material_switch"
|
||||||
app:defaultValue="true"
|
app:defaultValue="true"
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="piped_instance"
|
app:key="piped_instance"
|
||||||
app:defaultValue="https://pipedapi.syncpundit.io"
|
app:defaultValue="https://api.piped.projectsegfau.lt"
|
||||||
android:summary="@string/piped_instance_summary"
|
android:summary="@string/piped_instance_summary"
|
||||||
app:title="@string/piped_instance" />
|
app:title="@string/piped_instance" />
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue