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 versionMinor = 7
def versionPatch = 6
def versionBuild = 1 // bump for dogfood builds, public betas, etc.
def isBeta = true
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def isBeta = false
def versionExt = isBeta ? ".${versionBuild}-beta" : ""
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:excludeFromRecents="true"
android:exported="true"
android:taskAffinity=""
android:launchMode="singleInstance"
android:theme="@style/Theme.BottomSheet">
<intent-filter>

View file

@ -41,7 +41,7 @@ import com.deniscerri.ytdl.database.models.TerminalItem
TerminalItem::class,
ObserveSourcesItem::class
],
version = 16,
version = 17,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@ -57,7 +57,8 @@ import com.deniscerri.ytdl.database.models.TerminalItem
AutoMigration (from = 12, to = 13),
// AutoMigration (from = 13, to = 14) MANUALLY HANDLED
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(){

View file

@ -83,6 +83,9 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")
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")
fun getQueuedDownloadsListIDs() : List<Long>
@ -123,6 +126,15 @@ interface DownloadDao {
@Query("SELECT * FROM downloads WHERE id IN (:ids)")
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")
fun getLastDownloadId(): Long

View file

@ -52,6 +52,9 @@ interface ResultDao {
@Query("SELECT * FROM results WHERE url=:url LIMIT 1")
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")
fun getResultsBetweenTwoItems(item1: Long, item2: Long) : List<ResultItem>

View file

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

View file

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

View file

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

View file

@ -125,6 +125,10 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
resultDao.update(item)
}
fun getItemByID(id: Long) : ResultItem? {
return resultDao.getResultByID(id)
}
fun getItemByURL(url: String): ResultItem? {
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()
if (downloadItem.title.isEmpty()) downloadItem.title = info.title
if (downloadItem.author.isEmpty()) downloadItem.author = info.author
if (downloadItem.playlistTitle.isEmpty() && downloadItem.playlistTitle != "ytdlnis-Search") downloadItem.playlistTitle = info.playlistTitle
downloadItem.duration = info.duration
downloadItem.website = info.website
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info.thumb

View file

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

View file

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

View file

@ -3,8 +3,10 @@ package com.deniscerri.ytdl.services
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.Build
import android.os.IBinder
import androidx.core.content.IntentCompat
import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdl.database.DBManager
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.ResultItem
@ -18,8 +20,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ProcessDownloadsInBackgroundService : Service() {
@ -46,7 +50,25 @@ class ProcessDownloadsInBackgroundService : Service() {
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
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)
startForeground(System.currentTimeMillis().toInt(), notificationUtil.createProcessingDownloads())
@ -98,7 +120,7 @@ class ProcessDownloadsInBackgroundService : Service() {
}
}
}else {
repository.getAllItemsByIDs(processingItemIDs).apply {
repository.getProcessingItemsBetweenIDs(processingItemIDs.first(), processingItemIDs.last()).apply {
this.chunked(100).map {
if (timeInMillis > 0){
this.forEach { d ->

View file

@ -26,18 +26,20 @@ open class BaseActivity : AppCompatActivity() {
fun askPermissions() {
val permissions = arrayListOf<String>()
if (!checkFilePermission()) {
if (Build.VERSION.SDK_INT >= 33){
permissions.add(Manifest.permission.READ_MEDIA_AUDIO)
permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
if (Build.VERSION.SDK_INT >= 34){
permissions.add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
}
}else{
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
val hasFilePerm = if (Build.VERSION.SDK_INT >= 30) {
true
}else {
(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) &&
(ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED)
}
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 (!checkNotificationPermission()){
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)
private fun checkNotificationPermission(): Boolean {
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
searchSuggestionsAdapter?.getList()?.apply {
if (this.first().type == SearchSuggestionType.CLIPBOARD){
val newList = this.toMutableList().drop(1)
searchSuggestionsAdapter?.submitList(newList)
if (this.isNotEmpty()) {
if (this.first().type == SearchSuggestionType.CLIPBOARD){
val newList = this.toMutableList().drop(1)
searchSuggestionsAdapter?.submitList(newList)
}
}
}
}

View file

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

View file

@ -78,11 +78,11 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
// 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) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title.ifEmpty { item.url }
itemTitle.text = title
//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.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
@ -71,14 +72,15 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration
duration.isVisible = item.duration != "-1"
// 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) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title.ifEmpty { item.url }
itemTitle.text = title
//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)
duration.text = item.duration
duration.isVisible = item.duration != "-1"
// 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) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title.ifEmpty { item.url }
itemTitle.text = title
//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.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
@ -83,14 +84,15 @@ class ScheduledDownloadAdapter(onItemClickListener: OnItemClickListener, activit
val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration
duration.isVisible = item.duration != "-1"
// 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) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title.ifEmpty { item.url }
itemTitle.text = title
//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
suggestedChips.removeAllViews()
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.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.isCheckedIconVisible = false
@ -514,9 +514,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private
//replace existing chip to enable click events
val idx = selectedCuts.indexOfFirst { c -> c.contains(it.title) }
if (idx > -1){
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text == it.title }
chipGroup.removeView(chipForDeletion)
createChapterChip(it, idx)
val chipForDeletion = chipGroup.children.firstOrNull { cc -> (cc as Chip).text.contains(it.title) }
chipForDeletion?.apply {
chipGroup.removeView(chipForDeletion)
createChapterChip(it, idx)
}
}
}
}

View file

@ -100,8 +100,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var currentDownloadIDs: List<Long>
private var processingItemsCount : Int = 0
private lateinit var jobData: DownloadViewModel.ProcessingItemsJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -174,58 +172,57 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
shimmerTitle = view.findViewById(R.id.shimmer_loading_title)
shimmerSubtitle = view.findViewById(R.id.shimmer_loading_subtitle)
downloadViewModel.processingItemsJob.observe(this) {
it?.apply {
lifecycleScope.launch {
jobData = it
processingItemsCount = it.processingDownloadItemIDs.size
count.text = "${processingItemsCount} ${getString(R.string.selected)}"
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.processingItems.collectLatest {
val loading = it
toggleLoadingShimmerTitle(loading)
bottomAppBar.menu.children.forEach { m -> m.isEnabled = !loading }
if (!loading){
continueInBackgroundSnackBar.dismiss()
}
}
}
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{
fun initSchedule(processingFinished: Boolean) {
UiUtil.showDatePicker(parentFragmentManager) { cal ->
@ -237,9 +234,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
downloadViewModel.deleteAllWithID(currentDownloadIDs)
}
jobData.job?.cancel(CancellationException())
jobData.job = null
downloadProcessDownloadsInBackground(jobData, processingFinished)
getProcessingItemsData()?.apply {
this.job?.cancel(CancellationException())
this.job = null
downloadProcessDownloadsInBackground(this, processingFinished)
}
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()
@ -268,9 +267,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
downloadViewModel.deleteAllWithID(currentDownloadIDs)
}
jobData.job?.cancel(CancellationException())
jobData.job = null
downloadProcessDownloadsInBackground(jobData, processingFinished)
getProcessingItemsData()?.apply {
this.job?.cancel(CancellationException())
this.job = null
downloadProcessDownloadsInBackground(this, processingFinished)
}
dismiss()
}
}
@ -295,10 +296,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch{
withContext(Dispatchers.IO){
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()
}
}
@ -309,13 +312,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val formatListener = object : OnFormatClickListener {
override fun onFormatClick(selectedFormats: List<FormatTuple>) {
CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingFormat(jobData.processingDownloadItemIDs, selectedFormats)
downloadViewModel.updateProcessingFormat(getProcessingDownloadItemIDs(), selectedFormats)
}
}
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingAllFormats(jobData.processingDownloadItemIDs, allFormats)
downloadViewModel.updateProcessingAllFormats(getProcessingDownloadItemIDs(), allFormats)
}
}
@ -323,10 +326,12 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
override fun onContinueOnBackground() {
requireActivity().lifecycleScope.launch {
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()
}
}
@ -364,7 +369,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
audio!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.audio)
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.audio)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
@ -376,7 +381,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
video!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.video)
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.video)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
@ -388,7 +393,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
command!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(jobData.processingDownloadItemIDs, DownloadViewModel.Type.command)
downloadViewModel.updateProcessingType(getProcessingDownloadItemIDs(), DownloadViewModel.Type.command)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[1].icon?.alpha = 255
@ -412,7 +417,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.format -> {
lifecycleScope.launch {
val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs,)
downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
}
if (!res.first){
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 {
downloadViewModel.updateProcessingCommandFormat(jobData.processingDownloadItemIDs, format)
downloadViewModel.updateProcessingCommandFormat(getProcessingDownloadItemIDs(), format)
}
}
}else{
val items = withContext(Dispatchers.IO){
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
}
val flatFormatCollection = items.map { it.allFormats }.flatten()
val commonFormats = withContext(Dispatchers.IO){
@ -460,7 +465,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.more -> {
lifecycleScope.launch {
val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType(jobData.processingDownloadItemIDs)
downloadViewModel.checkIfAllProcessingItemsHaveSameType(getProcessingDownloadItemIDs())
}
if (!res.first) {
Toast.makeText(
@ -481,7 +486,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
val items = withContext(Dispatchers.IO){
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
}
UiUtil.configureAudio(
@ -562,7 +567,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding,padding,padding,padding)
val items = withContext(Dispatchers.IO){
downloadViewModel.getAllByIDs(jobData.processingDownloadItemIDs)
downloadViewModel.getAllByIDs(getProcessingDownloadItemIDs())
}
UiUtil.configureVideo(
@ -700,7 +705,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
}
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())
@ -832,9 +837,11 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
}
override fun onDismiss(dialog: DialogInterface) {
if (jobData.job != null){
jobData.job?.cancel(CancellationException())
downloadViewModel.deleteAllWithID(jobData.processingDownloadItemIDs)
getProcessingItemsData()?.apply {
if (this.job != null){
this.job?.cancel(CancellationException())
downloadViewModel.deleteAllWithID(this.processingDownloadItemIDs)
}
}
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){
Intent(requireActivity(), ProcessDownloadsInBackgroundService::class.java).also { intent ->
intent.putExtra("itemType", jobData.originItemType)

View file

@ -523,6 +523,11 @@ class ObserveSourcesBottomSheetDialog : BottomSheetDialogFragment() {
mutableListOf()
}else{
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.os.Build
import android.os.Bundle
import android.text.method.DigitsKeyListener
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MenuItem
@ -106,7 +107,9 @@ class SelectPlaylistItemsDialog : BottomSheetDialogFragment(), PlaylistAdapter.O
count.text = "0 ${resources.getString(R.string.selected)}"
fromTextInput = view.findViewById(R.id.from_textinput)
fromTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
toTextInput = view.findViewById(R.id.to_textinput)
toTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
fromTextInput.editText!!.doAfterTextChanged { _text ->

View file

@ -67,7 +67,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
): View? {
fragmentView = inflater.inflate(R.layout.generic_list, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
return fragmentView
}
@ -251,24 +251,20 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
true
}
R.id.redownload -> {
lifecycleScope.launch {
val selectedObjects = 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 selectedObjects = runBlocking {
getSelectedIDs()
}
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 {
actionMode?.finish()
}

View file

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

View file

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

View file

@ -414,7 +414,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
val w = websites[i]
if (w == "null" || w.isEmpty()) continue
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.setOnClickListener {
Log.e(TAG, tmp.isChecked.toString())

View file

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

View file

@ -15,7 +15,9 @@ import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
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.viewmodel.CookieViewModel
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.UiUtil
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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
@ -114,7 +118,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
private fun initChips() {
val new = view?.findViewById<Chip>(R.id.newCookie)
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 {
val item = withContext(Dispatchers.IO){
cookiesViewModel.getByURL(url)
}
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.cookies))
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
val text = if (url.isNullOrBlank()) "https://" else url
val layout = layoutInflater.inflate(R.layout.cookie_details, null)
val editText = layout.findViewById<EditText>(R.id.url_edittext)
layout.findViewById<TextInputLayout>(R.id.url_textinput).hint = "URL"
val text = item?.url ?: "https://"
editText.setText(text)
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 {
builder.setNeutralButton(
@ -194,7 +201,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
}
builder.setPositiveButton(
getString(R.string.get_cookies)
if (item == null) getString(R.string.get_cookies) else getString(R.string.update)
) { dialog: DialogInterface?, which: Int ->
val myIntent = Intent(requireContext(), WebViewActivity::class.java)
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) {
showDialog(cookie.url)
showDialog(cookie)
}
override fun onSelected(cookie: CookieItem) {

View file

@ -99,7 +99,6 @@ class DownloadLogListFragment : Fragment(), DownloadLogsAdapter.OnItemClickListe
topAppBar.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId
if (itemId == R.id.remove_logs) {
throw Exception("asffffffffff")
try{
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
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?.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?.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 {
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))
"downloads" -> json.add("downloads", backupHistory())
"queued" -> json.add("queued", backupQueuedDownloads() )
"scheduled" -> json.add("scheduled", backupScheduledDownloads() )
"cancelled" -> json.add("cancelled", backupCancelledDownloads() )
"errored" -> json.add("errored", backupErroredDownloads() )
"saved" -> json.add("saved", backupSavedDownloads() )
@ -279,6 +280,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
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 {
runCatching {
val items = withContext(Dispatchers.IO) {
@ -493,6 +508,25 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
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
kotlin.runCatching {
if(json.has("cancelled")){

View file

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

View file

@ -648,7 +648,7 @@ class InfoUtil(private val context: Context) {
for (result in results) {
if (result.isNullOrBlank()) continue
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
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 playlistURL: String? = ""
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,
url,
title,
@ -977,8 +985,8 @@ class InfoUtil(private val context: Context) {
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
val retries = sharedPreferences.getString("--retries", "")!!
val fragmentRetries = sharedPreferences.getString("--fragment_retries", "")!!
val retries = sharedPreferences.getString("retries", "")!!
val fragmentRetries = sharedPreferences.getString("fragment_retries", "")!!
if(retries.isNotEmpty()) request.addOption("--retries", retries)
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("--socket-timeout", 60)
val sponsorblockURL = sharedPreferences.getString("sponsorblock_url", "")!!
if (sponsorblockURL.isNotBlank()) request.addOption("--sponsorblock-api", sponsorblockURL)
@ -1017,15 +1023,15 @@ class InfoUtil(private val context: Context) {
request.addOption("--keep-fragments")
}
if (sharedPreferences.getBoolean("no_part", false)){
request.addOption("--no-part")
}
val embedMetadata = sharedPreferences.getBoolean("embed_metadata", true)
val thumbnailFormat = sharedPreferences.getString("thumbnail_format", "jpg")
var filenameTemplate = downloadItem.customFileNameTemplate
if(downloadItem.type != DownloadViewModel.Type.command){
if (sharedPreferences.getBoolean("no_part", false)){
request.addOption("--no-part")
}
request.addOption("--trim-filenames", 254 - downDir.absolutePath.length)
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.addOption("--parse-metadata", "uploader:(?P<uploader>.+)(?: - Topic)$")
request.addOption("--parse-metadata", "uploader:^(?P<uploader>.*?)(?:(?= - Topic)|$)")
if (embedMetadata){
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()){
"%(section_title&{} |)s%(title)s"
"%(section_title&{} |)s%(title).170B"
}else{
"%(section_title&{} |)s$filenameTemplate"
}
@ -1119,6 +1125,9 @@ class InfoUtil(private val context: Context) {
audioQualityId = "ba/b"
}else if (listOf(context.getString(R.string.worst_quality), "wa", "worst").contains(audioQualityId)){
audioQualityId = "wa/w"
}else if(audioQualityId.contains("kbps_ytdlnisgeneric")){
request.addOption("--match-filter", "abr<=${audioQualityId.split("kbps")[0]}")
audioQualityId = ""
}
@ -1150,7 +1159,7 @@ class InfoUtil(private val context: Context) {
if(ext.isNotBlank()){
if(!ext.matches("(webm)|(Default)|(${context.getString(R.string.defaultValue)})".toRegex()) && supportedContainers.contains(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){
request.addOption("--embed-metadata")
request.addOption("--parse-metadata", "artist:(?P<meta_album_artist>[^,]+)")
request.addOption("--parse-metadata", "%(album_artist,meta_album_artist,uploader|)s:%(album_artist)s")
request.addOption("--parse-metadata", "%(artist,uploader)s:^(?P<meta_album_artist>[^,]*)")
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", "%(dscrptn_year,release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
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")
} else {
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
var videoF = downloadItem.format.format_id
@ -1259,6 +1270,11 @@ class InfoUtil(private val context: Context) {
val preferredAudioLanguage = sharedPreferences.getString("audio_language", "")!!
if (downloadItem.videoPreferences.removeAudio) audioF = ""
if(audioF.contains("kbps_ytdlnisgeneric")){
request.addOption("--match-filter", "abr<=${audioF.split("kbps")[0]}")
audioF = ""
}
val f = StringBuilder()
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 (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
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()){
request.addOption("-S", "+hasaud$this")
}
@ -1381,14 +1398,7 @@ class InfoUtil(private val context: Context) {
request.addOption("-f", f.toString().replace("/$".toRegex(), ""))
if (downloadItem.videoPreferences.writeSubs){
val subFormat = sharedPreferences.getString("sub_format", "srt")
request.addOption("--write-subs")
if(subFormat!!.isNotBlank()){
request.addOption("--sub-format", "${subFormat}/best")
request.addOption("--convert-subtitles", subFormat ?: "srt")
}
}
if (downloadItem.videoPreferences.embedSubs) {
@ -1400,6 +1410,11 @@ class InfoUtil(private val context: Context) {
}
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" })
}

View file

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

View file

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

View file

@ -23,9 +23,14 @@ import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
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.R
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.chip.Chip
import com.google.android.material.chip.ChipGroup
@ -166,71 +171,25 @@ class UpdateUtil(var context: Context) {
fun showChangeLog(activity: Activity){
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 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)
.setTitle(activity.getString(R.string.changelog))
.setView(scrollView)
.setView(view)
.setIcon(R.drawable.ic_chapters)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
Handler(Looper.getMainLooper()).post {
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 {
if (it.message != null){
Handler(Looper.getMainLooper()).post {

View file

@ -84,8 +84,7 @@ class DownloadWorker(
.createPendingIntent()
val workNotif = notificationUtil.createDefaultWorkerNotification()
val workNotifID = Random.nextInt(900000000, 1000000000)
val foregroundInfo = ForegroundInfo(workNotifID, workNotif)
val foregroundInfo = ForegroundInfo(1000000000, workNotif)
setForegroundAsync(foregroundInfo)
queuedItems.collect { items ->
@ -265,7 +264,7 @@ class DownloadWorker(
val historyItem = HistoryItem(0,
downloadItem.url,
downloadItem.title,
downloadItem.title.ifEmpty { downloadItem.playlistTitle },
downloadItem.author,
downloadItem.duration,
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
)
if (wasQuickDownloaded && createResultItem){
runCatching {
eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
runBlocking {
infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
if (res != null) {
resultDao.insert(res)
}
}
}
}
}
// if (wasQuickDownloaded && createResultItem){
// runCatching {
// eventBus.post(WorkerProgress(100, "Creating Result Items", downloadItem.id))
// runBlocking {
// infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
// if (res != null) {
// resultDao.insert(res)
// }
// }
// }
// }
// }
dao.delete(downloadItem.id)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="2"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
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_gravity="center_vertical"
android:paddingHorizontal="10dp"
android:text=":"
android:text="-"
android:textSize="20sp"
android:textStyle="bold" />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -390,8 +390,7 @@
<string name="clear_scheduled">Planlaşdırılanı Təmizlə</string>
<string name="minute">Dəqiqə</string>
<string name="second">Saniyə</string>
<string name="format_importance_audio">Format Vacibliyi Sırası (Audio)</string>
<string name="format_importance_video">Format Vacibliyi Sırası (Video)</string>
<string name="format_importance">Format Vacibliyi Sırası</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="download_archive_folder">Yükləmə Arxivi Qovluğu</string>

View file

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

View file

@ -390,8 +390,7 @@
<string name="clear_scheduled">Borrar programado</string>
<string name="second">Segundo</string>
<string name="milliseconds">Milisegundos</string>
<string name="format_importance_audio">Orden de importancia del formato (audio)</string>
<string name="format_importance_video">Orden de importancia del formato (vídeo)</string>
<string name="format_importance">Orden de importancia del formato</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="download_archive_folder">Descargar carpeta de archivos</string>

View file

@ -391,8 +391,7 @@
<string name="second">सेकेंड</string>
<string name="minute">मिनट</string>
<string name="milliseconds">मिलीसेकेंड</string>
<string name="format_importance_audio">प्रारूप महत्व क्रम (ऑडियो)</string>
<string name="format_importance_video">प्रारूप महत्व क्रम (वीडियो)</string>
<string name="format_importance">प्रारूप महत्व क्रम</string>
<string name="format_importance_note">प्रारूप तत्व महत्व को समायोजित करें। इस आदेश का उपयोग केवल तभी किया जाएगा जब ऐप डाउनलोड कार्ड में प्रारूप लाएगा और प्रारूप का स्वतः चयन करेगा!</string>
<string name="no_audio">बिना ऑडियो वाला वीडियो</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_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="format_importance_audio">Formátum fontossági sorrend (Hang)</string>
<string name="format_importance_video">Formátum fontossági sorrend (Videó)</string>
<string name="format_importance">Formátum fontossági sorrend</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="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="milliseconds">ミリ秒</string>
<string name="download_archive_folder">ダウンロード記録のフォルダ</string>
<string name="format_importance_video">形式の重要度の順序 (動画)</string>
<string name="process_downloads_background">"ダウンロードはまだ読み込み中です。処理をバックグラウンドで続け、ダウンロードを開始しますか? "</string>
<string name="format_importance">形式の重要度の順序</string>
<string name="process_downloads_background">ダウンロードはまだ読み込み中です。処理をバックグラウンドで続け、ダウンロードを開始しますか?</string>
<string name="minute"></string>
<string name="second"></string>
<string name="no_audio">音声なし動画</string>
<string name="location">場所</string>
<string name="format_importance_audio">形式の重要度の順序 (音声)</string>
<string name="enable_alarm_permission">アプリの設定から SCHEDULE_EXACT_ALARM の権限を有効にする必要があります。</string>
<string name="format_importance_note">形式の要素の重要度を調整します。この順序はアプリがダウンロードカード内で形式を取得し、形式を自動選択する際にみ使われます!</string>
</resources>

View file

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

View file

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

View file

@ -388,14 +388,13 @@
<string name="clear_scheduled">Limpar downloads agendados</string>
<string name="copy_url">Copiar URL</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="second">Segundo(s)</string>
<string name="milliseconds">Milissegundo(s)</string>
<string name="location">Localização</string>
<string name="download_archive_folder">Pasta para arquivamento de downloads</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="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>

View file

@ -388,11 +388,10 @@
<string name="move_bottom">Mover para baixo</string>
<string name="scheduled">Agendado</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="second">Segundo</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="location">Localização</string>
</resources>

View file

@ -388,13 +388,12 @@
<string name="move_bottom">Двигайтесь вниз</string>
<string name="scheduled">Запланированное</string>
<string name="clear_scheduled">Очистить Запланированное</string>
<string name="format_importance_video">Порядок важности формата (видео)</string>
<string name="download_archive_folder">Скачать папку архива</string>
<string name="location">Расположение</string>
<string name="minute">Минута</string>
<string name="second">Секунда</string>
<string name="milliseconds">Миллисекунды</string>
<string name="format_importance_audio">Порядок важности формата (аудио)</string>
<string name="format_importance">Порядок важности формата</string>
<string name="format_importance_note">Отрегулируйте важность элемента формата. Этот порядок будет использоваться только в том случае, если приложение извлекает форматы из карты загрузки и автоматически выбирает формат!</string>
<string name="no_audio">Видео без звука</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="minute">Minúta</string>
<string name="second">Sekunda</string>
<string name="format_importance_audio">Poradie dôležitosti formátu (Audio)</string>
<string name="format_importance_video">Poradie dôležitosti formátu (Video)</string>
<string name="format_importance">Poradie dôležitosti formátu</string>
<string name="no_audio">Video bez zvuku</string>
<string name="location">Umiestnenie</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="second">Sekonda</string>
<string name="milliseconds">Milisekonda</string>
<string name="format_importance_audio">Rradha e rëndësisë së Formateve (Audio)</string>
<string name="format_importance_video">Rradha e rëndësisë së Formateve (Video)</string>
<string name="format_importance">Rradha e rëndësisë së Formateve</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="download_archive_folder">Skedari i arkivës së shkarkimeve</string>

View file

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

View file

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

View file

@ -394,8 +394,7 @@
<string name="minute">Dakika</string>
<string name="second">Saniye</string>
<string name="milliseconds">Milisaniye</string>
<string name="format_importance_audio">Biçim Önem Sırası (Ses)</string>
<string name="format_importance_video">Biçim Önem Sırası (Video)</string>
<string name="format_importance">Biçim Önem Sırası</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="download_archive_folder">Arşiv Klasörünü İndir</string>

View file

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

View file

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

View file

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

View file

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

View file

@ -394,8 +394,7 @@
<string name="minute">Minute</string>
<string name="second">Second</string>
<string name="milliseconds">Milliseconds</string>
<string name="format_importance_audio">Format Importance Order (Audio)</string>
<string name="format_importance_video">Format Importance Order (Video)</string>
<string name="format_importance">Format Importance Order</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="download_archive_folder">Download Archive Folder</string>

View file

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

View file

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