Merge branch 'main' into weblate-ytdlnis-strings

This commit is contained in:
zaednasr 2024-06-09 21:10:58 +02:00 committed by GitHub
commit 1b9beccfdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 1501 additions and 414 deletions

View file

@ -11,8 +11,8 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 7 def versionMinor = 7
def versionPatch = 6 def versionPatch = 6
def versionBuild = 1 // bump for dogfood builds, public betas, etc. def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def isBeta = true def isBeta = false
def versionExt = isBeta ? ".${versionBuild}-beta" : "" def versionExt = isBeta ? ".${versionBuild}-beta" : ""
android { android {

View file

@ -0,0 +1,725 @@
{
"formatVersion": 1,
"database": {
"version": 17,
"identityHash": "ae22d3024fcae377c9bd9b8ba6f1f299",
"entities": [
{
"tableName": "results",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER, `creationTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formats",
"columnName": "formats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "urls",
"columnName": "urls",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "chapters",
"columnName": "chapters",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "playlistURL",
"columnName": "playlistURL",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
},
{
"fieldPath": "playlistIndex",
"columnName": "playlistIndex",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "creationTime",
"columnName": "creationTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadId` INTEGER NOT NULL DEFAULT 0, `command` TEXT NOT NULL DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "time",
"columnName": "time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadId",
"columnName": "downloadId",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `container` TEXT NOT NULL DEFAULT 'Default', `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `extraCommands` TEXT NOT NULL DEFAULT '', `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0, `logID` INTEGER, `playlistURL` TEXT DEFAULT '', `playlistIndex` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "container",
"columnName": "container",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Default'"
},
{
"fieldPath": "downloadSections",
"columnName": "downloadSections",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "allFormats",
"columnName": "allFormats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadPath",
"columnName": "downloadPath",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadSize",
"columnName": "downloadSize",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "audioPreferences",
"columnName": "audioPreferences",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "videoPreferences",
"columnName": "videoPreferences",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "extraCommands",
"columnName": "extraCommands",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "customFileNameTemplate",
"columnName": "customFileNameTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "SaveThumb",
"columnName": "SaveThumb",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'Queued'"
},
{
"fieldPath": "downloadStartTime",
"columnName": "downloadStartTime",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "logID",
"columnName": "logID",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "playlistURL",
"columnName": "playlistURL",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
},
{
"fieldPath": "playlistIndex",
"columnName": "playlistIndex",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "commandTemplates",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `useAsExtraCommand` INTEGER NOT NULL DEFAULT 0, `useAsExtraCommandAudio` INTEGER NOT NULL DEFAULT 1, `useAsExtraCommandVideo` INTEGER NOT NULL DEFAULT 1)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "useAsExtraCommand",
"columnName": "useAsExtraCommand",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "useAsExtraCommandAudio",
"columnName": "useAsExtraCommandAudio",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "useAsExtraCommandVideo",
"columnName": "useAsExtraCommandVideo",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "searchHistory",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "query",
"columnName": "query",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "templateShortcuts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cookies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "logs",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadType` TEXT NOT NULL, `downloadTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "format",
"columnName": "format",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadType",
"columnName": "downloadType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadTime",
"columnName": "downloadTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "terminalDownloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `command` TEXT NOT NULL, `log` TEXT DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "command",
"columnName": "command",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "log",
"columnName": "log",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `downloadItemTemplate` TEXT NOT NULL, `everyNr` INTEGER NOT NULL, `everyCategory` TEXT NOT NULL, `everyTime` INTEGER NOT NULL, `weeklyConfig` TEXT, `monthlyConfig` TEXT, `status` TEXT NOT NULL, `startsTime` INTEGER NOT NULL, `endsDate` INTEGER NOT NULL DEFAULT 0, `endsAfterCount` INTEGER NOT NULL DEFAULT 0, `runCount` INTEGER NOT NULL DEFAULT 0, `getOnlyNewUploads` INTEGER NOT NULL DEFAULT 0, `retryMissingDownloads` INTEGER NOT NULL, `ignoredLinks` TEXT NOT NULL DEFAULT '[]', `alreadyProcessedLinks` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "downloadItemTemplate",
"columnName": "downloadItemTemplate",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyNr",
"columnName": "everyNr",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "everyCategory",
"columnName": "everyCategory",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "everyTime",
"columnName": "everyTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "weeklyConfig",
"columnName": "weeklyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "monthlyConfig",
"columnName": "monthlyConfig",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "startsTime",
"columnName": "startsTime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endsDate",
"columnName": "endsDate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "endsAfterCount",
"columnName": "endsAfterCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "runCount",
"columnName": "runCount",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "getOnlyNewUploads",
"columnName": "getOnlyNewUploads",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "retryMissingDownloads",
"columnName": "retryMissingDownloads",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ignoredLinks",
"columnName": "ignoredLinks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'[]'"
},
{
"fieldPath": "alreadyProcessedLinks",
"columnName": "alreadyProcessedLinks",
"affinity": "TEXT",
"notNull": true
}
],
"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, 'ae22d3024fcae377c9bd9b8ba6f1f299')"
]
}
}

View file

@ -63,6 +63,7 @@
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="true" android:exported="true"
android:taskAffinity=""
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:theme="@style/Theme.BottomSheet"> android:theme="@style/Theme.BottomSheet">
<intent-filter> <intent-filter>

View file

@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
TerminalItem::class, TerminalItem::class,
ObserveSourcesItem::class ObserveSourcesItem::class
], ],
version = 16, version = 17,
autoMigrations = [ autoMigrations = [
AutoMigration (from = 1, to = 2), AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3), AutoMigration (from = 2, to = 3),
@ -57,7 +57,8 @@ import com.deniscerri.ytdl.database.models.TerminalItem
AutoMigration (from = 12, to = 13), AutoMigration (from = 12, to = 13),
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED // AutoMigration (from = 13, to = 14) MANUALLY HANDLED
AutoMigration (from = 14, to = 15), AutoMigration (from = 14, to = 15),
AutoMigration (from = 15, to = 16, spec = Migrations.resetObserveSources::class) AutoMigration (from = 15, to = 16, spec = Migrations.resetObserveSources::class),
AutoMigration (from = 16, to = 17)
] ]
) )
abstract class DBManager : RoomDatabase(){ abstract class DBManager : RoomDatabase(){

View file

@ -83,6 +83,9 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
fun getQueuedDownloadsList() : List<DownloadItem> fun getQueuedDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Scheduled' ORDER BY downloadStartTime, id")
fun getScheduledDownloadsList() : List<DownloadItem>
@Query("SELECT id FROM downloads WHERE status='Queued' ORDER BY id") @Query("SELECT id FROM downloads WHERE status='Queued' ORDER BY id")
fun getQueuedDownloadsListIDs() : List<Long> fun getQueuedDownloadsListIDs() : List<Long>
@ -123,6 +126,15 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE id IN (:ids)") @Query("SELECT * FROM downloads WHERE id IN (:ids)")
fun getDownloadsByIds(ids: List<Long>) : List<DownloadItem> fun getDownloadsByIds(ids: List<Long>) : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Processing' AND id >=:id")
fun getProcessingItemsFromID(id: Long) : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Processing' AND id >=:id and id<=:id2")
fun getProcessingItemsBetweenIDs(id: Long, id2: Long) : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
fun getDownloadsByIdsFlow(ids: List<Long>) : Flow<List<DownloadItem>>
@Query("SELECT id FROM downloads ORDER BY id DESC LIMIT 1") @Query("SELECT id FROM downloads ORDER BY id DESC LIMIT 1")
fun getLastDownloadId(): Long fun getLastDownloadId(): Long

View file

@ -52,6 +52,9 @@ interface ResultDao {
@Query("SELECT * FROM results WHERE url=:url LIMIT 1") @Query("SELECT * FROM results WHERE url=:url LIMIT 1")
fun getResultByURL(url: String) : ResultItem? fun getResultByURL(url: String) : ResultItem?
@Query("SELECT * FROM results where id=:id LIMIT 1")
fun getResultByID(id: Long): ResultItem?
@Query("SELECT * from results WHERE id > :item1 AND id < :item2 ORDER BY id") @Query("SELECT * from results WHERE id > :item1 AND id < :item2 ORDER BY id")
fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem> fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem>

View file

@ -11,6 +11,7 @@ data class DownloadItemSimple(
var id: Long, var id: Long,
var url: String, var url: String,
var title: String, var title: String,
var playlistTitle: String,
var author: String, var author: String,
var thumb: String, var thumb: String,
var duration: String, var duration: String,

View file

@ -35,5 +35,7 @@ data class ObserveSourcesItem(
@ColumnInfo(defaultValue = "0") @ColumnInfo(defaultValue = "0")
var getOnlyNewUploads: Boolean, var getOnlyNewUploads: Boolean,
var retryMissingDownloads: Boolean, var retryMissingDownloads: Boolean,
@ColumnInfo(defaultValue = "[]")
var ignoredLinks: MutableList<String>,
var alreadyProcessedLinks : MutableList<String> var alreadyProcessedLinks : MutableList<String>
) : Parcelable ) : Parcelable

View file

@ -109,10 +109,22 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
return downloadDao.getDownloadById(id) return downloadDao.getDownloadById(id)
} }
fun getProcessingItemsFromID(id: Long) : Flow<List<DownloadItem>> {
return downloadDao.getProcessingItemsFromID(id)
}
fun getProcessingItemsBetweenIDs(first: Long, last:Long) : List<DownloadItem> {
return downloadDao.getProcessingItemsBetweenIDs(first, last)
}
fun getAllItemsByIDs(ids : List<Long>) : List<DownloadItem>{ fun getAllItemsByIDs(ids : List<Long>) : List<DownloadItem>{
return downloadDao.getDownloadsByIds(ids) return downloadDao.getDownloadsByIds(ids)
} }
fun getAllItemsByIDsFlow(ids: List<Long>) : Flow<List<DownloadItem>> {
return downloadDao.getDownloadsByIdsFlow(ids)
}
fun getActiveDownloads() : List<DownloadItem> { fun getActiveDownloads() : List<DownloadItem> {
return downloadDao.getActiveAndPausedDownloadsList() return downloadDao.getActiveAndPausedDownloadsList()
} }
@ -133,6 +145,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
return downloadDao.getQueuedDownloadsList() return downloadDao.getQueuedDownloadsList()
} }
fun getScheduledDownloads() : List<DownloadItem> {
return downloadDao.getScheduledDownloadsList()
}
fun getCancelledDownloads() : List<DownloadItem> { fun getCancelledDownloads() : List<DownloadItem> {
return downloadDao.getCancelledDownloadsList() return downloadDao.getCancelledDownloadsList()
} }
@ -210,8 +226,9 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
var delay = 0L var delay = 0L
if (queuedItems.isNotEmpty()){ if (queuedItems.isNotEmpty()){
delay = if (queuedItems[0].downloadStartTime != 0L){ val earliestStart = queuedItems.minBy { it.downloadStartTime }
queuedItems[0].downloadStartTime.minus(currentTime) delay = if (earliestStart.downloadStartTime != 0L){
earliestStart.downloadStartTime.minus(currentTime)
} else 0 } else 0
if (delay <= 60000L) delay = 0L if (delay <= 60000L) delay = 0L
} }

View file

@ -125,6 +125,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
resultDao.update(item) resultDao.update(item)
} }
fun getItemByID(id: Long) : ResultItem? {
return resultDao.getResultByID(id)
}
fun getItemByURL(url: String): ResultItem? { fun getItemByURL(url: String): ResultItem? {
return resultDao.getResultByURL(url) return resultDao.getResultByURL(url)
} }
@ -178,6 +182,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
val info = getResultsFromSource(downloadItem.url, resetResults = false, addToResults = false, singleItem = true).first() val info = getResultsFromSource(downloadItem.url, resetResults = false, addToResults = false, singleItem = true).first()
if (downloadItem.title.isEmpty()) downloadItem.title = info.title if (downloadItem.title.isEmpty()) downloadItem.title = info.title
if (downloadItem.author.isEmpty()) downloadItem.author = info.author if (downloadItem.author.isEmpty()) downloadItem.author = info.author
if (downloadItem.playlistTitle.isEmpty() && downloadItem.playlistTitle != "ytdlnis-Search") downloadItem.playlistTitle = info.playlistTitle
downloadItem.duration = info.duration downloadItem.duration = info.duration
downloadItem.website = info.website downloadItem.website = info.website
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb

View file

@ -125,6 +125,9 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
close() close()
} }
db.close() db.close()
"# $url\n" +
"# Generated by YTDLnis\n" +
cookieList.fold(StringBuilder("")) { acc, cookie -> cookieList.fold(StringBuilder("")) { acc, cookie ->
acc.append(cookie.toNetscapeFormat()).append("\n") acc.append(cookie.toNetscapeFormat()).append("\n")
}.toString() }.toString()

View file

@ -8,9 +8,12 @@ import android.content.res.Resources
import android.util.DisplayMetrics import android.util.DisplayMetrics
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.media3.exoplayer.offline.Download
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.Data import androidx.work.Data
@ -43,14 +46,21 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
@ -65,7 +75,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val allDownloads : Flow<PagingData<DownloadItem>> val allDownloads : Flow<PagingData<DownloadItem>>
val queuedDownloads : Flow<PagingData<DownloadItemSimple>> val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
val activeDownloads : Flow<List<DownloadItem>> val activeDownloads : Flow<List<DownloadItem>>
val processingDownloads : Flow<List<DownloadItem>> val allProcessingDownloads : Flow<List<DownloadItem>>
var mostRecentProcessingDownloads: Flow<List<DownloadItem>>
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>> val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
val erroredDownloads : Flow<PagingData<DownloadItemSimple>> val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
val savedDownloads : Flow<PagingData<DownloadItemSimple>> val savedDownloads : Flow<PagingData<DownloadItemSimple>>
@ -104,11 +115,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
auto, audio, video, command auto, audio, video, command
} }
private val urlsForAudioType = listOf(
"music", val processingDownloads = MediatorLiveData<List<DownloadItem>>(listOf())
"audio", var processingItemsFlow : ProcessingItemsJob? = null
"soundcloud" var processingItems = MutableStateFlow(false)
)
init { init {
dbManager = DBManager.getInstance(application) dbManager = DBManager.getInstance(application)
@ -133,7 +143,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
allDownloads = repository.allDownloads.flow allDownloads = repository.allDownloads.flow
queuedDownloads = repository.queuedDownloads.flow queuedDownloads = repository.queuedDownloads.flow
activeDownloads = repository.activeDownloads activeDownloads = repository.activeDownloads
processingDownloads = repository.processingDownloads allProcessingDownloads = repository.processingDownloads
mostRecentProcessingDownloads = repository.getProcessingItemsFromID(0)
savedDownloads = repository.savedDownloads.flow savedDownloads = repository.savedDownloads.flow
scheduledDownloads = repository.scheduledDownloads.flow scheduledDownloads = repository.scheduledDownloads.flow
cancelledDownloads = repository.cancelledDownloads.flow cancelledDownloads = repository.cancelledDownloads.flow
@ -285,8 +296,8 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false) val cropThumb = sharedPreferences.getBoolean("crop_thumbnail", false)
val customFileNameTemplate = when(historyItem.type) { val customFileNameTemplate = when(historyItem.type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)30B - %(title)120B") Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)30B - %(title)120B") Type.video -> sharedPreferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
else -> "" else -> ""
} }
@ -367,88 +378,83 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
var originItemIDs: List<Long>, var originItemIDs: List<Long>,
var processingDownloadItemIDs: MutableList<Long> = mutableListOf() var processingDownloadItemIDs: MutableList<Long> = mutableListOf()
) )
private fun updateProcessingJobData(item: ProcessingItemsJob?) {
var processingItemsJob: MutableLiveData<ProcessingItemsJob?> = MutableLiveData(null) processingItemsFlow = item
var _filteredProcessingItems = MutableStateFlow<List<DownloadItem>>(listOf())
var filteredProcessingItems: StateFlow<List<DownloadItem>> = _filteredProcessingItems
private fun updateProcessingJobData(item: ProcessingItemsJob) = viewModelScope.launch {
processingItemsJob.postValue(item)
} }
fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){ fun turnDownloadItemsToProcessingDownloads(itemIDs: List<Long>) = viewModelScope.launch(Dispatchers.IO){
processingItemsJob.postValue(ProcessingItemsJob(null, DownloadItem::class.java.toString(), itemIDs)) updateProcessingJobData(ProcessingItemsJob(null, DownloadItem::class.java.toString(), itemIDs))
val job = viewModelScope.launch(Dispatchers.IO) { val job = viewModelScope.launch(Dispatchers.IO) {
processingItems.emit(true)
try { try {
itemIDs.chunked(100).forEach { ids -> itemIDs.forEachIndexed { idx, it ->
val items = repository.getAllItemsByIDs(ids) val item = repository.getItemByID(it)
if (!isActive) { if (!isActive) throw CancellationException()
throw CancellationException()
}
items.forEach {
it.id = 0
it.status = DownloadRepository.Status.Processing.toString()
}
processingItemsJob.value?.apply { item.id = 0
processingDownloadItemIDs.addAll(repository.insertAll(items)) item.status = DownloadRepository.Status.Processing.toString()
processingItemsFlow?.apply {
val id = repository.insert(item)
if (idx == 0) mostRecentProcessingDownloads = repository.getProcessingItemsFromID(id)
processingDownloadItemIDs.add(id)
updateProcessingJobData(this) updateProcessingJobData(this)
} }
} }
processingItems.emit(false)
} catch (e: Exception) { } catch (e: Exception) {
processingItemsJob.value?.apply { processingItemsFlow?.apply {
this.processingDownloadItemIDs.chunked(100).forEach { this.processingDownloadItemIDs.chunked(100).forEach {
repository.deleteAllWithIDs(it) repository.deleteAllWithIDs(it)
} }
} }
processingItemsJob.postValue(null) updateProcessingJobData(null)
processingItems.emit(false)
} }
} }
processingItemsJob.postValue(ProcessingItemsJob(job, DownloadItem::class.java.toString(), itemIDs)) updateProcessingJobData(ProcessingItemsJob(job, DownloadItem::class.java.toString(), itemIDs))
} }
fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) { fun turnResultItemsToProcessingDownloads(itemIDs: List<Long>, downloadNow: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
processingItemsJob.postValue(ProcessingItemsJob(null, ResultItem::class.java.toString(), itemIDs)) updateProcessingJobData(ProcessingItemsJob(null, ResultItem::class.java.toString(), itemIDs))
val job = viewModelScope.launch(Dispatchers.IO) { val job = viewModelScope.launch(Dispatchers.IO) {
processingItems.emit(true)
try { try {
itemIDs.chunked(100).forEach { ids -> itemIDs.forEach { id ->
val items = resultRepository.getAllByIDs(ids) val item = resultRepository.getItemByID(id) ?: return@forEach
val downloadItems = items.map { val preferredType = getDownloadType(url = item.url).toString()
val preferredType = getDownloadType(url = it.url).toString() val downloadItem = createDownloadItemFromResult(result = item, givenType = Type.valueOf(
val tmp = createDownloadItemFromResult(result = it, givenType = Type.valueOf( preferredType
preferredType ))
)) downloadItem.status = DownloadRepository.Status.Processing.toString()
tmp.status = DownloadRepository.Status.Processing.toString()
tmp
}
if (!isActive) { if (!isActive) {
throw CancellationException() throw CancellationException()
} }
if (downloadNow) { if (downloadNow) {
downloadItems.forEach { downloadItem.status = DownloadRepository.Status.Queued.toString()
it.status = DownloadRepository.Status.Queued.toString() queueDownloads(listOf(downloadItem))
}
queueDownloads(downloadItems)
}else{ }else{
processingItemsJob.value?.apply { processingItemsFlow?.apply {
processingDownloadItemIDs.addAll(repository.insertAll(downloadItems)) processingDownloadItemIDs.add(repository.insert(downloadItem))
updateProcessingJobData(this) updateProcessingJobData(this)
} }
} }
} }
processingItems.emit(false)
}catch (e: Exception) { }catch (e: Exception) {
processingItemsJob.value?.apply { processingItemsFlow?.apply {
this.processingDownloadItemIDs.chunked(100).forEach { this.processingDownloadItemIDs.chunked(100).forEach {
repository.deleteAllWithIDs(it) repository.deleteAllWithIDs(it)
} }
} }
processingItemsJob.postValue(null) updateProcessingJobData(null)
processingItems.emit(false)
} }
} }
processingItemsJob.postValue(ProcessingItemsJob(job, ResultItem::class.java.toString(), itemIDs)) updateProcessingJobData(ProcessingItemsJob(job, ResultItem::class.java.toString(), itemIDs))
} }
fun insert(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){ fun insert(item: DownloadItem) = viewModelScope.launch(Dispatchers.IO){
@ -498,7 +504,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) { fun cancelActiveQueued() = viewModelScope.launch(Dispatchers.IO) {
processingItemsJob.value?.apply { this.job?.cancel(CancellationException()) } processingItemsFlow?.apply { this.job?.cancel(CancellationException()) }
repository.cancelActiveQueued() repository.cancelActiveQueued()
} }
@ -506,6 +512,10 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
return repository.getQueuedDownloads() return repository.getQueuedDownloads()
} }
fun getScheduled() : List<DownloadItem> {
return repository.getScheduledDownloads()
}
fun getCancelled() : List<DownloadItem> { fun getCancelled() : List<DownloadItem> {
return repository.getCancelledDownloads() return repository.getCancelledDownloads()
} }

View file

@ -178,8 +178,8 @@ class SharedDownloadViewModel(private val context: Context) {
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
val customFileNameTemplate = when(type) { val customFileNameTemplate = when(type) {
Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader)s - %(title)s") Type.audio -> sharedPreferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
Type.video -> sharedPreferences.getString("file_name_template", "%(uploader)s - %(title)s") Type.video -> sharedPreferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
else -> "" else -> ""
} }
@ -288,7 +288,7 @@ class SharedDownloadViewModel(private val context: Context) {
val prefAudio = sharedPreferences.getString("format_importance_audio", itemValues.joinToString(","))!! val prefAudio = sharedPreferences.getString("format_importance_audio", itemValues.joinToString(","))!!
prefAudio.split(",").forEachIndexed { idx, s -> prefAudio.split(",").forEachIndexed { idx, s ->
var importance = (itemValues.size - idx) * 10 val importance = (itemValues.size - idx) * 10
when(s) { when(s) {
"id" -> { "id" -> {
requirements.add {it: Format -> if (audioFormatIDPreference.contains(it.format_id)) importance else 0} requirements.add {it: Format -> if (audioFormatIDPreference.contains(it.format_id)) importance else 0}

View file

@ -98,7 +98,7 @@ class ShareActivity : BaseActivity() {
// val params = window.attributes // val params = window.attributes
// params.alpha = 0f // params.alpha = 0f
// window.attributes = params // window.attributes = params
// setContentView(R.layout.activity_share) setContentView(R.layout.activity_share)
}else{ }else{
window.run { window.run {

View file

@ -3,8 +3,10 @@ package com.deniscerri.ytdl.services
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdl.database.DBManager import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.ResultItem import com.deniscerri.ytdl.database.models.ResultItem
@ -18,8 +20,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ProcessDownloadsInBackgroundService : Service() { class ProcessDownloadsInBackgroundService : Service() {
@ -46,7 +50,25 @@ class ProcessDownloadsInBackgroundService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val binding = intent.getBooleanExtra("binding", false) val binding = intent.getBooleanExtra("binding", false)
if (binding) return super.onStartCommand(intent, flags, startId) if (binding) {
val cancel = intent.getBooleanExtra("cancel", false)
if (cancel) {
cancelAllProcessingJobs()
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
repository.deleteProcessing()
withContext(Dispatchers.Main){
if (Build.VERSION.SDK_INT > 23){
stopForeground(STOP_FOREGROUND_REMOVE)
}else{
stopForeground(true)
}
stopSelf()
}
}
}else{
return super.onStartCommand(intent, flags, startId)
}
}
val notificationUtil = NotificationUtil(this) val notificationUtil = NotificationUtil(this)
startForeground(System.currentTimeMillis().toInt(), notificationUtil.createProcessingDownloads()) startForeground(System.currentTimeMillis().toInt(), notificationUtil.createProcessingDownloads())
@ -98,7 +120,7 @@ class ProcessDownloadsInBackgroundService : Service() {
} }
} }
}else { }else {
repository.getAllItemsByIDs(processingItemIDs).apply { repository.getProcessingItemsBetweenIDs(processingItemIDs.first(), processingItemIDs.last()).apply {
this.chunked(100).map { this.chunked(100).map {
if (timeInMillis > 0){ if (timeInMillis > 0){
this.forEach { d -> this.forEach { d ->

View file

@ -26,18 +26,20 @@ open class BaseActivity : AppCompatActivity() {
fun askPermissions() { fun askPermissions() {
val permissions = arrayListOf<String>() val permissions = arrayListOf<String>()
if (!checkFilePermission()) { val hasFilePerm = if (Build.VERSION.SDK_INT >= 30) {
if (Build.VERSION.SDK_INT >= 33){ true
permissions.add(Manifest.permission.READ_MEDIA_AUDIO) }else {
permissions.add(Manifest.permission.READ_MEDIA_VIDEO) (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (Build.VERSION.SDK_INT >= 34){ == PackageManager.PERMISSION_GRANTED) &&
permissions.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
} == PackageManager.PERMISSION_GRANTED)
}else{
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
} }
if (!hasFilePerm){
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (!checkNotificationPermission()){ if (!checkNotificationPermission()){
permissions.add(Manifest.permission.POST_NOTIFICATIONS) permissions.add(Manifest.permission.POST_NOTIFICATIONS)
@ -67,20 +69,6 @@ open class BaseActivity : AppCompatActivity() {
} }
} }
private fun checkFilePermission(): Boolean {
return if(Build.VERSION.SDK_INT >= 33){
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
== PackageManager.PERMISSION_GRANTED) &&
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_VIDEO)
== PackageManager.PERMISSION_GRANTED)
}else{
(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) &&
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED)
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun checkNotificationPermission(): Boolean { private fun checkNotificationPermission(): Boolean {
return (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) return (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)

View file

@ -915,10 +915,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggesti
queriesConstraint?.isVisible = queriesChipGroup?.childCount!! > 0 queriesConstraint?.isVisible = queriesChipGroup?.childCount!! > 0
searchSuggestionsAdapter?.getList()?.apply { searchSuggestionsAdapter?.getList()?.apply {
if (this.first().type == SearchSuggestionType.CLIPBOARD){ if (this.isNotEmpty()) {
val newList = this.toMutableList().drop(1) if (this.first().type == SearchSuggestionType.CLIPBOARD){
searchSuggestionsAdapter?.submitList(newList) val newList = this.toMutableList().drop(1)
searchSuggestionsAdapter?.submitList(newList)
}
} }
} }
} }

View file

@ -76,11 +76,11 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
// 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.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
if (title.length > 100) { if (title.length > 100) {
title = title.substring(0, 40) + "..." title = title.substring(0, 40) + "..."
} }
itemTitle.text = title.ifEmpty { item.url } itemTitle.text = title
// Author ---------------------------------- // Author ----------------------------------
val author = card.findViewById<TextView>(R.id.author) val author = card.findViewById<TextView>(R.id.author)
@ -107,7 +107,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val fileSize = FileUtil.convertFileSize(item.format.filesize) val fileSize = FileUtil.convertFileSize(item.format.filesize)
if (fileSize != "?") sideDetails.add(fileSize) if (fileSize != "?") sideDetails.add(fileSize)
formatDetailsChip.text = sideDetails.joinToString(" · ") formatDetailsChip.text = sideDetails.filter { it.isNotBlank() }.joinToString(" · ")
//OUTPUT //OUTPUT
val output = card.findViewById<TextView>(R.id.output) val output = card.findViewById<TextView>(R.id.output)

View file

@ -78,11 +78,11 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
// 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.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
if (title.length > 100) { if (title.length > 100) {
title = title.substring(0, 40) + "..." title = title.substring(0, 40) + "..."
} }
itemTitle.text = title.ifEmpty { item.url } itemTitle.text = title
//DOWNLOAD TYPE ----------------------------- //DOWNLOAD TYPE -----------------------------
val type = card.findViewById<TextView>(R.id.download_type) val type = card.findViewById<TextView>(R.id.download_type)

View file

@ -0,0 +1,107 @@
package com.deniscerri.ytdl.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CommandTemplate
import com.deniscerri.ytdl.database.models.GithubRelease
import com.deniscerri.ytdl.util.Extensions.popup
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import java.text.SimpleDateFormat
import java.util.Locale
class ChangelogAdapter(activity: Activity) : ListAdapter<GithubRelease?, ChangelogAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val activity: Activity
init {
this.activity = activity
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var layoutParams: LinearLayout.LayoutParams
init {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams.setMargins(10, 10, 10, 0)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.changelog_item, parent, false)
return ViewHolder(cardView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val it = getItem(position) ?: return
val card = holder.itemView
val layoutParams = holder.layoutParams
card.findViewById<TextView>(R.id.version).text = it.tag_name
card.findViewById<TextView>(R.id.date).text = SimpleDateFormat(
DateFormat.getBestDateTimePattern(
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
val mdText = card.findViewById<TextView>(R.id.content)
val mw = Markwon.builder(activity).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
ContextCompat.startActivity(activity, browserIntent, Bundle())
}
}
}).build()
mw.setMarkdown(mdText, it.body)
val assetGroup = card.findViewById<ChipGroup>(R.id.assets)
assetGroup.removeAllViews()
it.assets.forEachIndexed { idx, c ->
val tmp = activity.layoutInflater.inflate(R.layout.filter_chip, assetGroup, false) as Chip
tmp.isCheckable = false
tmp.layoutParams = layoutParams
tmp.text = c.name
tmp.id = idx
tmp.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
ContextCompat.startActivity(activity, browserIntent, Bundle())
}
assetGroup!!.addView(tmp)
}
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<GithubRelease> = object : DiffUtil.ItemCallback<GithubRelease>() {
override fun areItemsTheSame(oldItem: GithubRelease, newItem: GithubRelease): Boolean {
return oldItem.html_url == newItem.html_url
}
override fun areContentsTheSame(oldItem: GithubRelease, newItem: GithubRelease): Boolean {
return oldItem.tag_name == newItem.tag_name
}
}
}
}

View file

@ -10,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.core.view.isVisible
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -71,14 +72,15 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val duration = card.findViewById<TextView>(R.id.duration) val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration duration.text = item.duration
duration.isVisible = item.duration != "-1"
// 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.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
if (title.length > 100) { if (title.length > 100) {
title = title.substring(0, 40) + "..." title = title.substring(0, 40) + "..."
} }
itemTitle.text = title.ifEmpty { item.url } itemTitle.text = title
//DOWNLOAD TYPE ----------------------------- //DOWNLOAD TYPE -----------------------------
val type = card.findViewById<TextView>(R.id.download_type) val type = card.findViewById<TextView>(R.id.download_type)

View file

@ -87,14 +87,16 @@ class QueuedDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val duration = card.findViewById<TextView>(R.id.duration) val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration duration.text = item.duration
duration.isVisible = item.duration != "-1"
// 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.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
if (title.length > 100) { if (title.length > 100) {
title = title.substring(0, 40) + "..." title = title.substring(0, 40) + "..."
} }
itemTitle.text = title.ifEmpty { item.url } itemTitle.text = title
//DOWNLOAD TYPE ----------------------------- //DOWNLOAD TYPE -----------------------------
val type = card.findViewById<TextView>(R.id.download_type) val type = card.findViewById<TextView>(R.id.download_type)

View file

@ -12,6 +12,7 @@ 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.core.view.isVisible
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -83,14 +84,15 @@ class ScheduledDownloadAdapter(onItemClickListener: OnItemClickListener, activit
val duration = card.findViewById<TextView>(R.id.duration) val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration duration.text = item.duration
duration.isVisible = item.duration != "-1"
// 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.ifEmpty { item.playlistTitle.ifEmpty { item.url } }
if (title.length > 100) { if (title.length > 100) {
title = title.substring(0, 40) + "..." title = title.substring(0, 40) + "..."
} }
itemTitle.text = title.ifEmpty { item.url } itemTitle.text = title
//DOWNLOAD TYPE ----------------------------- //DOWNLOAD TYPE -----------------------------
val type = card.findViewById<TextView>(R.id.download_type) val type = card.findViewById<TextView>(R.id.download_type)

View file

@ -498,7 +498,7 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
suggestedChapters.visibility = View.VISIBLE suggestedChapters.visibility = View.VISIBLE
suggestedChips.removeAllViews() suggestedChips.removeAllViews()
chapters!!.forEach { chapters!!.forEach {
val chip = layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip val chip = layoutInflater.inflate(R.layout.suggestion_chip, suggestedChips, false) as Chip
chip.text = it.title chip.text = it.title
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK)) chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false chip.isCheckedIconVisible = false
@ -514,9 +514,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
//replace existing chip to enable click events //replace existing chip to enable click events
val idx = selectedCuts.indexOfFirst { c -> c.contains(it.title) } val idx = selectedCuts.indexOfFirst { c -> c.contains(it.title) }
if (idx > -1){ if (idx > -1){
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text == it.title } val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text.contains(it.title) }
chipGroup.removeView(chipForDeletion) chipForDeletion?.apply {
createChapterChip(it, idx) chipGroup.removeView(chipForDeletion)
createChapterChip(it, idx)
}
} }
} }
} }

View file

@ -100,8 +100,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var currentDownloadIDs: List<Long> private lateinit var currentDownloadIDs: List<Long>
private var processingItemsCount : Int = 0 private var processingItemsCount : Int = 0
private lateinit var jobData: DownloadViewModel.ProcessingItemsJob
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -174,58 +172,57 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
shimmerTitle = view.findViewById(R.id.shimmer_loading_title) shimmerTitle = view.findViewById(R.id.shimmer_loading_title)
shimmerSubtitle = view.findViewById(R.id.shimmer_loading_subtitle) shimmerSubtitle = view.findViewById(R.id.shimmer_loading_subtitle)
downloadViewModel.processingItemsJob.observe(this) { lifecycleScope.launch {
it?.apply { downloadViewModel.processingItems.collectLatest {
lifecycleScope.launch { val loading = it
jobData = it toggleLoadingShimmerTitle(loading)
processingItemsCount = it.processingDownloadItemIDs.size bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
count.text = "${processingItemsCount} ${getString(R.string.selected)}" if (!loading){
continueInBackgroundSnackBar.dismiss()
val loading = it.originItemIDs.size != processingItemsCount
toggleLoadingShimmerTitle(loading)
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
if (!loading){
continueInBackgroundSnackBar.dismiss()
}
downloadViewModel.processingDownloads.map { p -> p.filter { pd -> jobData.processingDownloadItemIDs.contains(pd.id) } }.collectLatest { items ->
listAdapter.submitList(items)
updateFileSize(items.map { it2 -> it2.format.filesize })
if (items.isNotEmpty()){
if (items.all { it2 -> it2.type == items[0].type }) {
bottomAppBar.menu[1].icon?.alpha = 255
if (items[0].type != DownloadViewModel.Type.command) {
bottomAppBar.menu[3].icon?.alpha = 255
}
} else {
bottomAppBar.menu[1].icon?.alpha = 30
bottomAppBar.menu[3].icon?.alpha = 30
}
val type = items.first().type
when(type){
DownloadViewModel.Type.audio -> {
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
}
DownloadViewModel.Type.video -> {
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
}
DownloadViewModel.Type.command -> {
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
}
else -> {}
}
}
}
} }
} }
} }
lifecycleScope.launch {
downloadViewModel.mostRecentProcessingDownloads.collectLatest { items ->
processingItemsCount = items.size
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
listAdapter.submitList(items)
updateFileSize(items.map { it2 -> it2.format.filesize })
if (items.isNotEmpty()){
if (items.all { it2 -> it2.type == items[0].type }) {
bottomAppBar.menu[1].icon?.alpha = 255
if (items[0].type != DownloadViewModel.Type.command) {
bottomAppBar.menu[3].icon?.alpha = 255
}
} else {
bottomAppBar.menu[1].icon?.alpha = 30
bottomAppBar.menu[3].icon?.alpha = 30
}
val type = items.first().type
when(type){
DownloadViewModel.Type.audio -> {
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
}
DownloadViewModel.Type.video -> {
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
}
DownloadViewModel.Type.command -> {
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
}
else -> {}
}
}
}
}
scheduleBtn.setOnClickListener{ scheduleBtn.setOnClickListener{
fun initSchedule(processingFinished: Boolean) { fun initSchedule(processingFinished: Boolean) {
UiUtil.showDatePicker(parentFragmentManager) { cal -> UiUtil.showDatePicker(parentFragmentManager) { cal ->
@ -237,9 +234,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.deleteAllWithID(currentDownloadIDs)
} }
jobData.job?.cancel(CancellationException()) getProcessingItemsData()?.apply {
jobData.job = null this.job?.cancel(CancellationException())
downloadProcessDownloadsInBackground(jobData, processingFinished) this.job = null
downloadProcessDownloadsInBackground(this, processingFinished)
}
val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(cal.timeInMillis) val date = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(cal.timeInMillis)
Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show() Toast.makeText(context, getString(R.string.download_rescheduled_to) + " " + date, Toast.LENGTH_LONG).show()
@ -268,9 +267,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.deleteAllWithID(currentDownloadIDs)
} }
jobData.job?.cancel(CancellationException()) getProcessingItemsData()?.apply {
jobData.job = null this.job?.cancel(CancellationException())
downloadProcessDownloadsInBackground(jobData, processingFinished) this.job = null
downloadProcessDownloadsInBackground(this, processingFinished)
}
dismiss() dismiss()
} }
} }
@ -295,10 +296,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.moveProcessingToSavedCategory(jobData.processingDownloadItemIDs) downloadViewModel.moveProcessingToSavedCategory(getProcessingDownloadItemIDs())
}
getProcessingItemsData()?.apply {
this.job?.cancel(CancellationException())
this.job = null
} }
jobData.job?.cancel(CancellationException())
jobData.job = null
dismiss() dismiss()
} }
} }
@ -309,13 +312,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val formatListener = object : OnFormatClickListener { val formatListener = object : OnFormatClickListener {
override fun onFormatClick(selectedFormats: List<FormatTuple>) { override fun onFormatClick(selectedFormats: List<FormatTuple>) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingFormat(jobData.processingDownloadItemIDs, selectedFormats) downloadViewModel.updateProcessingFormat(getProcessingDownloadItemIDs(), selectedFormats)
} }
} }
override fun onFormatsUpdated(allFormats: List<List<Format>>) { override fun onFormatsUpdated(allFormats: List<List<Format>>) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingAllFormats(jobData.processingDownloadItemIDs, allFormats) downloadViewModel.updateProcessingAllFormats(getProcessingDownloadItemIDs(), allFormats)
} }
} }
@ -323,10 +326,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
override fun onContinueOnBackground() { override fun onContinueOnBackground() {
requireActivity().lifecycleScope.launch { requireActivity().lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.continueUpdatingFormatsOnBackground(jobData.processingDownloadItemIDs) downloadViewModel.continueUpdatingFormatsOnBackground(getProcessingDownloadItemIDs())
}
getProcessingItemsData()?.apply {
this.job?.cancel(CancellationException())
this.job = null
} }
jobData.job?.cancel(CancellationException())
jobData.job = null
dismiss() dismiss()
} }
} }
@ -364,7 +369,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
audio!!.setOnClickListener { audio!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.audio) downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.audio)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24) preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[1].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
@ -376,7 +381,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
video!!.setOnClickListener { video!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{ CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.video) downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.video)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24) preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[1].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
@ -388,7 +393,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
command!!.setOnClickListener { command!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{ CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.command) downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.command)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24) preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[1].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
@ -412,7 +417,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.format -> { R.id.format -> {
lifecycleScope.launch { lifecycleScope.launch {
val res = withContext(Dispatchers.IO){ val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs,) downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
} }
if (!res.first){ if (!res.first){
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
@ -431,12 +436,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
) )
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingCommandFormat(jobData.processingDownloadItemIDs, format) downloadViewModel.updateProcessingCommandFormat(getProcessingDownloadItemIDs(), format)
} }
} }
}else{ }else{
val items = withContext(Dispatchers.IO){ val items = withContext(Dispatchers.IO){
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs) downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
} }
val flatFormatCollection = items.map { it.allFormats }.flatten() val flatFormatCollection = items.map { it.allFormats }.flatten()
val commonFormats = withContext(Dispatchers.IO){ val commonFormats = withContext(Dispatchers.IO){
@ -460,7 +465,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.more -> { R.id.more -> {
lifecycleScope.launch { lifecycleScope.launch {
val res = withContext(Dispatchers.IO){ val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs) downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
} }
if (!res.first) { if (!res.first) {
Toast.makeText( Toast.makeText(
@ -481,7 +486,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding) sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
val items = withContext(Dispatchers.IO){ val items = withContext(Dispatchers.IO){
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs) downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
} }
UiUtil.configureAudio( UiUtil.configureAudio(
@ -562,7 +567,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding) sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
val items = withContext(Dispatchers.IO){ val items = withContext(Dispatchers.IO){
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs) downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
} }
UiUtil.configureVideo( UiUtil.configureVideo(
@ -700,7 +705,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingDownloadPath(jobData.processingDownloadItemIDs, result.data?.data.toString()) downloadViewModel.updateProcessingDownloadPath(getProcessingDownloadItemIDs(), result.data?.data.toString())
} }
val path = FileUtil.formatPath(result.data!!.data.toString()) val path = FileUtil.formatPath(result.data!!.data.toString())
@ -832,9 +837,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
override fun onDismiss(dialog: DialogInterface) { override fun onDismiss(dialog: DialogInterface) {
if (jobData.job != null){ getProcessingItemsData()?.apply {
jobData.job?.cancel(CancellationException()) if (this.job != null){
downloadViewModel.deleteAllWithID(jobData.processingDownloadItemIDs) this.job?.cancel(CancellationException())
downloadViewModel.deleteAllWithID(this.processingDownloadItemIDs)
}
} }
super.onDismiss(dialog) super.onDismiss(dialog)
} }
@ -916,6 +923,14 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
} }
private fun getProcessingDownloadItemIDs() : List<Long> {
return downloadViewModel.processingItemsFlow?.processingDownloadItemIDs ?: listOf()
}
private fun getProcessingItemsData() : DownloadViewModel.ProcessingItemsJob? {
return downloadViewModel.processingItemsFlow
}
private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob, processingFinished: Boolean, timeInMillis: Long = 0){ private fun downloadProcessDownloadsInBackground(jobData: DownloadViewModel.ProcessingItemsJob, processingFinished: Boolean, timeInMillis: Long = 0){
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent -> Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
intent.putExtra("itemType", jobData.originItemType) intent.putExtra("itemType", jobData.originItemType)

View file

@ -523,6 +523,11 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
mutableListOf() mutableListOf()
}else{ }else{
currentItem?.alreadyProcessedLinks ?: mutableListOf() currentItem?.alreadyProcessedLinks ?: mutableListOf()
},
ignoredLinks = if (resetProcessedLinks.isChecked || !getOnlyNewUploads.isChecked){
mutableListOf()
}else{
currentItem?.ignoredLinks ?: mutableListOf()
} }
) )

View file

@ -5,6 +5,7 @@ import android.app.Dialog
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.method.DigitsKeyListener
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
@ -106,7 +107,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
count.text = "0 ${resources.getString(R.string.selected)}" count.text = "0 ${resources.getString(R.string.selected)}"
fromTextInput = view.findViewById(R.id.from_textinput) fromTextInput = view.findViewById(R.id.from_textinput)
fromTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
toTextInput = view.findViewById(R.id.to_textinput) toTextInput = view.findViewById(R.id.to_textinput)
toTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
fromTextInput.editText!!.doAfterTextChanged { _text -> fromTextInput.editText!!.doAfterTextChanged { _text ->

View file

@ -67,7 +67,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }
@ -251,24 +251,20 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
true true
} }
R.id.redownload -> { R.id.redownload -> {
lifecycleScope.launch { val selectedObjects = runBlocking {
val selectedObjects = getSelectedIDs() getSelectedIDs()
val showDownloadCard = preferences.getBoolean("download_card", true)
if (showDownloadCard) {
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
downloadViewModel.turnDownloadItemsToProcessingDownloads(selectedObjects)
}
withContext(Dispatchers.Main){
val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundle)
}
}else {
downloadViewModel.reQueueDownloadItems(selectedObjects)
}
adapter.clearCheckedItems()
} }
val showDownloadCard = preferences.getBoolean("download_card", true)
if (showDownloadCard) {
downloadViewModel.turnDownloadItemsToProcessingDownloads(selectedObjects)
val bundle = Bundle()
bundle.putLongArray("currentDownloadIDs", selectedObjects.toLongArray())
findNavController().navigate(R.id.downloadMultipleBottomSheetDialog2, bundle)
}else {
downloadViewModel.reQueueDownloadItems(selectedObjects)
}
adapter.clearCheckedItems()
runBlocking { runBlocking {
actionMode?.finish() actionMode?.finish()
} }

View file

@ -39,6 +39,7 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -288,8 +289,6 @@ class DownloadQueueMainFragment : Fragment(){
val binder = service as ProcessDownloadsInBackgroundService.LocalBinder val binder = service as ProcessDownloadsInBackgroundService.LocalBinder
mService = binder.service mService = binder.service
mBound = true mBound = true
mService.cancelAllProcessingJobs()
} }
override fun onServiceDisconnected(arg0: ComponentName) { override fun onServiceDisconnected(arg0: ComponentName) {
@ -299,6 +298,7 @@ class DownloadQueueMainFragment : Fragment(){
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent -> Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
intent.putExtra("binding", true) intent.putExtra("binding", true)
intent.putExtra("cancel", true)
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE) requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE)
} }
} }

View file

@ -67,7 +67,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }

View file

@ -414,7 +414,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
val w = websites[i] val w = websites[i]
if (w == "null" || w.isEmpty()) continue if (w == "null" || w.isEmpty()) continue
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip val tmp = layoutinflater!!.inflate(R.layout.filter_chip, websiteGroup, false) as Chip
tmp.text = w.replaceFirstChar { if (it.isLowerCase()) it.titlecase(java.util.Locale.getDefault()) else it.toString() } tmp.text = w
tmp.id = i tmp.id = i
tmp.setOnClickListener { tmp.setOnClickListener {
Log.e(TAG, tmp.isChecked.toString()) Log.e(TAG, tmp.isChecked.toString())

View file

@ -66,7 +66,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.generic_list, container, false) fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView return fragmentView
} }

View file

@ -15,7 +15,9 @@ import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.EditText import android.widget.EditText
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -30,9 +32,11 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CookieItem import com.deniscerri.ytdl.database.models.CookieItem
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
import com.deniscerri.ytdl.ui.adapter.CookieAdapter import com.deniscerri.ytdl.ui.adapter.CookieAdapter
import com.deniscerri.ytdl.util.Extensions.enableTextHighlight
import com.deniscerri.ytdl.util.FileUtil import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch import com.google.android.material.materialswitch.MaterialSwitch
@ -114,7 +118,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
private fun initChips() { private fun initChips() {
val new = view?.findViewById<Chip>(R.id.newCookie) val new = view?.findViewById<Chip>(R.id.newCookie)
new?.setOnClickListener { new?.setOnClickListener {
showDialog("") showDialog(null)
} }
} }
@ -168,22 +172,25 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
} }
} }
private fun showDialog(url: String){ private fun showDialog(item: CookieItem?){
lifecycleScope.launch { lifecycleScope.launch {
val item = withContext(Dispatchers.IO){
cookiesViewModel.getByURL(url)
}
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.cookies)) builder.setTitle(getString(R.string.cookies))
val inputLayout = layoutInflater.inflate(R.layout.textinput, null) val layout = layoutInflater.inflate(R.layout.cookie_details, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext) val editText = layout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL" layout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
val text = if (url.isNullOrBlank()) "https://" else url val text = item?.url ?: "https://"
editText.setText(text) editText.setText(text)
editText.setSelection(editText.text.length) editText.setSelection(editText.text.length)
builder.setView(inputLayout) val current = layout.findViewById<MaterialCardView>(R.id.current)
current.isVisible = item != null
item?.apply {
current.findViewById<TextView>(R.id.currentText).apply {
setText(item.content)
enableTextHighlight()
}
}
builder.setView(layout)
item?.apply { item?.apply {
builder.setNeutralButton( builder.setNeutralButton(
@ -194,7 +201,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
} }
builder.setPositiveButton( builder.setPositiveButton(
getString(R.string.get_cookies) if (item == null) getString(R.string.get_cookies) else getString(R.string.update)
) { dialog: DialogInterface?, which: Int -> ) { dialog: DialogInterface?, which: Int ->
val myIntent = Intent(requireContext(), WebViewActivity::class.java) val myIntent = Intent(requireContext(), WebViewActivity::class.java)
myIntent.putExtra("url", editText.text.toString()) myIntent.putExtra("url", editText.text.toString())
@ -220,12 +227,8 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
} }
companion object {
private const val TAG = "CookiesActivity"
}
override fun onItemClick(cookie: CookieItem) { override fun onItemClick(cookie: CookieItem) {
showDialog(cookie.url) showDialog(cookie)
} }
override fun onSelected(cookie: CookieItem) { override fun onSelected(cookie: CookieItem) {

View file

@ -99,7 +99,6 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
topAppBar.setOnMenuItemClickListener { m: MenuItem -> topAppBar.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId val itemId = m.itemId
if (itemId == R.id.remove_logs) { if (itemId == R.id.remove_logs) {
throw Exception("asffffffffff")
try{ try{
val deleteDialog = MaterialAlertDialogBuilder(requireContext()) val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.confirm_delete_history)) deleteDialog.setTitle(getString(R.string.confirm_delete_history))

View file

@ -142,9 +142,9 @@ class FolderSettingsFragment : BaseSettingsFragment() {
} }
videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]" videoFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.video)}]"
videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader)s - %(title)s") videoFilenameTemplate?.summary = preferences.getString("file_name_template", "%(uploader).30B - %(title).170B")
audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]" audioFilenameTemplate?.title = "${getString(R.string.file_name_template)} [${getString(R.string.audio)}]"
audioFilenameTemplate?.summary = preferences.getString("file_name_template_audio", "%(uploader)s - %(title)s") audioFilenameTemplate?.summary = preferences.getString("file_name_template_audio", "%(uploader).30B - %(title).170B")
videoFilenameTemplate?.setOnPreferenceClickListener { videoFilenameTemplate?.setOnPreferenceClickListener {
UiUtil.showFilenameTemplateDialog(requireActivity(), videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") { UiUtil.showFilenameTemplateDialog(requireActivity(), videoFilenameTemplate?.summary.toString() ?: "", "${getString(R.string.file_name_template)} [${getString(R.string.video)}]") {

View file

@ -171,6 +171,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
"settings" -> json.add("settings", backupSettings(preferences)) "settings" -> json.add("settings", backupSettings(preferences))
"downloads" -> json.add("downloads", backupHistory()) "downloads" -> json.add("downloads", backupHistory())
"queued" -> json.add("queued", backupQueuedDownloads() ) "queued" -> json.add("queued", backupQueuedDownloads() )
"scheduled" -> json.add("scheduled", backupScheduledDownloads() )
"cancelled" -> json.add("cancelled", backupCancelledDownloads() ) "cancelled" -> json.add("cancelled", backupCancelledDownloads() )
"errored" -> json.add("errored", backupErroredDownloads() ) "errored" -> json.add("errored", backupErroredDownloads() )
"saved" -> json.add("saved", backupSavedDownloads() ) "saved" -> json.add("saved", backupSavedDownloads() )
@ -279,6 +280,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
return JsonArray() return JsonArray()
} }
private suspend fun backupScheduledDownloads() : JsonArray {
runCatching {
val items = withContext(Dispatchers.IO) {
downloadViewModel.getScheduled()
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
return JsonArray()
}
private suspend fun backupCancelledDownloads() : JsonArray { private suspend fun backupCancelledDownloads() : JsonArray {
runCatching { runCatching {
val items = withContext(Dispatchers.IO) { val items = withContext(Dispatchers.IO) {
@ -493,6 +508,25 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
errorMessage.append("\n QUEUED DOWNLOADS RESTORE FAILED: \n ${it.message}") errorMessage.append("\n QUEUED DOWNLOADS RESTORE FAILED: \n ${it.message}")
} }
//scheduled downloads restore
kotlin.runCatching {
if(json.has("scheduled")){
val items = json.getAsJsonArray("scheduled")
val scheduled = mutableListOf<DownloadItem>()
items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
item.id = 0L
scheduled.add(item)
}
if (scheduled.isNotEmpty()) {
finalMessage.append("${getString(R.string.scheduled)}: ${scheduled.count()}\n")
withContext(Dispatchers.IO){downloadViewModel.queueDownloads(scheduled)}
}
}
}.onFailure {
errorMessage.append("\n SCHEDULED DOWNLOADS RESTORE FAILED: \n ${it.message}")
}
//cancelled downloads restore //cancelled downloads restore
kotlin.runCatching { kotlin.runCatching {
if(json.has("cancelled")){ if(json.has("cancelled")){

View file

@ -51,6 +51,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
formatImportanceAudio?.apply { formatImportanceAudio?.apply {
title = "${getString(R.string.format_importance)} [${getString(R.string.audio)}]"
val items = requireContext().getStringArray(R.array.format_importance_audio) val items = requireContext().getStringArray(R.array.format_importance_audio)
val itemValues = requireContext().getStringArray(R.array.format_importance_audio_values).toSet() val itemValues = requireContext().getStringArray(R.array.format_importance_audio_values).toSet()
val prefVideo = prefs.getString("format_importance_audio", itemValues.joinToString(","))!! val prefVideo = prefs.getString("format_importance_audio", itemValues.joinToString(","))!!
@ -62,7 +63,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
Pair(it, items[itemValues.indexOf(it)]) Pair(it, items[itemValues.indexOf(it)])
}.toMutableList() }.toMutableList()
showFormatImportanceDialog(getString(R.string.format_importance_audio), itms) { new -> showFormatImportanceDialog(title.toString(), itms) { new ->
editor.putString("format_importance_audio", new.joinToString(",") { it.first }).apply() editor.putString("format_importance_audio", new.joinToString(",") { it.first }).apply()
formatImportanceAudio.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n") formatImportanceAudio.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
} }
@ -71,6 +72,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
} }
formatImportanceVideo?.apply { formatImportanceVideo?.apply {
title = "${getString(R.string.format_importance)} [${getString(R.string.video)}]"
val items = requireContext().getStringArray(R.array.format_importance_video) val items = requireContext().getStringArray(R.array.format_importance_video)
val itemValues = requireContext().getStringArray(R.array.format_importance_video_values).toSet() val itemValues = requireContext().getStringArray(R.array.format_importance_video_values).toSet()
val prefVideo = prefs.getString("format_importance_video", itemValues.joinToString(","))!! val prefVideo = prefs.getString("format_importance_video", itemValues.joinToString(","))!!
@ -82,7 +84,7 @@ class ProcessingSettingsFragment : BaseSettingsFragment() {
Pair(it, items[itemValues.indexOf(it)]) Pair(it, items[itemValues.indexOf(it)])
}.toMutableList() }.toMutableList()
showFormatImportanceDialog(getString(R.string.format_importance_video), itms) {new -> showFormatImportanceDialog(title.toString(), itms) {new ->
editor.putString("format_importance_video", new.joinToString(",") { it.first }).apply() editor.putString("format_importance_video", new.joinToString(",") { it.first }).apply()
formatImportanceVideo.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n") formatImportanceVideo.summary = new.map { it.second }.mapIndexed { index, s -> "${index + 1}. $s" }.joinToString("\n")
} }

View file

@ -16,7 +16,7 @@ class CrashListener(private val context: Context) : Thread.UncaughtExceptionHand
override fun uncaughtException(p0: Thread, p1: Throwable) { override fun uncaughtException(p0: Thread, p1: Throwable) {
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) { CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
createLog("${p1.message}\n${p1.stackTrace}") createLog("${p1.message}\n\n${p1.stackTrace.joinToString("\n")}")
} }
} }

View file

@ -296,7 +296,15 @@ object FileUtil {
} }
fun getDownloadArchivePath(context: Context) : String { fun getDownloadArchivePath(context: Context) : String {
val folder = PreferenceManager.getDefaultSharedPreferences(context).getString("download_archive_path", context.filesDir.absolutePath + "/")!! var folder = PreferenceManager.getDefaultSharedPreferences(context).getString("download_archive_path", "")!!
if (folder == "") {
val externalPath = context.getExternalFilesDir(null)
folder = if (externalPath == null){
context.cacheDir.absolutePath + File.separator
}else{
externalPath.absolutePath + File.separator
}
}
return "${formatPath(folder)}download_archive.txt" return "${formatPath(folder)}download_archive.txt"
} }

View file

@ -648,7 +648,7 @@ class InfoUtil(private val context: Context) {
for (result in results) { for (result in results) {
if (result.isNullOrBlank()) continue if (result.isNullOrBlank()) continue
val jsonObject = JSONObject(result) val jsonObject = JSONObject(result)
val title = jsonObject.getStringByAny("track", "alt_title", "title", "webpage_url_basename") var title = jsonObject.getStringByAny("alt_title", "title", "webpage_url_basename")
if (title == "[Private video]" || title == "[Deleted video]") continue if (title == "[Private video]" || title == "[Deleted video]") continue
var author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id") var author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
@ -678,7 +678,8 @@ class InfoUtil(private val context: Context) {
} }
} }
val website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor") var website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor")
if (website == "Generic" || website == "HTML5MediaEmbed") website = jsonObject.getStringByAny("webpage_url_domain")
var playlistTitle = jsonObject.getStringByAny("playlist_title") var playlistTitle = jsonObject.getStringByAny("playlist_title")
var playlistURL: String? = "" var playlistURL: String? = ""
var playlistIndex: Int? = null var playlistIndex: Int? = null
@ -724,6 +725,13 @@ class InfoUtil(private val context: Context) {
} }
} }
val type = jsonObject.getStringByAny("_type")
if (type == "playlist" && playlistTitle.isEmpty()) {
playlistTitle = title
title = ""
author = ""
}
val res = ResultItem(0, val res = ResultItem(0,
url, url,
title, title,
@ -977,8 +985,8 @@ class InfoUtil(private val context: Context) {
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1) val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments) if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
val retries = sharedPreferences.getString("--retries", "")!! val retries = sharedPreferences.getString("retries", "")!!
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!! val fragmentRetries = sharedPreferences.getString("fragment_retries", "")!!
if(retries.isNotEmpty()) request.addOption("--retries", retries) if(retries.isNotEmpty()) request.addOption("--retries", retries)
if(fragmentRetries.isNotEmpty()) request.addOption("--fragment-retries", fragmentRetries) if(fragmentRetries.isNotEmpty()) request.addOption("--fragment-retries", fragmentRetries)
@ -992,8 +1000,6 @@ class InfoUtil(private val context: Context) {
request.addOption("--no-resize-buffer") request.addOption("--no-resize-buffer")
} }
request.addOption("--socket-timeout", 60)
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!! val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL) if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
@ -1017,15 +1023,15 @@ class InfoUtil(private val context: Context) {
request.addOption("--keep-fragments") request.addOption("--keep-fragments")
} }
if (sharedPreferences.getBoolean("no_part", false)){
request.addOption("--no-part")
}
val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true) val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true)
val thumbnailFormat = sharedPreferences.getString("thumbnail_format", "jpg") val thumbnailFormat = sharedPreferences.getString("thumbnail_format", "jpg")
var filenameTemplate = downloadItem.customFileNameTemplate var filenameTemplate = downloadItem.customFileNameTemplate
if(downloadItem.type != DownloadViewModel.Type.command){ if(downloadItem.type != DownloadViewModel.Type.command){
if (sharedPreferences.getBoolean("no_part", false)){
request.addOption("--no-part")
}
request.addOption("--trim-filenames", 254 - downDir.absolutePath.length) request.addOption("--trim-filenames", 254 - downDir.absolutePath.length)
if (downloadItem.SaveThumb) { if (downloadItem.SaveThumb) {
@ -1068,7 +1074,7 @@ class InfoUtil(private val context: Context) {
request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30))) request.addCommands(listOf("--replace-in-metadata", "video:uploader", ".+", downloadItem.author.take(30)))
} }
request.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$") request.addOption("--parse-metadata", "uploader:^(?P<uploader>.*?)(?:(?= - Topic)|$)")
if (embedMetadata){ if (embedMetadata){
request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s") request.addOption("--parse-metadata", "%(uploader,channel,creator,artist|null)s:%(uploader)s")
@ -1084,7 +1090,7 @@ class InfoUtil(private val context: Context) {
} }
} }
filenameTemplate = if (filenameTemplate.isBlank()){ filenameTemplate = if (filenameTemplate.isBlank()){
"%(section_title&{} |)s%(title)s" "%(section_title&{} |)s%(title).170B"
}else{ }else{
"%(section_title&{} |)s$filenameTemplate" "%(section_title&{} |)s$filenameTemplate"
} }
@ -1119,6 +1125,9 @@ class InfoUtil(private val context: Context) {
audioQualityId = "ba/b" audioQualityId = "ba/b"
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){ }else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
audioQualityId = "wa/w" audioQualityId = "wa/w"
}else if(audioQualityId.contains("kbps_ytdlnisgeneric")){
request.addOption("--match-filter", "abr<=${audioQualityId.split("kbps")[0]}")
audioQualityId = ""
} }
@ -1150,7 +1159,7 @@ class InfoUtil(private val context: Context) {
if(ext.isNotBlank()){ if(ext.isNotBlank()){
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){ if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(ext)){
request.addOption("--audio-format", ext) request.addOption("--audio-format", ext)
formatSorting.append(",ext::$ext") formatSorting.append(",aext:$ext")
} }
} }
@ -1164,14 +1173,14 @@ class InfoUtil(private val context: Context) {
if (embedMetadata){ if (embedMetadata){
request.addOption("--embed-metadata") request.addOption("--embed-metadata")
request.addOption("--parse-metadata", "artist:(?P<meta_album_artist>[^,]+)") request.addOption("--parse-metadata", "%(artist,uploader)s:^(?P<meta_album_artist>[^,]*)")
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist,uploader|)s:%(album_artist)s") request.addOption("--parse-metadata", "%(album_artist,meta_album_artist|)s:%(album_artist)s")
request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})") request.addOption("--parse-metadata", "description:(?:Released on: )(?P<dscrptn_year>\\d{4})")
request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s") request.addOption("--parse-metadata", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
if (downloadItem.playlistTitle.isNotEmpty()) { if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s") request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(track_number)s") request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(track_number)s")
} else { } else {
request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s")
@ -1239,6 +1248,8 @@ class InfoUtil(private val context: Context) {
} }
} }
} }
var acont = sharedPreferences.getString("audio_format", "")!!
if (acont == "Default") acont = ""
//format logic //format logic
var videoF = downloadItem.format.format_id var videoF = downloadItem.format.format_id
@ -1259,6 +1270,11 @@ class InfoUtil(private val context: Context) {
val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!! val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!!
if (downloadItem.videoPreferences.removeAudio) audioF = "" if (downloadItem.videoPreferences.removeAudio) audioF = ""
if(audioF.contains("kbps_ytdlnisgeneric")){
request.addOption("--match-filter", "abr<=${audioF.split("kbps")[0]}")
audioF = ""
}
val f = StringBuilder() val f = StringBuilder()
val preferredCodec = sharedPreferences.getString("video_codec", "") val preferredCodec = sharedPreferences.getString("video_codec", "")
@ -1371,7 +1387,8 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size") if (sharedPreferences.getBoolean("prefer_smaller_formats", false)) append(",+size")
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref") if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref") if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
if (cont.isNotBlank()) append(",ext:$cont:") if (cont.isNotBlank()) append(",vext:$cont")
if (acont.isNotBlank()) append(",aext:$acont")
if (this.isNotBlank()){ if (this.isNotBlank()){
request.addOption("-S", "+hasaud$this") request.addOption("-S", "+hasaud$this")
} }
@ -1381,14 +1398,7 @@ class InfoUtil(private val context: Context) {
request.addOption("-f", f.toString().replace("/$".toRegex(), "")) request.addOption("-f", f.toString().replace("/$".toRegex(), ""))
if (downloadItem.videoPreferences.writeSubs){ if (downloadItem.videoPreferences.writeSubs){
val subFormat = sharedPreferences.getString("sub_format", "srt")
request.addOption("--write-subs") request.addOption("--write-subs")
if(subFormat!!.isNotBlank()){
request.addOption("--sub-format", "${subFormat}/best")
request.addOption("--convert-subtitles", subFormat ?: "srt")
}
} }
if (downloadItem.videoPreferences.embedSubs) { if (downloadItem.videoPreferences.embedSubs) {
@ -1400,6 +1410,11 @@ class InfoUtil(private val context: Context) {
} }
if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs || downloadItem.videoPreferences.writeAutoSubs){ if (downloadItem.videoPreferences.embedSubs || downloadItem.videoPreferences.writeSubs || downloadItem.videoPreferences.writeAutoSubs){
val subFormat = sharedPreferences.getString("sub_format", "srt")
if(subFormat!!.isNotBlank()){
request.addOption("--sub-format", "${subFormat}/best")
request.addOption("--convert-subtitles", subFormat)
}
request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" }) request.addOption("--sub-langs", downloadItem.videoPreferences.subsLanguages.ifEmpty { "en.*,.*-orig" })
} }

View file

@ -26,6 +26,7 @@ import com.deniscerri.ytdl.receiver.CancelDownloadNotificationReceiver
import com.deniscerri.ytdl.receiver.CancelWorkReceiver import com.deniscerri.ytdl.receiver.CancelWorkReceiver
import com.deniscerri.ytdl.receiver.PauseDownloadNotificationReceiver import com.deniscerri.ytdl.receiver.PauseDownloadNotificationReceiver
import com.deniscerri.ytdl.receiver.ResumeActivity import com.deniscerri.ytdl.receiver.ResumeActivity
import com.deniscerri.ytdl.services.ProcessDownloadsInBackgroundService
import com.deniscerri.ytdl.util.Extensions.toBitmap import com.deniscerri.ytdl.util.Extensions.toBitmap
import java.io.File import java.io.File
@ -247,6 +248,8 @@ class NotificationUtil(var context: Context) {
else -> R.drawable.ic_launcher_foreground_large else -> R.drawable.ic_launcher_foreground_large
} }
val contentText = StringBuilder("$title")
val bitmap = iconType.toBitmap(context) val bitmap = iconType.toBitmap(context)
notificationBuilder notificationBuilder
.setContentTitle("${res.getString(R.string.downloaded)} $title") .setContentTitle("${res.getString(R.string.downloaded)} $title")
@ -255,15 +258,11 @@ class NotificationUtil(var context: Context) {
.setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString()) .setGroup(DOWNLOAD_FINISHED_NOTIFICATION_ID.toString())
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
.setContentText(title) .setContentText(title)
.setStyle(NotificationCompat.BigTextStyle()
.bigText("""
$title
${filepath?.joinToString("\n")}
""".trimIndent()))
.setPriority(NotificationCompat.PRIORITY_MAX) .setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions() .clearActions()
if (filepath != null){ if (filepath != null){
contentText.append("\n\n"+ filepath.joinToString("\n"))
try{ try{
val uris = filepath.mapNotNull { val uris = filepath.mapNotNull {
runCatching { runCatching {
@ -317,6 +316,7 @@ class NotificationUtil(var context: Context) {
notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent) notificationBuilder.addAction(0, res.getString(R.string.share), shareNotificationPendingIntent)
}catch (_: Exception){} }catch (_: Exception){}
} }
notificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(contentText.toString().trimIndent()))
notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build()) notificationManager.notify(DOWNLOAD_FINISHED_NOTIFICATION_ID + id.toInt(), notificationBuilder.build())
} }
@ -646,6 +646,16 @@ class NotificationUtil(var context: Context) {
fun createProcessingDownloads() : Notification{ fun createProcessingDownloads() : Notification{
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID) val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
val intent = Intent(context, ProcessDownloadsInBackgroundService::class.java)
intent.putExtra("binding", true)
intent.putExtra("cancel", true)
val cancelNotificationPendingIntent = PendingIntent.getService(
context,
System.currentTimeMillis().toInt(),
intent,
PendingIntent.FLAG_IMMUTABLE
)
notificationBuilder notificationBuilder
.setContentTitle(resources.getString(R.string.processing)) .setContentTitle(resources.getString(R.string.processing))
.setSmallIcon(R.drawable.ic_app_icon) .setSmallIcon(R.drawable.ic_app_icon)
@ -658,6 +668,7 @@ class NotificationUtil(var context: Context) {
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.clearActions() .clearActions()
.addAction(0, resources.getString(R.string.cancel), cancelNotificationPendingIntent)
return notificationBuilder.build() return notificationBuilder.build()
} }

View file

@ -112,7 +112,7 @@ object UiUtil {
else if (formatNote == "worst") formatNote = context.getString(R.string.worst_quality) else if (formatNote == "worst") formatNote = context.getString(R.string.worst_quality)
var container = chosenFormat.container var container = chosenFormat.container
if (container == "Default") container = context.getString(R.string.defaultValue) if (container == "Default" || container.isBlank()) container = context.getString(R.string.defaultValue)
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase() formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase() formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
@ -580,14 +580,14 @@ object UiUtil {
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE) bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet) bottomSheet.setContentView(R.layout.history_item_details_bottom_sheet)
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply { bottomSheet.findViewById<TextView>(R.id.bottom_sheet_title)?.apply {
text = item.title.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } text = item.title.ifEmpty { item.playlistTitle.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } }
setOnLongClickListener{ setOnLongClickListener{
showFullTextDialog(context, text.toString(), context.getString(R.string.title)) showFullTextDialog(context, text.toString(), context.getString(R.string.title))
true true
} }
} }
bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply { bottomSheet.findViewById<TextView>(R.id.bottom_sheet_author)?.apply {
text = item.author.ifEmpty { "`${context.getString(R.string.defaultValue)}`" } text = item.author
setOnLongClickListener{ setOnLongClickListener{
showFullTextDialog(context, text.toString(), context.getString(R.string.author)) showFullTextDialog(context, text.toString(), context.getString(R.string.author))
true true

View file

@ -23,9 +23,14 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
import com.deniscerri.ytdl.BuildConfig import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.R import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.GithubRelease import com.deniscerri.ytdl.database.models.GithubRelease
import com.deniscerri.ytdl.ui.adapter.ChangelogAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
@ -166,71 +171,25 @@ class UpdateUtil(var context: Context) {
fun showChangeLog(activity: Activity){ fun showChangeLog(activity: Activity){
runCatching { runCatching {
val scrollView = ScrollView(activity)
val linearLayout = LinearLayout(activity)
linearLayout.orientation = LinearLayout.VERTICAL
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams.setMargins(10, 10, 10, 0)
scrollView.layoutParams = layoutParams
scrollView.addView(linearLayout)
val releases = getGithubReleases() val releases = getGithubReleases()
val view = activity.layoutInflater.inflate(R.layout.generic_list, null)
val adapter = ChangelogAdapter(activity)
val recycler = view.findViewById<RecyclerView>(R.id.download_recyclerview)
recycler.layoutManager = LinearLayoutManager(activity)
recycler.adapter = adapter
adapter.submitList(releases)
recycler.enableFastScroll()
val changeLogDialog = MaterialAlertDialogBuilder(context) val changeLogDialog = MaterialAlertDialogBuilder(context)
.setTitle(activity.getString(R.string.changelog)) .setTitle(activity.getString(R.string.changelog))
.setView(scrollView) .setView(view)
.setIcon(R.drawable.ic_chapters) .setIcon(R.drawable.ic_chapters)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> } .setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
changeLogDialog.show() changeLogDialog.show()
} }
CoroutineScope(Dispatchers.IO).launch {
releases.forEach {
(activity.layoutInflater.inflate(R.layout.changelog_item, null) as MaterialCardView).apply {
this.layoutParams = layoutParams
findViewById<TextView>(R.id.version).text = it.tag_name
findViewById<TextView>(R.id.date).text = SimpleDateFormat(
DateFormat.getBestDateTimePattern(
Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(it.published_at.time)
val mdText = findViewById<TextView>(R.id.content)
val mw = Markwon.builder(context).usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
startActivity(context, browserIntent, Bundle())
}
}
}).build()
mw.setMarkdown(mdText, it.body)
val assetGroup = findViewById<ChipGroup>(R.id.assets)
it.assets.forEachIndexed { idx, c ->
val tmp = activity.layoutInflater.inflate(R.layout.filter_chip, assetGroup, false) as Chip
tmp.isCheckable = false
tmp.layoutParams = layoutParams
tmp.text = c.name
tmp.id = idx
tmp.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(c.browser_download_url))
startActivity(context, browserIntent, Bundle())
}
assetGroup!!.addView(tmp)
}
withContext(Dispatchers.Main){
linearLayout.addView(this@apply)
}
}
}
}
}.onFailure { }.onFailure {
if (it.message != null){ if (it.message != null){
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {

View file

@ -84,8 +84,7 @@ class DownloadWorker(
.createPendingIntent() .createPendingIntent()
val workNotif = notificationUtil.createDefaultWorkerNotification() val workNotif = notificationUtil.createDefaultWorkerNotification()
val workNotifID = Random.nextInt(900000000, 1000000000) val foregroundInfo = ForegroundInfo(1000000000, workNotif)
val foregroundInfo = ForegroundInfo(workNotifID, workNotif)
setForegroundAsync(foregroundInfo) setForegroundAsync(foregroundInfo)
queuedItems.collect { items -> queuedItems.collect { items ->
@ -265,7 +264,7 @@ class DownloadWorker(
val historyItem = HistoryItem(0, val historyItem = HistoryItem(0,
downloadItem.url, downloadItem.url,
downloadItem.title, downloadItem.title.ifEmpty { downloadItem.playlistTitle },
downloadItem.author, downloadItem.author,
downloadItem.duration, downloadItem.duration,
downloadItem.thumb, downloadItem.thumb,
@ -287,18 +286,18 @@ class DownloadWorker(
downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources downloadItem.id, downloadItem.title, downloadItem.type, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, resources
) )
if (wasQuickDownloaded && createResultItem){ // if (wasQuickDownloaded && createResultItem){
runCatching { // runCatching {
eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id)) // eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
runBlocking { // runBlocking {
infoUtil.getFromYTDL(downloadItem.url).forEach { res -> // infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
if (res != null) { // if (res != null) {
resultDao.insert(res) // resultDao.insert(res)
} // }
} // }
} // }
} // }
} // }
dao.delete(downloadItem.id) dao.delete(downloadItem.id)

View file

@ -58,7 +58,7 @@ class ObserveSourceWorker(
.filter { result -> .filter { result ->
//if first run and get new items only is preferred then dont get anything on first run //if first run and get new items only is preferred then dont get anything on first run
if (item.getOnlyNewUploads && item.runCount == 0){ if (item.getOnlyNewUploads && item.runCount == 0){
item.alreadyProcessedLinks.add(result.url) item.ignoredLinks.add(result.url)
false false
}else{ }else{
true true
@ -66,6 +66,9 @@ class ObserveSourceWorker(
} }
.filter { result -> .filter { result ->
val history = historyRepo.getAllByURL(result.url) val history = historyRepo.getAllByURL(result.url)
!item.ignoredLinks.contains(result.url) &&
if (item.retryMissingDownloads){ if (item.retryMissingDownloads){
//all items that are not present in history //all items that are not present in history
history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } } history.none { hi -> hi.downloadPath.any { path -> FileUtil.exists(path) } }

View file

@ -20,15 +20,14 @@
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:scrollbars="none" android:scrollbars="none"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/providers" android:id="@+id/providers"
android:layout_margin="10dp" android:layout_marginTop="5dp"
app:singleSelection="true" app:singleSelection="true"
app:chipSpacingVertical="-10dp" app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true" /> app:singleLine="true" />

View file

@ -20,15 +20,14 @@
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:scrollbars="none" android:scrollbars="none"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/providers" android:id="@+id/providers"
android:layout_margin="10dp" android:layout_marginTop="5dp"
app:singleSelection="true" app:singleSelection="true"
app:chipSpacingVertical="-10dp" app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true" /> app:singleLine="true" />

View file

@ -20,15 +20,14 @@
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:scrollbars="none" android:scrollbars="none"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/providers" android:id="@+id/providers"
android:layout_margin="10dp" android:layout_marginTop="5dp"
app:singleSelection="true" app:singleSelection="true"
app:chipSpacingVertical="-10dp" app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true" /> app:singleLine="true" />

View file

@ -20,15 +20,14 @@
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:scrollbars="none" android:scrollbars="none"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/providers" android:id="@+id/providers"
android:layout_margin="10dp" android:layout_marginTop="5dp"
app:singleSelection="true" app:singleSelection="true"
app:chipSpacingVertical="-10dp" app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true" /> app:singleLine="true" />

View file

@ -18,8 +18,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:cardCornerRadius="20dp" app:cardCornerRadius="20dp"
app:cardElevation="10dp" app:cardElevation="5dp"
app:cardMaxElevation="12dp"
android:checkable="true" android:checkable="true"
app:strokeWidth="0dp" app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"

View file

@ -34,7 +34,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="15dp" android:layout_marginEnd="15dp"
android:maxLines="2" android:maxLines="3"
android:ellipsize="end" android:ellipsize="end"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -0,0 +1,67 @@
<?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:padding="10dp"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/current"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginVertical="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintHeight_max="300dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/currentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textIsSelectable="true"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/current">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -191,7 +191,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingHorizontal="10dp" android:paddingHorizontal="10dp"
android:text=":" android:text="-"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />

View file

@ -2,12 +2,12 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
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"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview" android:id="@+id/download_recyclerview"
@ -16,14 +16,16 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:scrollbars="none" android:scrollbars="none"
tools:listitem="@layout/active_download_card"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
android:paddingBottom="100dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:layout_marginBottom="100dp"
app:layout_constraintTop_toBottomOf="@+id/pause_resume"> app:layout_constraintTop_toBottomOf="@+id/pause_resume">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
</LinearLayout> </androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/pause" android:id="@+id/pause"
@ -50,6 +52,7 @@
<include layout="@layout/no_results" <include layout="@layout/no_results"
android:layout_height="match_parent"
android:visibility="gone" android:visibility="gone"
android:layout_width="match_parent"/> android:layout_width="match_parent"/>

View file

@ -66,6 +66,7 @@
<AutoCompleteTextView <AutoCompleteTextView
android:id="@+id/container_textview" android:id="@+id/container_textview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:textDirection="locale"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="none" android:inputType="none"
app:simpleItems="@array/audio_containers" /> app:simpleItems="@array/audio_containers" />

View file

@ -20,15 +20,14 @@
<HorizontalScrollView <HorizontalScrollView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:scrollbars="none" android:scrollbars="none"
android:paddingHorizontal="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/providers" android:id="@+id/providers"
android:layout_margin="10dp" android:layout_marginTop="5dp"
app:singleSelection="true" app:singleSelection="true"
app:chipSpacingVertical="-10dp" app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true" /> app:singleLine="true" />

View file

@ -117,7 +117,7 @@
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/info" android:id="@+id/info"
style="@style/Widget.Material3.Chip.Assist" style="@style/Widget.Material3.Chip.Assist.Elevated"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="10dp" android:layout_margin="10dp"

View file

@ -392,8 +392,7 @@
<string name="minute">دقيقة</string> <string name="minute">دقيقة</string>
<string name="second">ثانية</string> <string name="second">ثانية</string>
<string name="milliseconds">ملّي ثانية</string> <string name="milliseconds">ملّي ثانية</string>
<string name="format_importance_audio">ترتيب الأولوية في التنسيق (الصوت)</string> <string name="format_importance">ترتيب الأولوية في التنسيق</string>
<string name="format_importance_video">ترتيب الأولوية في التنسيق (الفيديو)</string>
<string name="no_audio">فيديو بدون صوت</string> <string name="no_audio">فيديو بدون صوت</string>
<string name="download_archive_folder">مجلد أرشيف التنزيل</string> <string name="download_archive_folder">مجلد أرشيف التنزيل</string>
<string name="location">المسار</string> <string name="location">المسار</string>

View file

@ -390,8 +390,7 @@
<string name="clear_scheduled">Planlaşdırılanı Təmizlə</string> <string name="clear_scheduled">Planlaşdırılanı Təmizlə</string>
<string name="minute">Dəqiqə</string> <string name="minute">Dəqiqə</string>
<string name="second">Saniyə</string> <string name="second">Saniyə</string>
<string name="format_importance_audio">Format Vacibliyi Sırası (Audio)</string> <string name="format_importance">Format Vacibliyi Sırası</string>
<string name="format_importance_video">Format Vacibliyi Sırası (Video)</string>
<string name="format_importance_note">Format elementinin vacibliyini tənzimləyin. Bu düzülüş yalnız tətbiq yükləmə kartındakı formatları əldə etdikdə və formatı avtomatik seçdikdə istifadə olunacaq!</string> <string name="format_importance_note">Format elementinin vacibliyini tənzimləyin. Bu düzülüş yalnız tətbiq yükləmə kartındakı formatları əldə etdikdə və formatı avtomatik seçdikdə istifadə olunacaq!</string>
<string name="no_audio">Səs olmayan video</string> <string name="no_audio">Səs olmayan video</string>
<string name="download_archive_folder">Yükləmə Arxivi Qovluğu</string> <string name="download_archive_folder">Yükləmə Arxivi Qovluğu</string>

View file

@ -394,8 +394,7 @@
<string name="no_audio">ڤیدیۆی بێ دەنگ</string> <string name="no_audio">ڤیدیۆی بێ دەنگ</string>
<string name="download_archive_folder">فۆڵدەری ئەرشیف دابەزێنە</string> <string name="download_archive_folder">فۆڵدەری ئەرشیف دابەزێنە</string>
<string name="process_downloads_background">"هێشتا دابەزاندنەکان لە بارکردندان. بەردەوام بە لە پرۆسێسکردنیان لە پاشبنەمادا و دەست بکە بە دابەزاندن؟ "</string> <string name="process_downloads_background">"هێشتا دابەزاندنەکان لە بارکردندان. بەردەوام بە لە پرۆسێسکردنیان لە پاشبنەمادا و دەست بکە بە دابەزاندن؟ "</string>
<string name="format_importance_audio">ڕێکخستنی گرنگی فۆرمات (دەنگ)</string> <string name="format_importance">ڕێکخستنی گرنگی فۆرمات</string>
<string name="format_importance_video">ڕێکخستنی گرنگی فۆرمات (ڤیدیۆ)</string>
<string name="format_importance_note">گرنگی توخمەکانی فۆرمات ڕێکبخە. ئەم فەرمانە تەنها کاتێک بەکاردەهێنرێت کە ئەپەکە فۆرماتەکانی ناو کارتی دابەزاندنەکە دەهێنێت و فۆرماتەکە بە شێوەیەکی ئۆتۆماتیکی هەڵدەبژێرێت!</string> <string name="format_importance_note">گرنگی توخمەکانی فۆرمات ڕێکبخە. ئەم فەرمانە تەنها کاتێک بەکاردەهێنرێت کە ئەپەکە فۆرماتەکانی ناو کارتی دابەزاندنەکە دەهێنێت و فۆرماتەکە بە شێوەیەکی ئۆتۆماتیکی هەڵدەبژێرێت!</string>
<string name="location">شوێن</string> <string name="location">شوێن</string>
<string name="enable_alarm_permission">پێویستە مۆڵەتی SCHEDULE_EXACT_ALARM لە ڕێکخستنەکانی ئەپەکەدا چالاک بکەیت.</string> <string name="enable_alarm_permission">پێویستە مۆڵەتی SCHEDULE_EXACT_ALARM لە ڕێکخستنەکانی ئەپەکەدا چالاک بکەیت.</string>

View file

@ -391,10 +391,9 @@
<string name="enable_alarm_permission">V nastavení aplikace je třeba povolit oprávnění SCHEDULE_EXACT_ALARM.</string> <string name="enable_alarm_permission">V nastavení aplikace je třeba povolit oprávnění SCHEDULE_EXACT_ALARM.</string>
<string name="process_downloads_background">"Stahované položky se stále načítají. Pokračovat v jejich zpracování na pozadí a zahájit stahování? "</string> <string name="process_downloads_background">"Stahované položky se stále načítají. Pokračovat v jejich zpracování na pozadí a zahájit stahování? "</string>
<string name="minute">Minuta</string> <string name="minute">Minuta</string>
<string name="format_importance_video">Pořadí důležitosti formátu (Video)</string> <string name="format_importance">Pořadí důležitosti formátu</string>
<string name="second">Sekunda</string> <string name="second">Sekunda</string>
<string name="milliseconds">Milisekundy</string> <string name="milliseconds">Milisekundy</string>
<string name="format_importance_audio">Pořadí důležitosti formátu (Audio)</string>
<string name="location">Umístění</string> <string name="location">Umístění</string>
<string name="format_importance_note">Upravte důležitost prvku formátu. Toto pořadí se použije pouze tehdy, když aplikace načte formáty na kartě stahování a automaticky vybere formát!</string> <string name="format_importance_note">Upravte důležitost prvku formátu. Toto pořadí se použije pouze tehdy, když aplikace načte formáty na kartě stahování a automaticky vybere formát!</string>
<string name="no_audio">Video bez zvuku</string> <string name="no_audio">Video bez zvuku</string>

View file

@ -390,8 +390,7 @@
<string name="clear_scheduled">Borrar programado</string> <string name="clear_scheduled">Borrar programado</string>
<string name="second">Segundo</string> <string name="second">Segundo</string>
<string name="milliseconds">Milisegundos</string> <string name="milliseconds">Milisegundos</string>
<string name="format_importance_audio">Orden de importancia del formato (audio)</string> <string name="format_importance">Orden de importancia del formato</string>
<string name="format_importance_video">Orden de importancia del formato (vídeo)</string>
<string name="format_importance_note">Ajustar la importancia de los elementos de formato. ¡Este orden solo se utilizará cuando la aplicación busque los formatos en la tarjeta de descarga y los seleccione automáticamente!</string> <string name="format_importance_note">Ajustar la importancia de los elementos de formato. ¡Este orden solo se utilizará cuando la aplicación busque los formatos en la tarjeta de descarga y los seleccione automáticamente!</string>
<string name="no_audio">Vídeo sin sonido</string> <string name="no_audio">Vídeo sin sonido</string>
<string name="download_archive_folder">Descargar carpeta de archivos</string> <string name="download_archive_folder">Descargar carpeta de archivos</string>

View file

@ -391,8 +391,7 @@
<string name="second">सेकेंड</string> <string name="second">सेकेंड</string>
<string name="minute">मिनट</string> <string name="minute">मिनट</string>
<string name="milliseconds">मिलीसेकेंड</string> <string name="milliseconds">मिलीसेकेंड</string>
<string name="format_importance_audio">प्रारूप महत्व क्रम (ऑडियो)</string> <string name="format_importance">प्रारूप महत्व क्रम</string>
<string name="format_importance_video">प्रारूप महत्व क्रम (वीडियो)</string>
<string name="format_importance_note">प्रारूप तत्व महत्व को समायोजित करें। इस आदेश का उपयोग केवल तभी किया जाएगा जब ऐप डाउनलोड कार्ड में प्रारूप लाएगा और प्रारूप का स्वतः चयन करेगा!</string> <string name="format_importance_note">प्रारूप तत्व महत्व को समायोजित करें। इस आदेश का उपयोग केवल तभी किया जाएगा जब ऐप डाउनलोड कार्ड में प्रारूप लाएगा और प्रारूप का स्वतः चयन करेगा!</string>
<string name="no_audio">बिना ऑडियो वाला वीडियो</string> <string name="no_audio">बिना ऑडियो वाला वीडियो</string>
<string name="download_archive_folder">संग्रह फ़ोल्डर डाउनलोड करें</string> <string name="download_archive_folder">संग्रह फ़ोल्डर डाउनलोड करें</string>

View file

@ -377,8 +377,7 @@
<string name="display_over_apps">Megjelenés alkalmazások felett</string> <string name="display_over_apps">Megjelenés alkalmazások felett</string>
<string name="display_over_apps_summary">A letöltés kártya mindig legfelül lesz. Kapcsold be, ha a jelenlegi alkalmazást bezárja, hogy a kártya megjelenjen</string> <string name="display_over_apps_summary">A letöltés kártya mindig legfelül lesz. Kapcsold be, ha a jelenlegi alkalmazást bezárja, hogy a kártya megjelenjen</string>
<string name="hour">Óra</string> <string name="hour">Óra</string>
<string name="format_importance_audio">Formátum fontossági sorrend (Hang)</string> <string name="format_importance">Formátum fontossági sorrend</string>
<string name="format_importance_video">Formátum fontossági sorrend (Videó)</string>
<string name="no_audio">Videó, hang nélkül</string> <string name="no_audio">Videó, hang nélkül</string>
<string name="download_archive_folder">Letöltési archívum mappa</string> <string name="download_archive_folder">Letöltési archívum mappa</string>
<string name="enable_alarm_permission">Engedélyezned kell a SCHEDULE_EXACT_ALARM engedélyt a beállításokban.</string> <string name="enable_alarm_permission">Engedélyezned kell a SCHEDULE_EXACT_ALARM engedélyt a beállításokban.</string>

View file

@ -390,13 +390,12 @@
<string name="move_top">上に移動</string> <string name="move_top">上に移動</string>
<string name="milliseconds">ミリ秒</string> <string name="milliseconds">ミリ秒</string>
<string name="download_archive_folder">ダウンロード記録のフォルダ</string> <string name="download_archive_folder">ダウンロード記録のフォルダ</string>
<string name="format_importance_video">形式の重要度の順序 (動画)</string> <string name="format_importance">形式の重要度の順序</string>
<string name="process_downloads_background">"ダウンロードはまだ読み込み中です。処理をバックグラウンドで続け、ダウンロードを開始しますか? "</string> <string name="process_downloads_background">ダウンロードはまだ読み込み中です。処理をバックグラウンドで続け、ダウンロードを開始しますか?</string>
<string name="minute"></string> <string name="minute"></string>
<string name="second"></string> <string name="second"></string>
<string name="no_audio">音声なし動画</string> <string name="no_audio">音声なし動画</string>
<string name="location">場所</string> <string name="location">場所</string>
<string name="format_importance_audio">形式の重要度の順序 (音声)</string>
<string name="enable_alarm_permission">アプリの設定から SCHEDULE_EXACT_ALARM の権限を有効にする必要があります。</string> <string name="enable_alarm_permission">アプリの設定から SCHEDULE_EXACT_ALARM の権限を有効にする必要があります。</string>
<string name="format_importance_note">形式の要素の重要度を調整します。この順序はアプリがダウンロードカード内で形式を取得し、形式を自動選択する際にみ使われます!</string> <string name="format_importance_note">形式の要素の重要度を調整します。この順序はアプリがダウンロードカード内で形式を取得し、形式を自動選択する際にみ使われます!</string>
</resources> </resources>

View file

@ -331,8 +331,7 @@
<string name="update_ytdl_stable">Stabiele versie van yt-dlp</string> <string name="update_ytdl_stable">Stabiele versie van yt-dlp</string>
<string name="minute">Minuut</string> <string name="minute">Minuut</string>
<string name="second">Seconde</string> <string name="second">Seconde</string>
<string name="format_importance_audio">Prioriteit van audio­formaten</string> <string name="format_importance">Prioriteit formaten</string>
<string name="format_importance_video">Prioriteit van video­formaten</string>
<string name="no_audio">Video zonder audio</string> <string name="no_audio">Video zonder audio</string>
<string name="location">Locatie</string> <string name="location">Locatie</string>
<string name="get_new_uploads">Alleen nieuwe uploads ophalen</string> <string name="get_new_uploads">Alleen nieuwe uploads ophalen</string>

View file

@ -391,8 +391,7 @@
<string name="minute">ਮਿੰਟ</string> <string name="minute">ਮਿੰਟ</string>
<string name="second">ਸਕਿੰਟ</string> <string name="second">ਸਕਿੰਟ</string>
<string name="milliseconds">ਮਿਲੀਸਕਿੰਟ</string> <string name="milliseconds">ਮਿਲੀਸਕਿੰਟ</string>
<string name="format_importance_audio">ਫਾਰਮੈਟ ਮਹੱਤਵ ਕ੍ਰਮ (ਆਡੀਓ)</string> <string name="format_importance">ਫਾਰਮੈਟ ਮਹੱਤਵ ਕ੍ਰਮ</string>
<string name="format_importance_video">ਫਾਰਮੈਟ ਮਹੱਤਵ ਕ੍ਰਮ (ਵੀਡੀਓ)</string>
<string name="no_audio">ਬਿਨਾਂ ਆਡੀਓ ਦੇ ਵੀਡੀਓ</string> <string name="no_audio">ਬਿਨਾਂ ਆਡੀਓ ਦੇ ਵੀਡੀਓ</string>
<string name="download_archive_folder">ਡਾਊਨਲੋਡ ਲਈ ਆਰਕਾਈਵ ਫੋਲਡਰ</string> <string name="download_archive_folder">ਡਾਊਨਲੋਡ ਲਈ ਆਰਕਾਈਵ ਫੋਲਡਰ</string>
<string name="format_importance_note">ਫਾਰਮੈਟ ਤੱਤਾਂ ਦੀ ਮਹੱਤਤਾ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਕ੍ਰਮ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਵਰਤਿਆ ਜਾਵੇਗਾ ਜਦੋਂ ਐਪ ਡਾਉਨਲੋਡ ਕਾਰਡ ਵਿੱਚ ਫਾਰਮੈਟਾਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਅਤੇ ਫਾਰਮੈਟ ਨੂੰ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੁਣਦਾ ਹੈ!</string> <string name="format_importance_note">ਫਾਰਮੈਟ ਤੱਤਾਂ ਦੀ ਮਹੱਤਤਾ ਨੂੰ ਵਿਵਸਥਿਤ ਕਰੋ। ਇਹ ਕ੍ਰਮ ਸਿਰਫ਼ ਉਦੋਂ ਹੀ ਵਰਤਿਆ ਜਾਵੇਗਾ ਜਦੋਂ ਐਪ ਡਾਉਨਲੋਡ ਕਾਰਡ ਵਿੱਚ ਫਾਰਮੈਟਾਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਦਾ ਹੈ ਅਤੇ ਫਾਰਮੈਟ ਨੂੰ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੁਣਦਾ ਹੈ!</string>

View file

@ -388,14 +388,13 @@
<string name="clear_scheduled">Limpar downloads agendados</string> <string name="clear_scheduled">Limpar downloads agendados</string>
<string name="copy_url">Copiar URL</string> <string name="copy_url">Copiar URL</string>
<string name="move_top">Mover para cima</string> <string name="move_top">Mover para cima</string>
<string name="format_importance_video">Ordem de importância do formato (vídeo)</string> <string name="format_importance">Ordem de importância do formato</string>
<string name="minute">Minuto(s)</string> <string name="minute">Minuto(s)</string>
<string name="second">Segundo(s)</string> <string name="second">Segundo(s)</string>
<string name="milliseconds">Milissegundo(s)</string> <string name="milliseconds">Milissegundo(s)</string>
<string name="location">Localização</string> <string name="location">Localização</string>
<string name="download_archive_folder">Pasta para arquivamento de downloads</string> <string name="download_archive_folder">Pasta para arquivamento de downloads</string>
<string name="no_audio">Vídeo sem áudio</string> <string name="no_audio">Vídeo sem áudio</string>
<string name="format_importance_audio">Ordem de importância do formato (áudio)</string>
<string name="enable_alarm_permission">Você precisa habilitar a permissão SCHEDULE_EXACT_ALARM nas configurações do aplicativo.</string> <string name="enable_alarm_permission">Você precisa habilitar a permissão SCHEDULE_EXACT_ALARM nas configurações do aplicativo.</string>
<string name="format_importance_note">Ajuste a importância do elemento de formato. Essa ordem só será usada quando o aplicativo buscar os formatos no cartão de download e selecionar automaticamente o formato!</string> <string name="format_importance_note">Ajuste a importância do elemento de formato. Essa ordem só será usada quando o aplicativo buscar os formatos no cartão de download e selecionar automaticamente o formato!</string>
<string name="process_downloads_background">"Os downloads ainda estão sendo carregados. Continuar processando-os em segundo plano e iniciar o download? "</string> <string name="process_downloads_background">"Os downloads ainda estão sendo carregados. Continuar processando-os em segundo plano e iniciar o download? "</string>

View file

@ -388,11 +388,10 @@
<string name="move_bottom">Mover para baixo</string> <string name="move_bottom">Mover para baixo</string>
<string name="scheduled">Agendado</string> <string name="scheduled">Agendado</string>
<string name="clear_scheduled">Limpar agendamento</string> <string name="clear_scheduled">Limpar agendamento</string>
<string name="format_importance_video">Ordem preferencial do formato (vídeo)</string> <string name="format_importance">Ordem preferencial do formato</string>
<string name="minute">Minuto</string> <string name="minute">Minuto</string>
<string name="second">Segundo</string> <string name="second">Segundo</string>
<string name="milliseconds">Milissegundos</string> <string name="milliseconds">Milissegundos</string>
<string name="format_importance_audio">Ordem preferencial do formato (áudio)</string>
<string name="no_audio">Vídeo sem áudio</string> <string name="no_audio">Vídeo sem áudio</string>
<string name="location">Localização</string> <string name="location">Localização</string>
</resources> </resources>

View file

@ -388,13 +388,12 @@
<string name="move_bottom">Двигайтесь вниз</string> <string name="move_bottom">Двигайтесь вниз</string>
<string name="scheduled">Запланированное</string> <string name="scheduled">Запланированное</string>
<string name="clear_scheduled">Очистить Запланированное</string> <string name="clear_scheduled">Очистить Запланированное</string>
<string name="format_importance_video">Порядок важности формата (видео)</string>
<string name="download_archive_folder">Скачать папку архива</string> <string name="download_archive_folder">Скачать папку архива</string>
<string name="location">Расположение</string> <string name="location">Расположение</string>
<string name="minute">Минута</string> <string name="minute">Минута</string>
<string name="second">Секунда</string> <string name="second">Секунда</string>
<string name="milliseconds">Миллисекунды</string> <string name="milliseconds">Миллисекунды</string>
<string name="format_importance_audio">Порядок важности формата (аудио)</string> <string name="format_importance">Порядок важности формата</string>
<string name="format_importance_note">Отрегулируйте важность элемента формата. Этот порядок будет использоваться только в том случае, если приложение извлекает форматы из карты загрузки и автоматически выбирает формат!</string> <string name="format_importance_note">Отрегулируйте важность элемента формата. Этот порядок будет использоваться только в том случае, если приложение извлекает форматы из карты загрузки и автоматически выбирает формат!</string>
<string name="no_audio">Видео без звука</string> <string name="no_audio">Видео без звука</string>
<string name="enable_alarm_permission">Вам необходимо включить разрешение SCHEDULE_EXACT_ALARM в настройках приложения.</string> <string name="enable_alarm_permission">Вам необходимо включить разрешение SCHEDULE_EXACT_ALARM в настройках приложения.</string>

View file

@ -394,8 +394,7 @@
<string name="process_downloads_background">"Položky na stiahnutie sa stále načítavajú. Pokračovať v ich spracovaní na pozadí a začať sťahovanie? "</string> <string name="process_downloads_background">"Položky na stiahnutie sa stále načítavajú. Pokračovať v ich spracovaní na pozadí a začať sťahovanie? "</string>
<string name="minute">Minúta</string> <string name="minute">Minúta</string>
<string name="second">Sekunda</string> <string name="second">Sekunda</string>
<string name="format_importance_audio">Poradie dôležitosti formátu (Audio)</string> <string name="format_importance">Poradie dôležitosti formátu</string>
<string name="format_importance_video">Poradie dôležitosti formátu (Video)</string>
<string name="no_audio">Video bez zvuku</string> <string name="no_audio">Video bez zvuku</string>
<string name="location">Umiestnenie</string> <string name="location">Umiestnenie</string>
<string name="enable_alarm_permission">V nastaveniach aplikácie musíte povoliť oprávnenie SCHEDULE_EXACT_ALARM.</string> <string name="enable_alarm_permission">V nastaveniach aplikácie musíte povoliť oprávnenie SCHEDULE_EXACT_ALARM.</string>

View file

@ -390,8 +390,7 @@
<string name="minute">Minuta</string> <string name="minute">Minuta</string>
<string name="second">Sekonda</string> <string name="second">Sekonda</string>
<string name="milliseconds">Milisekonda</string> <string name="milliseconds">Milisekonda</string>
<string name="format_importance_audio">Rradha e rëndësisë së Formateve (Audio)</string> <string name="format_importance">Rradha e rëndësisë së Formateve</string>
<string name="format_importance_video">Rradha e rëndësisë së Formateve (Video)</string>
<string name="format_importance_note">Rregullo rëndësinë e elementit të formatit. Kjo porosi do të përdoret vetëm kur aplikacioni merr formatet në kartën e shkarkimit dhe zgjedh automatikisht formatin!</string> <string name="format_importance_note">Rregullo rëndësinë e elementit të formatit. Kjo porosi do të përdoret vetëm kur aplikacioni merr formatet në kartën e shkarkimit dhe zgjedh automatikisht formatin!</string>
<string name="no_audio">Video pa audio</string> <string name="no_audio">Video pa audio</string>
<string name="download_archive_folder">Skedari i arkivës së shkarkimeve</string> <string name="download_archive_folder">Skedari i arkivës së shkarkimeve</string>

View file

@ -391,8 +391,7 @@
<string name="second">Секунд</string> <string name="second">Секунд</string>
<string name="milliseconds">Милисекунде</string> <string name="milliseconds">Милисекунде</string>
<string name="minute">Минут</string> <string name="minute">Минут</string>
<string name="format_importance_audio">Редослед важности формата (аудио)</string> <string name="format_importance">Редослед важности формата</string>
<string name="format_importance_video">Редослед важности формата (видео)</string>
<string name="format_importance_note">Подесите важност елемента формата. Овај налог ће се користити само када апликација преузме формате на картици за преузимање и аутоматски изабере формат!</string> <string name="format_importance_note">Подесите важност елемента формата. Овај налог ће се користити само када апликација преузме формате на картици за преузимање и аутоматски изабере формат!</string>
<string name="no_audio">Видео без звука</string> <string name="no_audio">Видео без звука</string>
<string name="process_downloads_background">"Преузимања се и даље учитавају. Наставити с обрађивањем преузимања у позадини и започети преузимање? "</string> <string name="process_downloads_background">"Преузимања се и даље учитавају. Наставити с обрађивањем преузимања у позадини и започети преузимање? "</string>

View file

@ -260,7 +260,6 @@
<string name="move_bottom">கீழே செல்லுங்கள்</string> <string name="move_bottom">கீழே செல்லுங்கள்</string>
<string name="scheduled">திட்டமிடப்பட்ட</string> <string name="scheduled">திட்டமிடப்பட்ட</string>
<string name="clear_scheduled">தெளிவான திட்டமிடப்பட்டுள்ளது</string> <string name="clear_scheduled">தெளிவான திட்டமிடப்பட்டுள்ளது</string>
<string name="format_importance_video">வடிவமைப்பு முக்கியத்துவம் வரிசை (வீடியோ)</string>
<string name="you_are_in_latest_version">அண்மைக் கால பதிப்பைப் பயன்படுத்துதல்</string> <string name="you_are_in_latest_version">அண்மைக் கால பதிப்பைப் பயன்படுத்துதல்</string>
<string name="ytdl_already_updating">புதிய பதிப்பை நிறுவுதல்</string> <string name="ytdl_already_updating">புதிய பதிப்பை நிறுவுதல்</string>
<string name="ytdl_updating_started">புதிய பதிப்பைப் பதிவிறக்குகிறது</string> <string name="ytdl_updating_started">புதிய பதிப்பைப் பதிவிறக்குகிறது</string>
@ -310,7 +309,7 @@
<string name="minute">நிமிடங்கள்</string> <string name="minute">நிமிடங்கள்</string>
<string name="second">இரண்டாவது</string> <string name="second">இரண்டாவது</string>
<string name="milliseconds">மில்லி விநாடிகள்</string> <string name="milliseconds">மில்லி விநாடிகள்</string>
<string name="format_importance_audio">வடிவமைப்பு முக்கியத்துவம் வரிசை (ஆடியோ)</string> <string name="format_importance">வடிவமைப்பு முக்கியத்துவம் வரிசை</string>
<string name="no_download_fragments">துண்டுகளாக பதிவிறக்கம் செய்ய வேண்டாம்</string> <string name="no_download_fragments">துண்டுகளாக பதிவிறக்கம் செய்ய வேண்டாம்</string>
<string name="no_download_fragments_summary">.Part கோப்புகளைப் பயன்படுத்த வேண்டாம் மற்றும் வெளியீட்டு கோப்பில் நேரடியாக எழுதுங்கள்</string> <string name="no_download_fragments_summary">.Part கோப்புகளைப் பயன்படுத்த வேண்டாம் மற்றும் வெளியீட்டு கோப்பில் நேரடியாக எழுதுங்கள்</string>
<string name="concurrent_fragments">ஒரே நேரத்தில் துண்டுகள்</string> <string name="concurrent_fragments">ஒரே நேரத்தில் துண்டுகள்</string>

View file

@ -394,8 +394,7 @@
<string name="minute">Dakika</string> <string name="minute">Dakika</string>
<string name="second">Saniye</string> <string name="second">Saniye</string>
<string name="milliseconds">Milisaniye</string> <string name="milliseconds">Milisaniye</string>
<string name="format_importance_audio">Biçim Önem Sırası (Ses)</string> <string name="format_importance">Biçim Önem Sırası</string>
<string name="format_importance_video">Biçim Önem Sırası (Video)</string>
<string name="format_importance_note">Biçim öğesinin önemini ayarlayın. Bu sıra yalnızca uygulama indirme kartındaki formatları alıp formatı otomatik seçtiğinde kullanılacaktır!</string> <string name="format_importance_note">Biçim öğesinin önemini ayarlayın. Bu sıra yalnızca uygulama indirme kartındaki formatları alıp formatı otomatik seçtiğinde kullanılacaktır!</string>
<string name="no_audio">Video ama ses olmadan</string> <string name="no_audio">Video ama ses olmadan</string>
<string name="download_archive_folder">Arşiv Klasörünü İndir</string> <string name="download_archive_folder">Arşiv Klasörünü İndir</string>

View file

@ -390,12 +390,11 @@
<string name="clear_scheduled">Очистити Заплановане</string> <string name="clear_scheduled">Очистити Заплановане</string>
<string name="minute">Хвилина</string> <string name="minute">Хвилина</string>
<string name="second">Секунда</string> <string name="second">Секунда</string>
<string name="format_importance_audio">Порядок важливості формату (аудіо)</string>
<string name="format_importance_note">Налаштуйте важливість елемента формату. Цей порядок буде використовуватися лише в тому випадку, якщо програма витягує формати з карти завантаження та автоматично вибирає формат!</string> <string name="format_importance_note">Налаштуйте важливість елемента формату. Цей порядок буде використовуватися лише в тому випадку, якщо програма витягує формати з карти завантаження та автоматично вибирає формат!</string>
<string name="no_audio">Відео без звуку</string> <string name="no_audio">Відео без звуку</string>
<string name="process_downloads_background">"Завантаження все ще завантажуються. Продовжити їх обробку у фоновому режимі та розпочати завантаження? "</string> <string name="process_downloads_background">"Завантаження все ще завантажуються. Продовжити їх обробку у фоновому режимі та розпочати завантаження? "</string>
<string name="milliseconds">Мілісекунди</string> <string name="milliseconds">Мілісекунди</string>
<string name="format_importance_video">Порядок важливості формату (відео)</string> <string name="format_importance">Порядок важливості формату</string>
<string name="download_archive_folder">Завантажити папку архіву</string> <string name="download_archive_folder">Завантажити папку архіву</string>
<string name="location">Розташування</string> <string name="location">Розташування</string>
<string name="enable_alarm_permission">Вам потрібно включити роздільну здатність SCHEDULE_EXACT_ALARM в установках програми.</string> <string name="enable_alarm_permission">Вам потрібно включити роздільну здатність SCHEDULE_EXACT_ALARM в установках програми.</string>

View file

@ -389,8 +389,7 @@
<string name="subtitles">字幕</string> <string name="subtitles">字幕</string>
<string name="move_bottom">移到底部</string> <string name="move_bottom">移到底部</string>
<string name="milliseconds">毫秒</string> <string name="milliseconds">毫秒</string>
<string name="format_importance_audio">格式重要性顺序(音频)</string> <string name="format_importance">格式重要性顺序</string>
<string name="format_importance_video">格式重要性顺序(视频)</string>
<string name="format_importance_note">调整格式元素重要性。此顺序仅当应用在下载卡片中获取格式并自动选择格式时才会用到!</string> <string name="format_importance_note">调整格式元素重要性。此顺序仅当应用在下载卡片中获取格式并自动选择格式时才会用到!</string>
<string name="no_audio">没有声音的视频</string> <string name="no_audio">没有声音的视频</string>
<string name="download_archive_folder">下载档案文件夹</string> <string name="download_archive_folder">下载档案文件夹</string>

View file

@ -390,10 +390,9 @@
<string name="clear_scheduled">清除排程</string> <string name="clear_scheduled">清除排程</string>
<string name="minute">分鐘</string> <string name="minute">分鐘</string>
<string name="milliseconds">毫秒</string> <string name="milliseconds">毫秒</string>
<string name="format_importance_video">格式重要性順序(影片)</string>
<string name="process_downloads_background">"下載仍在載入中。繼續在背景處理它們並開始下載? "</string> <string name="process_downloads_background">"下載仍在載入中。繼續在背景處理它們並開始下載? "</string>
<string name="second"></string> <string name="second"></string>
<string name="format_importance_audio">格式重要性順序(音訊)</string> <string name="format_importance">格式重要性順序</string>
<string name="download_archive_folder">下載存檔資料夾</string> <string name="download_archive_folder">下載存檔資料夾</string>
<string name="enable_alarm_permission">您需要在應用程式設定中啟用 SCHEDULE_EXACT_ALARM 權限。</string> <string name="enable_alarm_permission">您需要在應用程式設定中啟用 SCHEDULE_EXACT_ALARM 權限。</string>
<string name="location">位置</string> <string name="location">位置</string>

View file

@ -11,7 +11,7 @@
</string-array> </string-array>
<string-array name="audio_containers_values"> <string-array name="audio_containers_values">
<item>Default</item> <item></item>
<item>mp3</item> <item>mp3</item>
<item>m4a</item> <item>m4a</item>
<item>aac</item> <item>aac</item>
@ -32,7 +32,7 @@
</string-array> </string-array>
<string-array name="video_containers_values"> <string-array name="video_containers_values">
<item>Default</item> <item></item>
<item>mp4</item> <item>mp4</item>
<item>webm</item> <item>webm</item>
<item>mkv</item> <item>mkv</item>
@ -67,11 +67,21 @@
<string-array name="audio_formats"> <string-array name="audio_formats">
<item>best</item> <item>best</item>
<item>192kbps</item>
<item>160kbps</item>
<item>128kbps</item>
<item>96kbps</item>
<item>64kbps</item>
<item>worst</item> <item>worst</item>
</string-array> </string-array>
<string-array name="audio_formats_values"> <string-array name="audio_formats_values">
<item>ba</item> <item>ba</item>
<item>192kbps_ytdlnisgeneric</item>
<item>160kbps_ytdlnisgeneric</item>
<item>128kbps_ytdlnisgeneric</item>
<item>96kbps_ytdlnisgeneric</item>
<item>64kbps_ytdlnisgeneric</item>
<item>wa</item> <item>wa</item>
</string-array> </string-array>
@ -799,6 +809,7 @@
<item>@string/settings</item> <item>@string/settings</item>
<item>@string/downloads</item> <item>@string/downloads</item>
<item>@string/in_queue</item> <item>@string/in_queue</item>
<item>@string/scheduled</item>
<item>@string/cancelled</item> <item>@string/cancelled</item>
<item>@string/errored</item> <item>@string/errored</item>
<item>@string/saved</item> <item>@string/saved</item>
@ -813,6 +824,7 @@
<item>settings</item> <item>settings</item>
<item>downloads</item> <item>downloads</item>
<item>queued</item> <item>queued</item>
<item>scheduled</item>
<item>cancelled</item> <item>cancelled</item>
<item>errored</item> <item>errored</item>
<item>saved</item> <item>saved</item>

View file

@ -394,8 +394,7 @@
<string name="minute">Minute</string> <string name="minute">Minute</string>
<string name="second">Second</string> <string name="second">Second</string>
<string name="milliseconds">Milliseconds</string> <string name="milliseconds">Milliseconds</string>
<string name="format_importance_audio">Format Importance Order (Audio)</string> <string name="format_importance">Format Importance Order</string>
<string name="format_importance_video">Format Importance Order (Video)</string>
<string name="format_importance_note">Adjust format element importance. This order will only be used when the app fetches the formats in the download card and auto-selects the format!</string> <string name="format_importance_note">Adjust format element importance. This order will only be used when the app fetches the formats in the download card and auto-selects the format!</string>
<string name="no_audio">Video with no Audio</string> <string name="no_audio">Video with no Audio</string>
<string name="download_archive_folder">Download Archive Folder</string> <string name="download_archive_folder">Download Archive Folder</string>

View file

@ -59,14 +59,14 @@
android:icon="@drawable/ic_textformat" android:icon="@drawable/ic_textformat"
app:key="file_name_template" app:key="file_name_template"
app:useSimpleSummaryProvider="true" app:useSimpleSummaryProvider="true"
app:defaultValue="%(uploader)30B - %(title)120B" app:defaultValue="%(uploader).30B - %(title).170B"
app:title="@string/file_name_template" /> app:title="@string/file_name_template" />
<Preference <Preference
android:icon="@drawable/ic_textformat" android:icon="@drawable/ic_textformat"
app:key="file_name_template_audio" app:key="file_name_template_audio"
app:useSimpleSummaryProvider="true" app:useSimpleSummaryProvider="true"
app:defaultValue="%(uploader)30B - %(title)120B" app:defaultValue="%(uploader).30B - %(title).170B"
app:title="@string/file_name_template" /> app:title="@string/file_name_template" />
<SwitchPreferenceCompat <SwitchPreferenceCompat

View file

@ -163,7 +163,7 @@
<PreferenceCategory android:title="@string/format"> <PreferenceCategory android:title="@string/format">
<ListPreference <ListPreference
android:defaultValue="Default" android:defaultValue=""
android:entries="@array/audio_containers" android:entries="@array/audio_containers"
android:entryValues="@array/audio_containers_values" android:entryValues="@array/audio_containers_values"
android:icon="@drawable/ic_code" android:icon="@drawable/ic_code"
@ -172,7 +172,7 @@
app:title="@string/audio_format" /> app:title="@string/audio_format" />
<ListPreference <ListPreference
android:defaultValue="Default" android:defaultValue=""
android:entries="@array/video_containers" android:entries="@array/video_containers"
android:entryValues="@array/video_containers_values" android:entryValues="@array/video_containers_values"
android:icon="@drawable/ic_code" android:icon="@drawable/ic_code"
@ -258,12 +258,12 @@
<Preference <Preference
android:icon="@drawable/ic_format" android:icon="@drawable/ic_format"
android:key="format_importance_audio" android:key="format_importance_audio"
app:title="@string/format_importance_audio" /> app:title="@string/format_importance" />
<Preference <Preference
android:icon="@drawable/ic_format" android:icon="@drawable/ic_format"
android:key="format_importance_video" android:key="format_importance_video"
app:title="@string/format_importance_video" /> app:title="@string/format_importance" />
</PreferenceCategory> </PreferenceCategory>

View file

@ -0,0 +1,71 @@
## What's New
- Made finished notification icon color to red
- Added download path in the history item details sheet
- Fixed Download On Schedule feature. Switched back to AlarmManager as its more accurate than WorkManager
- Removed move to top and to bottom in the scheduled tab. Code cleanup
- Fixed a huge issue when you press cancel to the quick download item before the data isn't fully fetched, it would go back to being queued after the data fetching is finally done
- moved force keyframes at cuts in the general category in processing settings
- showed the number when you select all items in the context menu bar
- fixed app going always to the search bar when you return to home if you launched the search shortcut from the app menu
- Fixed app not hiding update formats button after updating formats
- Fixed app not passing ba/b in format logic when using generic best audio format
- Fixed meta track parse metadata
- Fixed app not cancelling downloads in some cases
- Fixed huge issue when using multiple download card and it wouldnt keep user defined changes
- Fixed app not removing download notification when cancelling download
- Fixed some errors in arabic interface when selecting playlist items
- Limited characters in cut interface textboxes to just numbers colons and periods
- When user had a defined preferred format id and the user selects a generic 'best' format the app just accounted that format id and didnt consider the preferred video resolution as a fallback
- Fixed when trying to schedule a video download and setting it to download as audio as well, the audio is put in queued and not in scheduled
- Fixed app crashing when trying to tap adjust download in the errored notification, in case the user removed the download beforehand
- Added ability to modify the download item in the queue. But to prevent the app from starting the download prematurely, it is moved to the saved category and after you hit ok it will be requeued. If you cancel the edit card, the item will stay in saved
- Fixed app reseting user defined changes in the download card like the download path when it finally updates data
- Fixed app crashing when trying to open a download that has no duration in it. Now its disabled
- Previously the app would change the state of downloads to Paused, whether it is activepaused or queuedpaused. Since the app now simply pauses all downloads, there is no need to do such transformations especially when you could have many items in the queue. Now simply the app cancels the ytdlp instances and the download worker and stores a state whether the downloads are paused or not
- Fixed album playlist track not being saved #482
- Fixed retries and fragment retries settings not applying
- Fixed container in RTL not showing correctly in video tab in the download card
- Fixed bad spacing in RTL for search engines
- Fixed app crashing when you multiple select cancelled downloads for redownload
- Removed the ability of the app to rename extractors in the history tab. Now they are raw from yt-dlp
- Changelog was too slow and buggy, so now its on a recyclerview ultra fast
- Fixed the case when you pasted multiple links in the home section and only one item would be registered
- Fixed observe sources not downloading new uploads when checked as such
- On select playlist items the end textbox was not purely numeric. fixed
- Fixed sub format conversion not applying when embedding subtitles
- added scheduled as a backup category in teh settings
- added ability to show cookie details
- fixed app crash in some cases when u copied multiple url and tapped the FAB in home screen
- removed excessive file permissions for API 30 and above
## Format aspect importance
Now in the settings you can order around which element based on your preferences like codec, container, quality, preferred format id should be prioritised when the app automatically chooses a format for you. The app uses a weigh system to sort formats based on preference.
- fixed app crashing when swipe to delete a scheduled item
- fixed app not modifying time in a scheduled item
- added network constraint for observe sources worker
- added reconfigure button in errored notification
- fixed app not hiding the "Link you copied" after clicking on it
- fixed app crashing when trying to toast in the end (#469)
- added alternate urls for instagram,facebook,and reddit as supported sites
- fixed app not escaping double quotes in filenames
- fixed container text alignment for arabic interface
- rearranged video quality order in descending order in the settings
## Background processing of items for download
Someone tested to download a 4000 long playlist in the app. First of all it couldnt even load them in the multiple download card. In those extreme cases the usual recommendation was to just turn quick download on, and consider the whole playlist as a single item and download.
But still, if a madman wants the ability to modify the list, now the app can handle it and will show the download card. Since it will take a while until all items all processed and converted into download items, a progress bar is added.
Still you are able to hit download and schedule button early and let the app continue on its own by using the default configurations of the download items. So its fast like quick download but still separates all items into separate downloads.
- fixed app using -a "" on observe sources command type downloads
- added milliseconds when trying to cut a download
- added ability to set the folder location of your download archive path. The file has to be named as download_archive.txt
- added feature to number each chapter when using split by chapters
- slight changes to downloads already exist dialog
- fixed app not considering m.youtube links
- other small fixes