- added hungarian
- added serbian
- added ability to enable/disable swipe gestures on any screen
- added ability to choose whether extra command applies to audio video or both
- hide search providers if the user has typed out an url in the searchview
This commit is contained in:
zaednasr 2023-11-12 15:59:55 +01:00
parent 715120dd59
commit 17d6a87e49
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
54 changed files with 1304 additions and 389 deletions

View file

@ -0,0 +1,572 @@
{
"formatVersion": 1,
"database": {
"version": 12,
"identityHash": "d56ec0fdb461aabd551c64b826f0f8ea",
"entities": [
{
"tableName": "results",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `urls` TEXT NOT NULL DEFAULT '', `chapters` TEXT, `creationTime` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "thumb",
"columnName": "thumb",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "website",
"columnName": "website",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistTitle",
"columnName": "playlistTitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "formats",
"columnName": "formats",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "urls",
"columnName": "urls",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "chapters",
"columnName": "chapters",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "creationTime",
"columnName": "creationTime",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadId` INTEGER NOT NULL DEFAULT 0, `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)",
"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
}
],
"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": []
}
],
"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, 'd56ec0fdb461aabd551c64b826f0f8ea')"
]
}
}

View file

@ -59,6 +59,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.BufferedReader
@ -146,14 +147,16 @@ class MainActivity : BaseActivity() {
}
val activeDownloadsBadge = (navigationView as NavigationBarView).getOrCreateBadge(R.id.historyFragment)
downloadViewModel.activeDownloadsCount.observe(this){
if (it == 0) {
activeDownloadsBadge.isVisible = false
activeDownloadsBadge.clearNumber()
}
else {
activeDownloadsBadge.isVisible = true
activeDownloadsBadge.number = it
lifecycleScope.launch {
downloadViewModel.activeDownloadsCount.collectLatest {
if (it == 0) {
activeDownloadsBadge.isVisible = false
activeDownloadsBadge.clearNumber()
}
else {
activeDownloadsBadge.isVisible = true
activeDownloadsBadge.number = it
}
}
}
}

View file

@ -2,6 +2,11 @@ package com.deniscerri.ytdlnis.database
import android.content.Context
import androidx.room.*
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.work.impl.Migration_11_12
import androidx.work.impl.Migration_12_13
import com.deniscerri.ytdlnis.database.dao.*
import com.deniscerri.ytdlnis.database.models.*
@ -18,7 +23,7 @@ import com.deniscerri.ytdlnis.database.models.*
LogItem::class,
TerminalItem::class
],
version = 11,
version = 12,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@ -29,7 +34,8 @@ import com.deniscerri.ytdlnis.database.models.*
AutoMigration (from = 7, to = 8),
AutoMigration (from = 8, to = 9),
AutoMigration (from = 9, to = 10),
AutoMigration (from = 10, to = 11)
AutoMigration (from = 10, to = 11),
AutoMigration (from = 11, to = 12)
]
)
abstract class DBManager : RoomDatabase(){
@ -53,15 +59,18 @@ abstract class DBManager : RoomDatabase(){
//if its not null return it, otherwise create db
fun getInstance(context: Context) : DBManager {
return instance ?: synchronized(this){
val dbInstance = Room.databaseBuilder(
context.applicationContext,
DBManager::class.java,
"YTDLnisDatabase"
)
.build()
.build()
instance = dbInstance
dbInstance
}
}
}
}

View file

@ -34,6 +34,12 @@ interface CommandTemplateDao {
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1")
fun getAllTemplatesAsExtraCommands() : List<String>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandAudio = 1")
fun getAllTemplatesAsExtraCommandsForAudio() : List<String>
@Query("SELECT content FROM commandTemplates WHERE useAsExtraCommand is 1 AND useAsExtraCommandVideo = 1")
fun getAllTemplatesAsExtraCommandsForVideo() : List<String>
@Query("SELECT * FROM templateShortcuts ORDER BY id DESC")
fun getAllShortcutsFlow() : Flow<List<TemplateShortcut>>

View file

@ -24,6 +24,9 @@ interface ResultDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: ResultItem) : Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMultiple(items: List<ResultItem?>) : List<Long>
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: ResultItem)

View file

@ -13,5 +13,9 @@ data class CommandTemplate(
var title: String,
var content: String,
@ColumnInfo(defaultValue = "0")
var useAsExtraCommand: Boolean
var useAsExtraCommand: Boolean,
@ColumnInfo(defaultValue = "1")
var useAsExtraCommandAudio: Boolean,
@ColumnInfo(defaultValue = "1")
var useAsExtraCommandVideo: Boolean
)

View file

@ -8,6 +8,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import java.io.File
@ -16,7 +17,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getAllDownloads()}
)
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads()
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads().distinctUntilChanged()
val queuedDownloads : Pager<Int, DownloadItemSimple> = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getQueuedDownloads()}

View file

@ -25,9 +25,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
val infoUtil = InfoUtil(context)
val items = infoUtil.getTrending()
itemCount.value = items.size
for (i in items){
resultDao.insert(i!!)
}
resultDao.insertMultiple(items)
}
suspend fun search(inputQuery: String, resetResults: Boolean) : ArrayList<ResultItem?>{
@ -35,9 +33,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
if (resetResults) deleteAll()
val res = infoUtil.search(inputQuery)
itemCount.value = res.size
res.forEach {
resultDao.insert(it!!)
}
resultDao.insertMultiple(res)
return res
}
@ -50,9 +46,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
}else{
v.forEach { it?.playlistTitle = "ytdlnis-Search" }
}
v.forEach {
resultDao.insert(it!!)
}
resultDao.insertMultiple(v)
return ArrayList(v)
}
@ -72,9 +66,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
nextPageToken = tmpToken
} while (true)
itemCount.value = items.size
items.forEach {
resultDao.insert(it!!)
}
resultDao.insertMultiple(items.toList())
return items
}
@ -87,9 +79,7 @@ class ResultRepository(private val resultDao: ResultDao, private val context: Co
}else{
items.forEach { it!!.playlistTitle = "ytdlnis-Search" }
}
items.forEach {
resultDao.insert(it!!)
}
resultDao.insertMultiple(items.toList())
return items
}

View file

@ -64,8 +64,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val infoUtil : InfoUtil
val allDownloads : Flow<PagingData<DownloadItem>>
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
val activeDownloads : LiveData<List<DownloadItem>>
val activeDownloadsCount : LiveData<Int>
val activeDownloads : Flow<List<DownloadItem>>
val activeDownloadsCount : Flow<Int>
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
@ -78,7 +78,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val formatIDPreference: List<String>
private val audioFormatIDPreference: List<String>
private val resources : Resources
private var extraCommands: String = ""
private var extraCommandsForAudio: String = ""
private var extraCommandsForVideo: String = ""
private var audioContainer: String?
private var videoContainer: String?
@ -123,14 +124,15 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
allDownloads = repository.allDownloads.flow
queuedDownloads = repository.queuedDownloads.flow
activeDownloads = repository.activeDownloads.asLiveData()
activeDownloadsCount = repository.activeDownloadsCount.asLiveData()
activeDownloads = repository.activeDownloads
activeDownloadsCount = repository.activeDownloadsCount
savedDownloads = repository.savedDownloads.flow
cancelledDownloads = repository.cancelledDownloads.flow
erroredDownloads = repository.erroredDownloads.flow
viewModelScope.launch(Dispatchers.IO){
if (sharedPreferences.getBoolean("use_extra_commands", false)){
extraCommands = commandTemplateDao.getAllTemplatesAsExtraCommands().joinToString(" ")
extraCommandsForAudio = commandTemplateDao.getAllTemplatesAsExtraCommandsForAudio().joinToString(" ")
extraCommandsForVideo = commandTemplateDao.getAllTemplatesAsExtraCommandsForVideo().joinToString(" ")
}
}
@ -283,6 +285,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
audioFormatIDs = preferredAudioFormats
)
val extraCommands = when(type){
Type.audio -> extraCommandsForAudio
Type.video -> extraCommandsForVideo
else -> ""
}
return DownloadItem(0,
resultItem.url,
resultItem.title,
@ -404,6 +412,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
val extraCommands = when(historyItem.type){
Type.audio -> extraCommandsForAudio
Type.video -> extraCommandsForVideo
else -> ""
}
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs)
val downloadPath = File(historyItem.downloadPath)
@ -467,9 +481,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
else -> {
val lastUsedCommandTemplate = sharedPreferences.getString("lastCommandTemplateUsed", "")!!
val c = if (lastUsedCommandTemplate.isBlank()){
commandTemplateDao.getFirst() ?: CommandTemplate(0,"","", false)
commandTemplateDao.getFirst() ?: CommandTemplate(0,"","", useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false)
}else{
commandTemplateDao.getTemplateByContent(lastUsedCommandTemplate) ?: CommandTemplate(0, "", lastUsedCommandTemplate, false)
commandTemplateDao.getTemplateByContent(lastUsedCommandTemplate) ?: CommandTemplate(0, "", lastUsedCommandTemplate, useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false)
}
return generateCommandFormat(c)
}

View file

@ -4,19 +4,29 @@ import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import android.util.Patterns
import android.view.View
import androidx.compose.material3.formatWithSkeleton
import androidx.core.os.bundleOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.SearchHistoryItem
import com.deniscerri.ytdlnis.database.repository.ResultRepository
import com.deniscerri.ytdlnis.database.repository.SearchHistoryRepository
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadAudioFragment
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadVideoFragment
import com.deniscerri.ytdlnis.util.InfoUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -28,6 +38,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
val repository : ResultRepository
private val searchHistoryRepository : SearchHistoryRepository
val items : LiveData<List<ResultItem>>
private val infoUtil: InfoUtil
data class ResultsUiState(
var processing: Boolean,
var errorMessage: Pair<Int, String>?,
@ -51,6 +62,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
searchHistoryRepository = SearchHistoryRepository(DBManager.getInstance(application).searchHistoryDao)
items = repository.allResults.asLiveData()
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(application)
infoUtil = InfoUtil(application)
}
fun checkTrending() = viewModelScope.launch(Dispatchers.IO){
@ -171,4 +183,62 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
repository.delete(it)
}
}
val updatingData: MutableStateFlow<Boolean> = MutableStateFlow(false)
var updateResultData: MutableStateFlow<List<ResultItem?>?> = MutableStateFlow(null)
private var updateResultDataJob : Job? = null
val updatingFormats: MutableStateFlow<Boolean> = MutableStateFlow(false)
var updateFormatsResultData: MutableStateFlow<MutableList<Format>?> = MutableStateFlow(null)
private var updateFormatsResultDataJob: Job? = null
suspend fun updateItemData(res: ResultItem){
if (updateResultDataJob == null || updateResultDataJob?.isCancelled == true || updateResultDataJob?.isCompleted == true){
updateResultDataJob = viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching {
updatingData.emit(true)
val result = parseQueries(listOf(res.url))
updatingData.emit(false)
updateResultData.emit(result)
}.onFailure {
updatingData.emit(false)
updateResultData.emit(mutableListOf())
}
}
}
updateResultDataJob?.start()
}
suspend fun cancelUpdateItemData(){
updatingData.emit(false)
updateResultData.emit(null)
updateResultDataJob?.cancel()
}
suspend fun cancelUpdateFormatsItemData(){
updatingFormats.emit(false)
updateFormatsResultData.emit(null)
updateFormatsResultDataJob?.cancel()
}
suspend fun updateFormatItemData(result: ResultItem){
updateFormatsResultDataJob = viewModelScope.launch(Dispatchers.IO) {
kotlin.runCatching {
updatingFormats.emit(true)
val formats = infoUtil.getFormats(result.url)
updatingFormats.emit(false)
if (formats.isNotEmpty()){
val res = getItemByURL(result.url)
res.formats = formats.toMutableList()
update(res)
}
updateFormatsResultData.emit(formats.toMutableList())
}.onFailure {
updatingFormats.emit(false)
updateFormatsResultData.emit(mutableListOf())
}
}
updateFormatsResultDataJob?.start()
}
}

View file

@ -33,18 +33,15 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
@ -316,6 +313,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
providersChipGroup!!.addView(tmp)
}
val chipGroupDivider : View? = requireView().findViewById(R.id.chipGroupDivider)
searchView!!.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.SHOWN) {
@ -328,13 +326,19 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
}
if (Patterns.WEB_URL.matcher(searchBar!!.text.toString()).matches() && searchBar!!.text.isNotBlank()){
providersChipGroup.visibility = GONE
chipGroupDivider?.visibility = GONE
}else{
providersChipGroup.visibility = VISIBLE
chipGroupDivider?.visibility = VISIBLE
}
try{
val clipboard =
requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val regex =
"(https?://(?:www\\.|(?!www))[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|www\\.[a-zA-Z\\d][a-zA-Z\\d-]+[a-zA-Z\\d]\\.\\S{2,}|https?://(?:www\\.|(?!www))[a-zA-Z\\d]+\\.\\S{2,}|www\\.[a-zA-Z\\d]+\\.\\S{2,})".toRegex()
val clip = clipboard.primaryClip!!.getItemAt(0).text
if (regex.containsMatchIn(clip.toString())) {
if (Patterns.WEB_URL.matcher(clip.toString()).matches()) {
linkYouCopied!!.visibility = VISIBLE
val textView = linkYouCopied!!.findViewById<TextView>(R.id.suggestion_text)
textView.text = getString(R.string.link_you_copied)
@ -381,6 +385,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
lifecycleScope.launch {
updateSearchViewItems(it, linkYouCopied)
}
if (Patterns.WEB_URL.matcher(it.toString()).matches()){
providersChipGroup.visibility = GONE
chipGroupDivider?.visibility = GONE
}else{
providersChipGroup.visibility = VISIBLE
chipGroupDivider?.visibility = VISIBLE
}
}
searchView!!.editText.setOnTouchListener(OnTouchListener { _, event ->

View file

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.ui.adapter
import android.animation.ValueAnimator
import android.app.Activity
import android.content.SharedPreferences
import android.graphics.Color
@ -9,6 +10,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
@ -20,6 +22,7 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.Extensions.dp
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
@ -27,6 +30,7 @@ import com.google.android.material.chip.Chip
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.squareup.picasso.Picasso
class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ActiveDownloadAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
@ -137,16 +141,37 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
when(DownloadRepository.Status.valueOf(item.status)){
DownloadRepository.Status.Active -> {
progressBar.isIndeterminate = true
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
val fromRadius: Int = dp(activity.resources, 30f)
val toRadius: Int = dp(activity.resources, 15f)
val animator = ValueAnimator.ofInt(fromRadius, toRadius)
animator.setDuration(500)
.addUpdateListener { animation ->
val value = animation.animatedValue as Int
pauseButton.cornerRadius = value
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white)
pauseButton.isEnabled = true
pauseButton.tag = ActiveDownloadAction.Pause
}
animator.start()
cancelButton.visibility = View.GONE
pauseButton.isEnabled = true
pauseButton.tag = ActiveDownloadAction.Pause
}
DownloadRepository.Status.ActivePaused -> {
progressBar.isIndeterminate = false
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
pauseButton.tag = ActiveDownloadAction.Resume
pauseButton.isEnabled = true
val fromRadius: Int = dp(activity.resources, 15f)
val toRadius: Int = dp(activity.resources, 30f)
val animator = ValueAnimator.ofInt(fromRadius, toRadius)
animator.setDuration(500)
.addUpdateListener { animation ->
val value = animation.animatedValue as Int
pauseButton.cornerRadius = value
pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white)
pauseButton.tag = ActiveDownloadAction.Resume
pauseButton.isEnabled = true
}
animator.start()
cancelButton.visibility = View.VISIBLE
}
DownloadRepository.Status.PausedReQueued -> {

View file

@ -6,6 +6,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.compose.ui.util.fastJoinToString
import androidx.core.view.isVisible
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
@ -51,11 +53,18 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
val content = card.findViewById<TextView>(R.id.content)
content.text = item?.content
val useInExtraCommands = card.findViewById<TextView>(R.id.useInExtraCommands)
if (item!!.useAsExtraCommand) useInExtraCommands.visibility = View.VISIBLE
else useInExtraCommands.visibility = View.GONE
card.findViewById<TextView>(R.id.useInExtraCommands).apply {
isVisible = item!!.useAsExtraCommand
val extraAudio = if (item.useAsExtraCommandAudio) context.getString(R.string.audio) else null
val extraVideo = if (item.useAsExtraCommandVideo) context.getString(R.string.video) else null
val finalText = context.getString(R.string.extra_command) + " " + listOfNotNull(
extraAudio,
extraVideo
).joinToString(" ", "[", "]", -1)
text = finalText
}
if (checkedItems.contains(item.id)) {
if (checkedItems.contains(item!!.id)) {
card.isChecked = true
card.strokeWidth = 5
} else {
@ -132,7 +141,11 @@ class TemplatesAdapter(onItemClickListener: OnItemClickListener, activity: Activ
}
override fun areContentsTheSame(oldItem: CommandTemplate, newItem: CommandTemplate): Boolean {
return oldItem.title == newItem.title && oldItem.content == newItem.content && oldItem.useAsExtraCommand == newItem.useAsExtraCommand
return oldItem.title == newItem.title &&
oldItem.content == newItem.content &&
oldItem.useAsExtraCommand == newItem.useAsExtraCommand &&
oldItem.useAsExtraCommandAudio == newItem.useAsExtraCommandAudio &&
oldItem.useAsExtraCommandVideo == newItem.useAsExtraCommandVideo
}
}
}

View file

@ -21,7 +21,7 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.coroutines.Dispatchers

View file

@ -30,7 +30,7 @@ import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
import com.deniscerri.ytdlnis.util.VideoPlayerUtil
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment

View file

@ -69,7 +69,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
infoUtil = InfoUtil(requireContext())
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
shownFields = preferences.getStringSet("modify_download_card", requireContext().getStringArray(R.array.modify_download_card_values).toSet())!!.toList()
return fragmentView
}

View file

@ -9,6 +9,7 @@ import android.os.Build
import android.os.Bundle
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.util.Log
import android.util.Patterns
import android.view.LayoutInflater
import android.view.View
@ -20,8 +21,6 @@ import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
@ -46,10 +45,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
@ -78,9 +77,6 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
private lateinit var shimmerLoadingSubtitle : ShimmerFrameLayout
private lateinit var subtitle : View
private var updateJob : Job? = null
private var updateFormatsJob : Job? = null
private lateinit var result: ResultItem
private lateinit var type: Type
@ -88,9 +84,9 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(this)[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
historyViewModel = ViewModelProvider(requireActivity())[HistoryViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
infoUtil = InfoUtil(requireContext())
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -143,6 +139,16 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
shimmerLoadingSubtitle = view.findViewById(R.id.shimmer_loading_subtitle)
subtitle = view.findViewById(R.id.bottom_sheet_subtitle)
shimmerLoading.setOnClickListener {
lifecycleScope.launch {
withContext(Dispatchers.IO){
resultViewModel.cancelUpdateItemData()
}
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
}
}
(viewPager2.getChildAt(0) as? RecyclerView)?.apply {
isNestedScrollingEnabled = false
overScrollMode = View.OVER_SCROLL_NEVER
@ -259,7 +265,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
val shownFields = sharedPreferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
val shownFields = sharedPreferences.getStringSet("modify_download_card", requireContext().getStringArray(R.array.modify_download_card_values).toSet())!!.toList()
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
scheduleBtn.visibility = if(shownFields.contains("schedule")){
@ -272,8 +278,11 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
scheduleBtn.setOnClickListener{
UiUtil.showDatePicker(fragmentManager) {
updateJob?.cancel()
updateFormatsJob?.cancel()
lifecycleScope.launch {
resultViewModel.cancelUpdateItemData()
resultViewModel.cancelUpdateFormatsItemData()
}
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem()
@ -287,8 +296,10 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
}
download!!.setOnClickListener {
updateJob?.cancel()
updateFormatsJob?.cancel()
lifecycleScope.launch {
resultViewModel.cancelUpdateItemData()
resultViewModel.cancelUpdateFormatsItemData()
}
scheduleBtn.isEnabled = false
download.isEnabled = false
val item: DownloadItem = getDownloadItem()
@ -356,7 +367,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}else {
val usingGenericFormatsOrEmpty = result.formats.isEmpty() || result.formats.any { it.format_note.contains("ytdlnisgeneric") }
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false) && !sharedPreferences.getBoolean("quick_download", false)){
initUpdateFormats(result.url)
initUpdateFormats(result)
}
}
@ -392,7 +403,115 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
}
}
lifecycleScope.launch {
resultViewModel.updatingData.collectLatest {
if (it){
title.visibility = View.GONE
subtitle.visibility = View.GONE
shimmerLoading.visibility = View.VISIBLE
shimmerLoadingSubtitle.visibility = View.VISIBLE
shimmerLoading.startShimmer()
shimmerLoadingSubtitle.startShimmer()
}
}
}
lifecycleScope.launch {
resultViewModel.updatingFormats.collectLatest {
if (it){
runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
}
runCatching {
val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
}
}
}
}
lifecycleScope.launch {
resultViewModel.updateResultData.collectLatest { result ->
if (result == null) return@collectLatest
lifecycleScope.launch(Dispatchers.Main) {
if (result.isNotEmpty()){
if (result.size == 1 && result[0] != null){
fragmentAdapter.setResultItem(result[0]!!)
runCatching {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateUI(result[0])
}
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f1.updateUI(result[0])
}
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
shimmerLoading.visibility = View.GONE
shimmerLoadingSubtitle.visibility = View.GONE
shimmerLoading.stopShimmer()
shimmerLoadingSubtitle.stopShimmer()
val usingGenericFormatsOrEmpty = result[0]!!.formats.isEmpty() || result[0]!!.formats.any { it.format_note.contains("ytdlnisgeneric") }
fragmentAdapter.setResultItem(result[0]!!)
arguments?.putParcelable("result", result[0]!!)
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){
initUpdateFormats(result[0]!!)
}
}else{
//open multi download card instead
if (activity is ShareActivity){
val preferredType = DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!)
findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_selectPlaylistItemsDialog, bundleOf(
Pair("results", result),
Pair("type", preferredType),
))
}else{
dismiss()
}
}
}
resultViewModel.updateResultData.emit(null)
}
}
}
lifecycleScope.launch {
resultViewModel.updateFormatsResultData.collectLatest { formats ->
if (formats == null) return@collectLatest
lifecycleScope.launch {
withContext(Dispatchers.Main){
runCatching {
val f1 = fragmentManager.findFragmentByTag("f0") as DownloadAudioFragment
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
resultItem.formats = formats
fragmentAdapter.setResultItem(resultItem)
f1.updateUI(resultItem)
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
}
runCatching {
val f1 = fragmentManager.findFragmentByTag("f1") as DownloadVideoFragment
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
resultItem.formats = formats
fragmentAdapter.setResultItem(resultItem)
f1.updateUI(resultItem)
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
}
}
if (formats.isNotEmpty()){
result.formats = formats
}
resultViewModel.updateFormatsResultData.emit(null)
}
}
}
}
private fun getDownloadItem(selectedTabPosition: Int = tabLayout.selectedTabPosition) : DownloadItem{
return when(selectedTabPosition){
0 -> {
@ -430,139 +549,22 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
private fun initUpdateData() {
updateJob = CoroutineScope(SupervisorJob()).launch(Dispatchers.IO){
findNavController().saveState()
kotlin.runCatching {
withContext(Dispatchers.Main){
title.visibility = View.GONE
subtitle.visibility = View.GONE
shimmerLoading.visibility = View.VISIBLE
shimmerLoadingSubtitle.visibility = View.VISIBLE
shimmerLoading.startShimmer()
shimmerLoadingSubtitle.startShimmer()
}
if (result.url.isBlank()){
withContext(Dispatchers.Main){dismiss()}
return@launch
}
val result = resultViewModel.parseQueries(listOf(result.url))
if (result.isEmpty()){
return@launch
}
if (result.size == 1 && result[0] != null){
fragmentAdapter.setResultItem(result[0]!!)
withContext(Dispatchers.Main){
runCatching {
val f = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f.updateUI(result[0])
}
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f1.updateUI(result[0])
}
}
withContext(Dispatchers.Main){
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
shimmerLoading.visibility = View.GONE
shimmerLoadingSubtitle.visibility = View.GONE
shimmerLoading.stopShimmer()
shimmerLoadingSubtitle.stopShimmer()
}
val usingGenericFormatsOrEmpty = result[0]!!.formats.isEmpty() || result[0]!!.formats.any { it.format_note.contains("ytdlnisgeneric") }
if (usingGenericFormatsOrEmpty && sharedPreferences.getBoolean("update_formats", false)){
initUpdateFormats(result[0]!!.url)
} else {
}
}else{
//open multi download card instead
if (activity is ShareActivity){
val preferredType = DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!)
withContext(Dispatchers.Main){
findNavController().navigate(R.id.action_downloadBottomSheetDialog_to_selectPlaylistItemsDialog, bundleOf(
Pair("results", result),
Pair("type", preferredType),
))
}
}else{
dismiss()
}
}
}
if (result.url.isBlank()) {
dismiss()
return
}
shimmerLoading.setOnClickListener {
updateJob?.cancel()
(updateItem.parent as LinearLayout).visibility = View.VISIBLE
if (resultViewModel.updatingData.value) return
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
resultViewModel.updateItemData(result)
}
updateJob?.invokeOnCompletion {
kotlin.runCatching {
requireActivity().runOnUiThread {
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
shimmerLoading.visibility = View.GONE
shimmerLoadingSubtitle.visibility = View.GONE
shimmerLoading.stopShimmer()
shimmerLoadingSubtitle.stopShimmer()
}
}
}
updateJob?.start()
}
private fun initUpdateFormats(url: String){
updateFormatsJob = CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
withContext(Dispatchers.Main){
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
}
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.VISIBLE
}
}
val formats = runCatching {
infoUtil.getFormats(url).toMutableList()
}.getOrElse {
mutableListOf()
}
withContext(Dispatchers.Main){
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f0") as DownloadAudioFragment
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
resultItem.formats = formats
fragmentAdapter.setResultItem(resultItem)
f1.updateUI(resultItem)
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
}
runCatching {
val f1 = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
val resultItem = downloadViewModel.createResultItemFromDownload(f1.downloadItem)
resultItem.formats = formats
fragmentAdapter.setResultItem(resultItem)
f1.updateUI(resultItem)
f1.view?.findViewById<LinearProgressIndicator>(R.id.format_loading_progress)?.visibility = View.GONE
}
}
if (formats.isNotEmpty()){
result.formats = formats
resultViewModel.update(result)
}
private fun initUpdateFormats(res: ResultItem){
if (resultViewModel.updatingFormats.value) return
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
resultViewModel.updateFormatItemData(res)
}
updateFormatsJob?.start()
}
}

View file

@ -31,7 +31,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.CommandTemplateViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
import com.deniscerri.ytdlnis.util.UiUtil.populateCommandCard
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
@ -87,7 +87,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
if (!Patterns.WEB_URL.matcher(downloadItem.url).matches()){
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", false))
downloadItem.format = downloadViewModel.generateCommandFormat(CommandTemplate(0,"txt", "-a \"${downloadItem.url}\"", useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false))
downloadItem.url = ""
}
@ -231,7 +231,7 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
0,
"",
chosenCommandView.editText!!.text.toString(),
false
useAsExtraCommand = false, useAsExtraCommandAudio = false, useAsExtraCommandVideo = false
)
UiUtil.showCommandTemplateCreationOrUpdatingSheet(current, requireActivity(), viewLifecycleOwner, commandTemplateViewModel) {
templates.add(it)

View file

@ -48,7 +48,7 @@ import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.work.UpdatePlaylistFormatsWorker
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior

View file

@ -74,7 +74,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
infoUtil = InfoUtil(requireContext())
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
shownFields = preferences.getStringSet("modify_download_card", setOf())!!.toList().ifEmpty { requireContext().getStringArray(R.array.modify_download_card_values).toList() }
shownFields = preferences.getStringSet("modify_download_card", requireContext().getStringArray(R.array.modify_download_card_values).toSet())!!.toList()
return fragmentView
}

View file

@ -62,6 +62,7 @@ import com.google.android.material.snackbar.Snackbar
import com.yausername.youtubedl_android.YoutubeDL
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@ -181,14 +182,17 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
queuedRecycler.adapter = queuedAdapter
queuedRecycler.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size))
downloadViewModel.activeDownloads.map { it.filter { d -> d.url == item.url } }.observe(viewLifecycleOwner) {
activeAdapter.submitList(it)
if (it.isEmpty()){
running.visibility = View.GONE
runningRecycler.visibility = View.GONE
}else{
running.visibility = View.VISIBLE
runningRecycler.visibility = View.VISIBLE
lifecycleScope.launch {
downloadViewModel.activeDownloads.map { it.filter { d -> d.url == item.url } }.collectLatest {
delay(500)
activeAdapter.submitList(it)
if (it.isEmpty()){
running.visibility = View.GONE
runningRecycler.visibility = View.GONE
}else{
running.visibility = View.VISIBLE
runningRecycler.visibility = View.VISIBLE
}
}
}
@ -231,8 +235,10 @@ class ResultCardDetailsDialog : BottomSheetDialogFragment(), GenericDownloadAdap
}
}
downloadViewModel.activeDownloadsCount.observe(viewLifecycleOwner){
activeCount = it
lifecycleScope.launch {
downloadViewModel.activeDownloadsCount.collectLatest {
activeCount = it
}
}

View file

@ -25,8 +25,8 @@ import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.receiver.ShareActivity
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.setTextAndRecalculateWidth
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.setTextAndRecalculateWidth
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton

View file

@ -23,11 +23,13 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.google.android.material.button.MaterialButton
import com.google.android.material.progressindicator.LinearProgressIndicator
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.runBlocking
import kotlinx.coroutines.withContext
@ -157,21 +159,30 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
}
}
downloadViewModel.activeDownloads.observe(viewLifecycleOwner) {
activeDownloads.submitList(it)
if (it.size > 1){
pauseResume.visibility = View.VISIBLE
if (it.all { l -> l.status == DownloadRepository.Status.ActivePaused.toString() }){
pauseResume.text = requireContext().getString(R.string.resume)
}else{
pauseResume.text = requireContext().getString(R.string.pause)
}
}else{
activeDownloads.notifyDataSetChanged()
pauseResume.visibility = View.GONE
lifecycleScope.launch {
downloadViewModel.activeDownloadsCount.collectLatest {
delay(200)
noResults.visibility = if (it == 0) View.VISIBLE else View.GONE
}
noResults.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE
}
lifecycleScope.launch {
downloadViewModel.activeDownloads.collectLatest {
delay(100)
activeDownloads.submitList(it)
if (it.size > 1){
pauseResume.visibility = View.VISIBLE
if (it.all { l -> l.status == DownloadRepository.Status.ActivePaused.toString() }){
pauseResume.text = requireContext().getString(R.string.resume)
}else{
pauseResume.text = requireContext().getString(R.string.pause)
}
}else{
pauseResume.visibility = View.GONE
}
}
}
}
@ -212,7 +223,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
withContext(Dispatchers.IO){
downloadViewModel.updateDownload(item)
}
activeDownloads.notifyItemChanged(position)
}
}
ActiveDownloadAdapter.ActiveDownloadAction.Resume -> {
@ -221,7 +231,6 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
withContext(Dispatchers.IO){
downloadViewModel.updateDownload(item)
}
activeDownloads.notifyItemChanged(position)
runBlocking {
downloadViewModel.queueDownloads(listOf(item))

View file

@ -23,6 +23,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -30,8 +31,8 @@ import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -80,7 +81,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
cancelledRecyclerView.adapter = adapter
cancelledRecyclerView.enableFastScroll()
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("cancelled")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(cancelledRecyclerView)
}

View file

@ -24,6 +24,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -31,8 +32,8 @@ import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -80,7 +81,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
erroredRecyclerView.adapter = adapter
erroredRecyclerView.enableFastScroll()
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("errored")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(erroredRecyclerView)
}

View file

@ -36,6 +36,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.HistoryAdapter
@ -47,7 +48,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.HistoryViewModel
import com.deniscerri.ytdlnis.databinding.FragmentHistoryBinding
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetDialog
@ -122,7 +123,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
recyclerView?.enableFastScroll()
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("history")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}

View file

@ -26,6 +26,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -35,8 +36,8 @@ import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -91,7 +92,7 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
queuedRecyclerView.enableFastScroll()
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("queued")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(queuedRecyclerView)
}

View file

@ -23,6 +23,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
@ -30,8 +31,8 @@ import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -79,7 +80,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
savedRecyclerView.enableFastScroll()
savedRecyclerView.adapter = adapter
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("saved")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(savedRecyclerView)
}

View file

@ -29,6 +29,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.TemplatesAdapter
@ -94,7 +95,7 @@ class CommandTemplatesFragment : Fragment(), TemplatesAdapter.OnItemClickListene
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = templatesAdapter
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("templates")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}

View file

@ -23,6 +23,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.CookieAdapter
@ -79,7 +80,7 @@ class CookiesFragment : Fragment(), CookieAdapter.OnItemClickListener {
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = listAdapter
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getBoolean("swipe_gestures", true)){
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("cookies")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}

View file

@ -27,7 +27,7 @@ import androidx.lifecycle.lifecycleScope
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton

View file

@ -26,8 +26,8 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.DownloadLogsAdapter
import com.deniscerri.ytdlnis.database.models.LogItem
import com.deniscerri.ytdlnis.database.viewmodel.LogViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.UiUtil.forceFastScrollMode
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.forceFastScrollMode
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar

View file

@ -51,7 +51,10 @@ abstract class BaseSettingsFragment : PreferenceFragmentCompat() {
}.toBooleanArray()
MaterialAlertDialogBuilder(requireContext())
.setTitle(preference.title)
.setMultiChoiceItems(preference.entries, selectedItems) { _, _, _ ->
.setMultiChoiceItems(preference.entries, selectedItems) { _, which, isChecked ->
selectedItems[which] = isChecked
}
.setPositiveButton(R.string.ok) { _, _ ->
val newValues = preference.entryValues
.filterIndexed { index, _ -> selectedItems[index] }
.map { it.toString() }

View file

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
@ -66,11 +67,9 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
language!!.entries = entries.toTypedArray()
if(language!!.value == null) language!!.value = Locale.getDefault().language
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
language!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
true
}

View file

@ -20,7 +20,7 @@ import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.TerminalDownloadsAdapter
import com.deniscerri.ytdlnis.database.models.TerminalItem
import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
import com.deniscerri.ytdlnis.util.UiUtil.enableFastScroll
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.progressindicator.LinearProgressIndicator
import kotlinx.coroutines.flow.collectLatest

View file

@ -37,7 +37,7 @@ import com.deniscerri.ytdlnis.database.viewmodel.TerminalViewModel
import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.UiUtil.enableTextHighlight
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton

View file

@ -0,0 +1,94 @@
package com.deniscerri.ytdlnis.util
import android.annotation.SuppressLint
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.util.regex.Pattern
object Extensions {
fun dp(resources: Resources, px: Float) : Int {
val metrics: DisplayMetrics = resources.displayMetrics
return (metrics.density * px).toInt()
}
private var textHighLightSchemes = listOf(
ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")),
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#77eb09")),
ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")),
ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564"))
)
fun View.enableTextHighlight(){
if (this is EditText || this is TextView){
//init syntax highlighter
val highlight = Highlight()
val highlightWatcher = HighlightTextWatcher()
highlight.addScheme(
*textHighLightSchemes.map { it }.toTypedArray()
)
highlightWatcher.addScheme(
*textHighLightSchemes.map { it }.toTypedArray()
)
highlight.setSpan(this as TextView)
this.addTextChangedListener(highlightWatcher)
}
}
fun EditText.setTextAndRecalculateWidth(t : String){
val scale = context.resources.displayMetrics.density
this.setText(t)
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
this.measure(widthMeasureSpec, heightMeasureSpec)
val requiredWidth: Int = this.measuredWidth
if (t.length < 5){
this.layoutParams.width = (70 * scale + 0.5F).toInt()
}else{
this.layoutParams.width = requiredWidth
}
this.requestLayout()
}
@SuppressLint("ClickableViewAccessibility")
fun RecyclerView.forceFastScrollMode()
{
overScrollMode = View.OVER_SCROLL_ALWAYS
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
isVerticalScrollBarEnabled = true
setOnTouchListener { view, event ->
if (event.x >= this.width - 30) {
view.parent.requestDisallowInterceptTouchEvent(true)
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_UP -> view.parent.requestDisallowInterceptTouchEvent(false)
}
}
false
}
}
fun RecyclerView.enableFastScroll(){
val drawable = ShapeDrawable(OvalShape())
drawable.paint.color = context.getColor(android.R.color.transparent)
FastScrollerBuilder(this)
.setTrackDrawable(drawable)
.build()
}
}

View file

@ -638,7 +638,7 @@ class InfoUtil(private val context: Context) {
val website = jsonObject.getString(listOf("ie_key", "extractor_key", "extractor").first { jsonObject.has(it) })
var playlistTitle: String? = ""
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
if(playlistTitle.equals(query)) playlistTitle = ""
if(playlistTitle?.removeSurrounding("\"").equals(query)) playlistTitle = ""
if (playlistTitle?.isNotBlank() == true){
playlistTitle += "[${jsonObject.getString("playlist_index")}]"

View file

@ -25,6 +25,7 @@ import android.view.ViewGroup
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
@ -100,14 +101,24 @@ object UiUtil {
formatCard.findViewById<TextView>(R.id.container).text = container.uppercase()
formatCard.findViewById<TextView>(R.id.format_note).text = formatNote.uppercase()
val audioFormatsTextView = formatCard.findViewById<TextView>(R.id.audio_formats)
if (!audioFormats.isNullOrEmpty()) {
audioFormatsTextView.text = "id: " + audioFormats.joinToString("+") { it.format_id }
audioFormatsTextView.visibility = View.VISIBLE
}else{
audioFormatsTextView.visibility = View.GONE
formatCard.findViewById<TextView>(R.id.audio_formats).apply {
if (!audioFormats.isNullOrEmpty()){
text = "id: " + audioFormats.joinToString("+") { it.format_id }
visibility = View.VISIBLE
}else{
visibility = View.GONE
}
setOnClickListener {
formatCard.callOnClick()
}
}
formatCard.findViewById<TextView>(R.id.format_id).apply {
text = "id: ${chosenFormat.format_id}"
setOnClickListener {
formatCard.callOnClick()
}
}
formatCard.findViewById<TextView>(R.id.format_id).text = "id: ${chosenFormat.format_id}"
val codec =
if (chosenFormat.encoding != "") {
chosenFormat.encoding.uppercase()
@ -116,15 +127,28 @@ object UiUtil {
} else {
chosenFormat.acodec.uppercase()
}
if (codec == "" || codec == "none"){
formatCard.findViewById<TextView>(R.id.codec).visibility = View.GONE
}else{
formatCard.findViewById<TextView>(R.id.codec).visibility = View.VISIBLE
formatCard.findViewById<TextView>(R.id.codec).text = codec
formatCard.findViewById<TextView>(R.id.codec).apply {
if (codec == "" || codec == "none"){
visibility = View.GONE
}else{
visibility = View.VISIBLE
text = codec
}
setOnClickListener {
formatCard.callOnClick()
}
}
var filesize = chosenFormat.filesize
if (!audioFormats.isNullOrEmpty()) filesize += audioFormats.sumOf { it.filesize }
formatCard.findViewById<TextView>(R.id.file_size).text = FileUtil.convertFileSize(filesize)
formatCard.findViewById<TextView>(R.id.file_size).apply {
text = FileUtil.convertFileSize(filesize)
setOnClickListener {
formatCard.callOnClick()
}
}
}
@ -144,6 +168,8 @@ object UiUtil {
val title : TextInputLayout = bottomSheet.findViewById(R.id.title)!!
val content : TextInputLayout = bottomSheet.findViewById(R.id.content)!!
val extraCommandsSwitch : MaterialSwitch = bottomSheet.findViewById(R.id.extraCommandsSwitch)!!
val extraCommandsAudio : CheckBox = bottomSheet.findViewById(R.id.checkbox_audio)!!
val extraCommandsVideo : CheckBox = bottomSheet.findViewById(R.id.checkbox_video)!!
val shortcutsChipGroup : ChipGroup = bottomSheet.findViewById(R.id.shortcutsChipGroup)!!
val editShortcuts : Button = bottomSheet.findViewById(R.id.edit_shortcuts)!!
@ -194,8 +220,34 @@ object UiUtil {
if (item != null){
extraCommandsSwitch.isChecked = item.useAsExtraCommand
if (item.useAsExtraCommand){
extraCommandsAudio.isVisible = true
extraCommandsAudio.isChecked = item.useAsExtraCommandAudio
extraCommandsVideo.isVisible = true
extraCommandsVideo.isChecked = item.useAsExtraCommandVideo
}else{
extraCommandsAudio.isVisible = false
extraCommandsAudio.isChecked = false
extraCommandsVideo.isVisible = false
extraCommandsVideo.isChecked = false
}
}
extraCommandsSwitch.setOnCheckedChangeListener { compoundButton, b ->
extraCommandsAudio.isVisible = extraCommandsSwitch.isChecked
extraCommandsAudio.isChecked = true
extraCommandsVideo.isVisible = extraCommandsSwitch.isChecked
extraCommandsVideo.isChecked = true
}
extraCommandsAudio.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked
}
extraCommandsVideo.setOnCheckedChangeListener { compoundButton, b ->
ok.isEnabled = extraCommandsAudio.isChecked || extraCommandsVideo.isChecked
}
commandTemplateViewModel.shortcuts.observe(lifeCycle){
shortcutsChipGroup.removeAllViews()
it.forEach {shortcut ->
@ -214,13 +266,15 @@ object UiUtil {
ok.setOnClickListener {
if (item == null){
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked)
val t = CommandTemplate(0, title.editText!!.text.toString(), content.editText!!.text.toString(), extraCommandsSwitch.isChecked, extraCommandsAudio.isChecked, extraCommandsVideo.isChecked)
commandTemplateViewModel.insert(t)
newTemplate(t)
}else{
item.title = title.editText!!.text.toString()
item.content = content.editText!!.text.toString()
item.useAsExtraCommand = extraCommandsSwitch.isChecked
item.useAsExtraCommandAudio = extraCommandsAudio.isChecked
item.useAsExtraCommandVideo = extraCommandsVideo.isChecked
Log.e("aa", item.toString())
commandTemplateViewModel.update(item)
newTemplate(item)
@ -1311,72 +1365,6 @@ object UiUtil {
@SuppressLint("ClickableViewAccessibility")
fun RecyclerView.forceFastScrollMode()
{
overScrollMode = View.OVER_SCROLL_ALWAYS
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
isVerticalScrollBarEnabled = true
setOnTouchListener { view, event ->
if (event.x >= this.width - 30) {
view.parent.requestDisallowInterceptTouchEvent(true)
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_UP -> view.parent.requestDisallowInterceptTouchEvent(false)
}
}
false
}
}
fun RecyclerView.enableFastScroll(){
val drawable = ShapeDrawable(OvalShape())
drawable.paint.color = context.getColor(android.R.color.transparent)
FastScrollerBuilder(this)
.setTrackDrawable(drawable)
.build()
}
private var textHighLightSchemes = listOf(
ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")),
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#77eb09")),
ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")),
ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564"))
)
fun View.enableTextHighlight(){
if (this is EditText || this is TextView){
//init syntax highlighter
val highlight = Highlight()
val highlightWatcher = HighlightTextWatcher()
highlight.addScheme(
*textHighLightSchemes.map { it }.toTypedArray()
)
highlightWatcher.addScheme(
*textHighLightSchemes.map { it }.toTypedArray()
)
highlight.setSpan(this as TextView)
this.addTextChangedListener(highlightWatcher)
}
}
fun EditText.setTextAndRecalculateWidth(t : String){
val scale = context.resources.displayMetrics.density
this.setText(t)
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
this.measure(widthMeasureSpec, heightMeasureSpec)
val requiredWidth: Int = this.measuredWidth
if (t.length < 5){
this.layoutParams.width = (70 * scale + 0.5F).toInt()
}else{
this.layoutParams.width = requiredWidth
}
this.requestLayout()
}
fun handleResultResponse(context: Activity, it: ResultViewModel.ResultsUiState, closed: () -> Unit){
val title = context.getString(it.errorMessage!!.first)
val message = it.errorMessage!!.second

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View file

@ -46,6 +46,7 @@
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>

View file

@ -46,6 +46,7 @@
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>

View file

@ -46,6 +46,7 @@
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>

View file

@ -46,6 +46,7 @@
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>

View file

@ -108,7 +108,7 @@
android:dividerPadding="5dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toStartOf="@+id/active_download_pause"
app:layout_constraintEnd_toStartOf="@+id/active_pause_cancel_btns"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview">
@ -151,37 +151,55 @@
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_pause"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp"
android:layout_marginEnd="5dp"
app:icon="@drawable/exomedia_ic_play_arrow_white"
app:iconSize="30dp"
app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toStartOf="@id/active_download_delete"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
app:layout_constraintVertical_bias="0.51" />
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_delete"
style="?attr/materialIconButtonFilledStyle"
<LinearLayout
android:id="@+id/active_pause_cancel_btns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp"
app:icon="@drawable/baseline_delete_24"
app:iconSize="30dp"
app:iconTint="?android:textColorPrimary"
android:orientation="horizontal"
android:animateLayoutChanges="true"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="5dp"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
app:layout_constraintVertical_bias="0.51" />
app:layout_constraintVertical_bias="0.51"
>
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_pause"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutAnimation="@anim/popup_enter"
app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp"
app:icon="@drawable/exomedia_ic_play_arrow_white"
app:iconSize="30dp"
app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toStartOf="@id/active_download_delete"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
app:layout_constraintVertical_bias="0.51" />
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_delete"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp"
app:icon="@drawable/baseline_delete_24"
app:iconSize="30dp"
app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
app:layout_constraintVertical_bias="0.51" />
</LinearLayout>
<TextView
android:id="@+id/output"

View file

@ -54,7 +54,6 @@
android:ellipsize="end"
android:text="@string/extra_command"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.core.widget.NestedScrollView
@ -103,17 +103,51 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/extraCommandsSwitch"
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="false"
android:layout_marginHorizontal="5dp"
android:textSize="15sp"
android:text="@string/extra_command"
app:layout_constraintEnd_toEndOf="parent" />
android:layout_height="wrap_content"
app:cardCornerRadius="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:orientation="vertical">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/extraCommandsSwitch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:checked="false"
android:text="@string/extra_command"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkbox_audio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:checked="true"
android:text="@string/audio" />
<CheckBox
android:id="@+id/checkbox_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/video" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<View
style="@style/Divider.Horizontal"
@ -171,5 +205,4 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -89,6 +89,8 @@
android:layout_width="wrap_content"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:layout_marginStart="10dp"
android:scrollbars="none">
@ -115,6 +117,7 @@
android:textStyle="bold"
app:cornerRadius="10dp"
app:drawableStartCompat="@drawable/ic_music"
android:textCursorDrawable="@string/audio_format"
app:drawableTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -46,6 +46,7 @@
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>

View file

@ -6,7 +6,7 @@
android:id="@+id/search_command"
android:title="@string/search"
android:icon="@drawable/ic_search"
app:showAsAction="collapseActionView|ifRoom"
app:showAsAction="always"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
<item

View file

@ -7,14 +7,14 @@
android:id="@+id/search_history"
android:title="@string/search"
android:icon="@drawable/ic_search"
app:showAsAction="collapseActionView|ifRoom"
app:showAsAction="collapseActionView|always"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
<item
android:id="@+id/download_queue"
android:title="@string/download_queue"
android:icon="@drawable/baseline_downloading_24"
app:showAsAction="collapseActionView|ifRoom"/>
app:showAsAction="always"/>
<item
android:id="@+id/remove_history"

View file

@ -1017,4 +1017,24 @@
<item>ko</item>
</string-array>
<array name="swipe_gestures">
<item>@string/downloads</item>
<item>@string/in_queue</item>
<item>@string/cancelled</item>
<item>@string/errored</item>
<item>@string/saved</item>
<item>@string/command_templates</item>
<item>@string/cookies</item>
</array>
<array name="swipe_gestures_values">
<item>history</item>
<item>queued</item>
<item>cancelled</item>
<item>errored</item>
<item>saved</item>
<item>templates</item>
<item>cookies</item>
</array>
</resources>

View file

@ -6,9 +6,11 @@
<ListPreference
android:entries="@array/language_values"
android:entryValues="@array/language_values"
android:defaultValue=""
android:icon="@drawable/baseline_translate_24"
app:key="app_language"
app:summary="en"
app:useSimpleSummaryProvider="true"
app:summary=""
app:title="@string/language" />
<ListPreference
@ -110,18 +112,19 @@
app:summary="@string/search_suggestions_summary"
app:title="@string/search_suggestions" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
<MultiSelectListPreference
android:icon="@drawable/baseline_swipe_24"
android:key="swipe_gestures"
android:key="swipe_gesture"
android:entries="@array/swipe_gestures"
android:entryValues="@array/swipe_gestures_values"
android:defaultValue="@array/swipe_gestures_values"
app:summary="@string/swipe_gestures_summary"
app:title="@string/swipe_gestures" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false"
android:icon="@drawable/baseline_share_24"
android:icon="@drawable/baseline_share2_24"
android:key="show_terminal"
app:title="@string/show_terminal" />

View file

@ -21,7 +21,7 @@ buildscript {
coroutineVer = '1.7.3'
retrofitVer = "2.9.0"
kodeinVer = "7.16.0"
navVer = "2.7.4"
navVer = "2.7.5"
media3_version = "1.1.1"
agp_version = '8.1.0'
agp_version1 = '7.4.2'