1.7.2 pre

- Increased the height of the history card a slight bit
- Fixed app not using the -S format sorter in normal GUI downloads
- Added the multiple download card as an option to disable the swipe gestures
- Added a recyclerview to the search results because they were buggy before and would not show sometimes
- Added a new history item card design in cases if you quick-downloaded a playlist or if you made multiple cuts of a file. Instead of being separate, they are bundled and you can share them all at once and if you want to open the file a new dialog will show telling you to choose which one. If any of them is deleted, they are greyed out
- Fixed the shortcuts behaviour by allowing duplicate shortcuts to be added to textbox and to not check
- Added format filtering in the format list bottom sheet. You can filter between ALL, SUGGESTED, SMALLEST, GENERIC. Suggested formats are built upon your app preferences like your preferred codec, container, quality. Smallest formats are all formats for each resolution with the smallest filesize. Generic are formats you see if an item has not updated the formats yet, in case you want to use those for some reason
- Added clipboard button right in the home screen for quick access to the url you copied instead of going to the searchview. That item is also available there aswell
- Added ability to show the URL in a history item if the download produced no title for it
- Added zoom-in zoom-out slider for logs and terminal screens.
- Added more options of datafetching for titles and authors, like track, alt_title to avoid the default youtube naming scheme when available. Useful for audio downloads
- Since some people are confused. In the download card each tab item is independent and they each create a distinct download item and when you press download the app picks from the tab you are currently. If you want to select the audio format in the video tab you can just open the format list and scroll to the bottom for the audio formats. You can select multiple if you want and then press ok. But since some people thought that selecting the audio in audio tab would apply to the video tab, i added the ability to synchronise that format automatically in case people dont know about this lol
- Other small bug fixes
This commit is contained in:
zaednasr 2024-01-14 12:23:49 +01:00
parent e294ca86d5
commit dd3578bc48
No known key found for this signature in database
GPG key ID: 92B1DE23AD3D0E9E
99 changed files with 2854 additions and 1436 deletions

3
.gitignore vendored
View file

@ -6,4 +6,5 @@
/build
/captures
.externalNativeBuild
/benchmark
/benchmark
*.png

View file

@ -10,7 +10,7 @@ plugins {
def properties = new Properties()
def versionMajor = 1
def versionMinor = 7
def versionPatch = 1
def versionPatch = 2
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
def versionExt = ""

View file

@ -0,0 +1,598 @@
{
"formatVersion": 1,
"database": {
"version": 14,
"identityHash": "5cea521e1a26473b2ec5298791f10ac4",
"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": []
}
],
"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, '5cea521e1a26473b2ec5298791f10ac4')"
]
}
}

View file

@ -15,6 +15,12 @@ class Converters {
@TypeConverter
fun listOfFormatsToString(list: List<Format?>?) = Gson().toJson(list).toString()
@TypeConverter
fun stringToListOfStrings(value: String) = Gson().fromJson(value, Array<String>::class.java).toList()
@TypeConverter
fun listOfStringsToString(list: List<String>) = Gson().toJson(list).toString()
@TypeConverter
fun formatToString(format: Format): String = Gson().toJson(format)

View file

@ -23,7 +23,7 @@ import com.deniscerri.ytdlnis.database.models.*
LogItem::class,
TerminalItem::class
],
version = 13,
version = 14,
autoMigrations = [
AutoMigration (from = 1, to = 2),
AutoMigration (from = 2, to = 3),
@ -36,7 +36,8 @@ import com.deniscerri.ytdlnis.database.models.*
AutoMigration (from = 9, to = 10),
AutoMigration (from = 10, to = 11),
AutoMigration (from = 11, to = 12),
AutoMigration (from = 12, to = 13)
AutoMigration (from = 12, to = 13),
//AutoMigration (from = 13, to = 14), MANUALLY HANDLED
]
)
abstract class DBManager : RoomDatabase(){
@ -66,6 +67,7 @@ abstract class DBManager : RoomDatabase(){
DBManager::class.java,
"YTDLnisDatabase"
)
.addMigrations(*Migrations.migrationList)
.build()
instance = dbInstance
dbInstance

View file

@ -0,0 +1,57 @@
package com.deniscerri.ytdlnis.database
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.content.res.Resources
import android.graphics.Color
import android.graphics.Outline
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.OvalShape
import android.media.MediaMetadataRetriever
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
import android.net.Uri
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.Px
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.withStarted
import androidx.recyclerview.widget.RecyclerView
import androidx.room.migration.Migration
import androidx.work.impl.Migration_12_13
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.io.File
import java.util.regex.Pattern
object Migrations {
@SuppressLint("Range")
val migrationList = arrayOf(
//Moving from one file path to multiple file paths of a history item
Migration(13, 14){database ->
val cursor = database.query("SELECT * FROM history")
while(cursor.moveToNext()) {
val id = cursor.getLong(cursor.getColumnIndex("id"))
val path = cursor.getString(cursor.getColumnIndex("downloadPath"))
val newPath = "[\"${path.replace("\"", "\\\"").replace("'", "''")}\"]"
database.execSQL("UPDATE history SET downloadPath = '${newPath}' WHERE id = $id")
}
}
)
}

View file

@ -138,6 +138,9 @@ interface DownloadDao {
@Upsert
suspend fun update(item: DownloadItem)
@Update
suspend fun updateWithoutUpsert(item: DownloadItem)
@Query("UPDATE downloads SET logID=null")
fun removeAllLogID()

View file

@ -16,7 +16,7 @@ data class HistoryItem(
val thumb: String,
val type: DownloadViewModel.Type,
val time: Long,
val downloadPath: String,
val downloadPath: List<String>,
val website: String,
val format: Format,
@ColumnInfo(defaultValue = "0")

View file

@ -0,0 +1,15 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
data class SearchSuggestionItem(
var text: String,
val type: SearchSuggestionType,
)
enum class SearchSuggestionType{
SUGGESTION, HISTORY, CLIPBOARD
}

View file

@ -66,6 +66,10 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.update(item)
}
suspend fun updateWithoutUpsert(item: DownloadItem){
kotlin.runCatching { downloadDao.updateWithoutUpsert(item) }
}
suspend fun setDownloadStatus(item: DownloadItem, status: Status){
item.status = status.toString()

View file

@ -48,14 +48,18 @@ class HistoryRepository(private val historyDao: HistoryDao) {
suspend fun delete(item: HistoryItem, deleteFile: Boolean){
historyDao.delete(item.id)
if (deleteFile){
FileUtil.deleteFile(item.downloadPath)
item.downloadPath.forEach {
FileUtil.deleteFile(it)
}
}
}
suspend fun deleteAll(deleteFile: Boolean = false){
if (deleteFile){
historyDao.getAllHistoryList().forEach { item ->
FileUtil.deleteFile(item.downloadPath)
item.downloadPath.forEach {
FileUtil.deleteFile(it)
}
}
}
historyDao.deleteAll()
@ -72,7 +76,7 @@ class HistoryRepository(private val historyDao: HistoryDao) {
suspend fun clearDeletedHistory(){
items.collectLatest {
it.forEach { item ->
if (!FileUtil.exists(item.downloadPath)){
if (item.downloadPath.all { path -> !FileUtil.exists(path) }){
historyDao.delete(item.id)
}
}

View file

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.database.viewmodel
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
@ -424,8 +425,15 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val audioPreferences = AudioPreferences(embedThumb, cropThumb,false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs)
val downloadPath = File(historyItem.downloadPath)
val path = if (downloadPath.exists()) downloadPath.parent else defaultPath
var path = defaultPath
historyItem.downloadPath.first().apply {
File(this).parent?.apply {
if (File(this).exists()){
path = this
}
}
}
return DownloadItem(0,
historyItem.url,
historyItem.title,
@ -443,23 +451,37 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
fun getPreferredAudioRequirements(): MutableList<(Format) -> Boolean> {
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
requirements.add {it: Format -> audioFormatIDPreference.contains(it.format_id)}
sharedPreferences.getString("audio_language", "")?.apply {
if (this.isNotBlank()){
requirements.add { it: Format -> it.lang == this }
}
}
requirements.add {it: Format -> it.container == audioContainer }
requirements.add {it: Format -> "^(${audioCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.acodec)}
return requirements
}
fun getPreferredVideoRequirements(): MutableList<(Format) -> Boolean> {
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
requirements.add { it: Format -> formatIDPreference.contains(it.format_id) }
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.split("_")[0].dropLast(1)) }
requirements.add { it: Format -> "^(${videoCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)}
return requirements
}
fun getFormat(formats: List<Format>, type: Type) : Format {
when(type) {
Type.audio -> {
return cloneFormat (
try {
val theFormats = formats.filter { it.format_note.contains("audio", ignoreCase = true) }
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
requirements.add {it: Format -> audioFormatIDPreference.contains(it.format_id)}
sharedPreferences.getString("audio_language", "")?.apply {
if (this.isNotBlank()){
requirements.add { it: Format -> it.lang == this }
}
}
requirements.add {it: Format -> it.container == audioContainer }
requirements.add {it: Format -> "^(${audioCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.acodec)}
val requirements = getPreferredAudioRequirements()
theFormats.maxByOrNull { f -> requirements.count{req -> req(f)} } ?: throw Exception()
}catch (e: Exception){
bestAudioFormat
@ -476,11 +498,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
theFormats.last()
}
else /*best*/ -> {
val requirements: MutableList<(Format) -> Boolean> = mutableListOf()
requirements.add { it: Format -> formatIDPreference.contains(it.format_id) }
requirements.add { it: Format -> if (videoContainer == "mp4") it.container.equals("mpeg_4", true) else it.container.equals(videoContainer, true)}
requirements.add { it: Format -> it.format_note.contains(videoQualityPreference.split("_")[0].dropLast(1)) }
requirements.add { it: Format -> "^(${videoCodec}).+$".toRegex(RegexOption.IGNORE_CASE).matches(it.vcodec)}
val requirements = getPreferredVideoRequirements()
theFormats.maxByOrNull { f -> requirements.count{ req -> req(f)} } ?: throw Exception()
}
}
@ -677,7 +695,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}else{
//check if downloaded and file exists
val history = withContext(Dispatchers.IO){
historyRepository.getAllByURL(it.url).filter { item -> FileUtil.exists(item.downloadPath) }
historyRepository.getAllByURL(it.url).filter { item -> item.downloadPath.any { path -> FileUtil.exists(path) } }
}
val existingHistory = history.firstOrNull {
@ -733,6 +751,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
}
}
@SuppressLint("RestrictedApi")
private suspend fun startDownloadWorker(queuedItems: List<DownloadItem>) {
val context = App.instance
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
@ -812,7 +831,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
if (downloadItem.thumb.isEmpty()) downloadItem.thumb = info?.thumb.toString()
runBlocking {
wasQuickDownloaded = resultDao.getCountInt() == 0
dao.update(downloadItem)
repository.updateWithoutUpsert(downloadItem)
}
}
}

View file

@ -116,7 +116,7 @@ class ResultViewModel(private val application: Application) : AndroidViewModel(a
}
}
if (!isForegrounded()){
if (!isForegrounded() && inputQueries.size > 1){
notificationUtil.showQueriesFinished()
}
uiState.update {it.copy(processing = false)}

View file

@ -28,18 +28,21 @@ import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.models.SearchSuggestionItem
import com.deniscerri.ytdlnis.database.models.SearchSuggestionType
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.adapter.HomeAdapter
import com.deniscerri.ytdlnis.ui.adapter.SearchSuggestionsAdapter
import com.deniscerri.ytdlnis.util.Extensions.enableFastScroll
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.ThemeUtil
@ -53,6 +56,7 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import kotlinx.coroutines.CoroutineScope
@ -64,17 +68,15 @@ import kotlinx.coroutines.withContext
import java.util.*
class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListener {
class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, SearchSuggestionsAdapter.OnItemClickListener, OnClickListener {
private var inputQueries: MutableList<String>? = null
private var homeAdapter: HomeAdapter? = null
private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null
private var queriesConstraint: ConstraintLayout? = null
private var searchSuggestions: ScrollView? = null
private var searchSuggestionsLinearLayout: LinearLayout? = null
private var searchHistory: ScrollView? = null
private var searchHistoryLinearLayout: LinearLayout? = null
private var downloadFabs: CoordinatorLayout? = null
private var downloadAllFabCoordinator: CoordinatorLayout? = null
private var downloadSelectedFab: ExtendedFloatingActionButton? = null
private var downloadAllFab: ExtendedFloatingActionButton? = null
private var clipboardFab: FloatingActionButton? = null
private var homeFabs: CoordinatorLayout? = null
private var infoUtil: InfoUtil? = null
private var downloadQueue: ArrayList<ResultItem>? = null
@ -92,9 +94,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
private var searchView: SearchView? = null
private var providersChipGroup: ChipGroup? = null
private var chipGroupDivider: View? = null
private var linkYouCopied: ConstraintLayout? = null
private var queriesChipGroup: ChipGroup? = null
private var recyclerView: RecyclerView? = null
private var searchSuggestionsRecyclerView: RecyclerView? = null
private var uiHandler: Handler? = null
private var resultsList: List<ResultItem?>? = null
private var selectedObjects: ArrayList<ResultItem>? = null
@ -136,17 +138,14 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
shimmerCards = view.findViewById(R.id.shimmer_results_framelayout)
searchBar = view.findViewById(R.id.search_bar)
searchView = view.findViewById(R.id.search_view)
linkYouCopied = searchView?.findViewById(R.id.link_you_copied)
appBarLayout = view.findViewById(R.id.home_appbarlayout)
materialToolbar = view.findViewById(R.id.home_toolbar)
queriesChipGroup = view.findViewById(R.id.queries)
searchSuggestions = view.findViewById(R.id.search_suggestions_scroll_view)
searchSuggestionsLinearLayout = view.findViewById(R.id.search_suggestions_linear_layout)
searchHistory = view.findViewById(R.id.search_history_scroll_view)
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
queriesConstraint = view.findViewById(R.id.queries_constraint)
homeFabs = view.findViewById(R.id.home_fabs)
downloadFabs = homeFabs!!.findViewById(R.id.download_selected_coordinator)
downloadAllFabCoordinator = homeFabs!!.findViewById(R.id.download_all_coordinator)
downloadSelectedFab = homeFabs!!.findViewById(R.id.download_selected_fab)
downloadAllFab = homeFabs!!.findViewById(R.id.download_all_fab)
clipboardFab = homeFabs!!.findViewById(R.id.copied_url_fab)
runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) }
@ -160,6 +159,17 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
recyclerView?.adapter = homeAdapter
recyclerView?.enableFastScroll()
searchSuggestionsAdapter = SearchSuggestionsAdapter(
this,
requireActivity()
)
searchSuggestionsRecyclerView = view.findViewById(R.id.search_suggestions_recycler)
searchSuggestionsRecyclerView?.layoutManager = LinearLayoutManager(context)
searchSuggestionsRecyclerView?.adapter = searchSuggestionsAdapter
searchSuggestionsRecyclerView?.enableFastScroll()
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
resultViewModel.items.observe(requireActivity()) {
kotlin.runCatching {
@ -167,9 +177,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
resultsList = it
if(resultViewModel.repository.itemCount.value > 1 || resultViewModel.repository.itemCount.value == -1){
if (it.size > 1 && it[0].playlistTitle.isNotEmpty() && !loadingItems){
downloadAllFabCoordinator!!.visibility = VISIBLE
downloadAllFab!!.visibility = VISIBLE
}else{
downloadAllFabCoordinator!!.visibility = GONE
downloadAllFab!!.visibility = GONE
}
}else if (resultViewModel.repository.itemCount.value == 1){
if (sharedPreferences!!.getBoolean("download_card", true)){
@ -181,20 +191,17 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
}
}else{
downloadAllFabCoordinator!!.visibility = GONE
downloadAllFab!!.visibility = GONE
}
quickLaunchSheet = true
}
}
initMenu()
val downloadSelectedFab = downloadFabs!!.findViewById<ExtendedFloatingActionButton>(R.id.download_selected_fab)
downloadSelectedFab.tag = "downloadSelected"
downloadSelectedFab.setOnClickListener(this)
val downloadAllFab =
downloadAllFabCoordinator!!.findViewById<ExtendedFloatingActionButton>(R.id.download_all_fab)
downloadAllFab.tag = "downloadAll"
downloadAllFab.setOnClickListener(this)
downloadSelectedFab?.tag = "downloadSelected"
downloadSelectedFab?.setOnClickListener(this)
downloadAllFab?.tag = "downloadAll"
downloadAllFab?.setOnClickListener(this)
if (arguments?.getString("url") != null){
val url = requireArguments().getString("url")
@ -247,9 +254,9 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
shimmerCards!!.stopShimmer()
shimmerCards!!.visibility = GONE
if (resultsList!!.size > 1 && resultsList!![0]!!.playlistTitle.isNotEmpty()){
downloadAllFabCoordinator!!.visibility = VISIBLE
this@HomeFragment.downloadAllFab!!.visibility = VISIBLE
}else{
downloadAllFabCoordinator!!.visibility = GONE
this@HomeFragment.downloadAllFab!!.visibility = GONE
}
}
}
@ -286,7 +293,18 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
if (searchView?.currentTransitionState == SearchView.TransitionState.SHOWN){
updateSearchViewItems(searchView?.editText?.text, linkYouCopied)
updateSearchViewItems(searchView?.editText?.text)
}
requireView().post {
checkClipboard().apply {
clipboardFab?.isVisible = this != null
clipboardFab?.setOnClickListener {
searchView!!.setText(this)
clipboardFab?.isVisible = false
initSearch(searchView!!)
}
}
}
}
@ -339,53 +357,13 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
chipGroupDivider?.visibility = VISIBLE
}
try{
val clipboard =
requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text
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)
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_language, 0, 0, 0)
val mb = linkYouCopied!!.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setImageResource(R.drawable.ic_plus)
mb.setOnClickListener {
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == clip.toString() }
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = clip.toString()
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
queriesChipGroup!!.removeView(chip)
}
queriesChipGroup!!.addView(chip)
}
if (queriesChipGroup!!.childCount == 0) queriesConstraint.visibility = GONE
else queriesConstraint.visibility = VISIBLE
searchView!!.editText.setText("")
linkYouCopied!!.visibility = GONE
}
textView.setOnClickListener {
searchView!!.setText(clip.toString())
initSearch(searchView!!)
}
}else{
linkYouCopied!!.visibility = GONE
}
updateSearchViewItems(searchView!!.editText.text, linkYouCopied)
}catch (e: Exception){
e.printStackTrace()
linkYouCopied!!.visibility = GONE
}
updateSearchViewItems(searchView!!.editText.text)
}
}
searchView!!.editText.doAfterTextChanged {
if (searchView!!.currentTransitionState != SearchView.TransitionState.SHOWN) return@doAfterTextChanged
updateSearchViewItems(it, linkYouCopied)
updateSearchViewItems(it)
}
searchView!!.editText.setOnTouchListener(OnTouchListener { _, event ->
@ -405,6 +383,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
queriesChipGroup!!.removeView(chip)
if (queriesChipGroup!!.childCount == 0) queriesConstraint.visibility = GONE
}
queriesChipGroup!!.addView(chip)
}
@ -430,12 +409,12 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
resultViewModel.getTrending()
selectedObjects = ArrayList()
searchBar!!.setText("")
downloadAllFabCoordinator!!.visibility = GONE
downloadFabs!!.visibility = GONE
downloadAllFab!!.visibility = GONE
downloadSelectedFab!!.visibility = GONE
}
R.id.delete_search -> {
resultViewModel.deleteAllSearchQueryHistory()
searchSuggestionsLinearLayout!!.removeAllViews()
searchSuggestionsAdapter?.submitList(listOf())
}
}
true
@ -451,26 +430,20 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
@SuppressLint("InflateParams")
private fun updateSearchViewItems(searchQuery: Editable?, linkYouCopied: View?) = lifecycleScope.launch(Dispatchers.Main) {
private fun updateSearchViewItems(searchQuery: Editable?) = lifecycleScope.launch(Dispatchers.Main) {
lifecycleScope.launch {
searchSuggestionsLinearLayout!!.visibility = GONE
searchHistoryLinearLayout!!.visibility = GONE
searchSuggestionsLinearLayout!!.post {
searchSuggestionsLinearLayout!!.removeAllViews()
}
searchHistoryLinearLayout!!.post {
searchHistoryLinearLayout!!.removeAllViews()
}
linkYouCopied!!.visibility = GONE
if (searchView!!.editText.text.isEmpty()){
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
}else{
searchView!!.editText.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_plus, 0)
}
val combinedList = mutableListOf<SearchSuggestionItem>()
val history = withContext(Dispatchers.IO){
resultViewModel.getSearchHistory().map { it.query }.filter { it.contains(searchQuery!!) }
}.map {
SearchSuggestionItem(it, SearchSuggestionType.HISTORY)
}
val suggestions = if (sharedPreferences!!.getBoolean("search_suggestions", false)){
withContext(Dispatchers.IO){
@ -478,66 +451,80 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
}else{
emptyList()
}.map {
SearchSuggestionItem(it, SearchSuggestionType.SUGGESTION)
}
history.forEach { s ->
val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null)
val textView = v.findViewById<TextView>(R.id.suggestion_text)
textView.text = s
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
Handler(Looper.getMainLooper()).post {
searchHistoryLinearLayout!!.addView(
v
)
}
textView.setOnClickListener {
searchView!!.setText(s)
initSearch(searchView!!)
}
textView.setOnLongClickListener {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
searchHistoryLinearLayout!!.removeView(v)
resultViewModel.removeSearchQueryFromHistory(s)
}
deleteDialog.show()
true
}
combinedList.addAll(history)
combinedList.addAll(suggestions)
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
searchView!!.editText.setText(s)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}
searchHistoryLinearLayout!!.isVisible = history.isNotEmpty()
if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
linkYouCopied.visibility = VISIBLE
val url = checkClipboard()
url?.apply {
combinedList.add(0, SearchSuggestionItem(this, SearchSuggestionType.CLIPBOARD))
}
suggestions.forEach { s ->
val v = LayoutInflater.from(fragmentContext)
.inflate(R.layout.search_suggestion_item, null)
val textView = v.findViewById<TextView>(R.id.suggestion_text)
textView.text = s
Handler(Looper.getMainLooper()).post {
searchSuggestionsLinearLayout!!.addView(
v
)
}
textView.setOnClickListener {
searchView!!.setText(s)
initSearch(searchView!!)
}
val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
searchView!!.editText.setText(s)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}
searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty()
searchSuggestionsAdapter?.submitList(combinedList)
// history.forEach { s ->
// val v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null)
// val textView = v.findViewById<TextView>(R.id.suggestion_text)
// textView.text = s
// textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
// Handler(Looper.getMainLooper()).post {
// searchHistoryLinearLayout!!.addView(
// v
// )
// }
// textView.setOnClickListener {
// searchView!!.setText(s)
// initSearch(searchView!!)
// }
// textView.setOnLongClickListener {
// val deleteDialog = MaterialAlertDialogBuilder(requireContext())
// deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + s + "\"!")
// deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
// deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
// searchHistoryLinearLayout!!.removeView(v)
// resultViewModel.removeSearchQueryFromHistory(s)
// }
// deleteDialog.show()
// true
// }
//
// val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
// mb.setOnClickListener {
// searchView!!.editText.setText(s)
// searchView!!.editText.setSelection(searchView!!.editText.length())
// }
// }
// searchHistoryLinearLayout!!.isVisible = history.isNotEmpty()
// if (linkYouCopied.findViewById<TextView>(R.id.suggestion_text).text.isNotEmpty()){
// linkYouCopied.visibility = VISIBLE
// clipboardFab?.isVisible = true
// }
//
// suggestions.forEach { s ->
// val v = LayoutInflater.from(fragmentContext)
// .inflate(R.layout.search_suggestion_item, null)
// val textView = v.findViewById<TextView>(R.id.suggestion_text)
// textView.text = s
// Handler(Looper.getMainLooper()).post {
// searchSuggestionsLinearLayout!!.addView(
// v
// )
// }
// textView.setOnClickListener {
// searchView!!.setText(s)
// initSearch(searchView!!)
// }
// val mb = v.findViewById<ImageButton>(R.id.set_search_query_button)
// mb.setOnClickListener {
// searchView!!.editText.setText(s)
// searchView!!.editText.setSelection(searchView!!.editText.length())
// }
// }
// searchSuggestionsLinearLayout!!.isVisible = suggestions.isNotEmpty()
if (Patterns.WEB_URL.matcher(searchView!!.editText.text).matches()){
providersChipGroup?.visibility = GONE
@ -637,6 +624,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
showSingleDownloadSheet(item!!, type!!)
}
@SuppressLint("RestrictedApi")
private fun showSingleDownloadSheet(
resultItem: ResultItem,
type: DownloadViewModel.Type
@ -807,6 +795,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
private fun checkClipboard(): String?{
return kotlin.runCatching {
val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = clipboard.primaryClip!!.getItemAt(0).text
val matchesURL = Patterns.WEB_URL.matcher(clip.toString()).matches()
return if (matchesURL) clip.toString() else null
}.getOrNull()
}
override fun onStop() {
actionMode?.finish()
super.onStop()
@ -816,4 +813,49 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
companion object {
private const val TAG = "HomeFragment"
}
override fun onSearchSuggestionClick(text: String) {
clipboardFab?.isVisible = false
searchView!!.setText(text)
initSearch(searchView!!)
}
override fun onSearchSuggestionAdd(text: String) {
val present = queriesChipGroup!!.children.firstOrNull { (it as Chip).text.toString() == text }
if (present == null) {
val chip = layoutinflater!!.inflate(R.layout.input_chip, queriesChipGroup, false) as Chip
chip.text = text
chip.chipBackgroundColor = ColorStateList.valueOf(MaterialColors.getColor(requireContext(), R.attr.colorSecondaryContainer, Color.BLACK))
chip.setOnClickListener {
queriesChipGroup!!.removeView(chip)
if (queriesChipGroup!!.childCount == 0) queriesChipGroup!!.visibility = View.GONE
}
queriesChipGroup!!.addView(chip)
}
if (queriesChipGroup!!.childCount == 0) queriesConstraint!!.visibility = View.GONE
else queriesConstraint!!.visibility = View.VISIBLE
searchView!!.editText.setText("")
val clipBoardItem = searchSuggestionsRecyclerView?.layoutManager?.findViewByPosition(0)
clipBoardItem?.apply {
if ((this as ConstraintLayout).findViewById<TextView>(R.id.suggestion_text).text == getString(R.string.link_you_copied)){
searchSuggestionsAdapter?.notifyItemRemoved(0)
}
}
}
override fun onSearchSuggestionLongClick(text: String, position: Int) {
val deleteDialog = MaterialAlertDialogBuilder(requireContext())
deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + text + "\"!")
deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
resultViewModel.removeSearchQueryFromHistory(text)
searchSuggestionsAdapter?.notifyItemRemoved(position)
}
deleteDialog.show()
}
override fun onSearchSuggestionAddToSearchBar(text: String) {
searchView!!.editText.setText(text)
searchView!!.editText.setSelection(searchView!!.editText.length())
}
}

View file

@ -8,12 +8,13 @@ import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
@ -23,7 +24,11 @@ import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.card.MaterialCardView
import com.google.android.material.color.MaterialColors
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.squareup.picasso.Picasso
import java.io.File
@ -54,10 +59,28 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryAdapter.ViewHolder {
return if (viewType == 0){
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.history_card, parent, false)
return ViewHolder(cardView, onItemClickListener)
ViewHolder(cardView, onItemClickListener)
}else{
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.history_card_multiple, parent, false)
ViewHolder(cardView, onItemClickListener)
}
}
override fun getItemViewType(position: Int): Int {
getItem(position)?.apply {
return if(this.downloadPath.size == 1){
0
}else{
1
}
}
return 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -82,7 +105,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.downloads_title)
var title = item!!.title
var title = item!!.title.ifEmpty { item.url }
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
@ -98,44 +121,38 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
// TIME DOWNLOADED ----------------------------------
val datetime = card.findViewById<TextView>(R.id.downloads_info_time)
// val relativeTime = DateUtils.getRelativeTimeSpanString(
// item.time * 1000L,
// System.currentTimeMillis(),
// DateUtils.MINUTE_IN_MILLIS
// )
datetime.text = SimpleDateFormat(android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()).format(item.time * 1000L)
// BUTTON ----------------------------------
val btn = card.findViewById<FloatingActionButton>(R.id.downloads_download_button_type)
var filePresent = true
var filesPresent = true
//IS IN THE FILE SYSTEM?
val path = item.downloadPath
val file = File(path)
if (!file.exists() && path.isNotEmpty()) {
filePresent = false
if (item.downloadPath.all { !File(it).exists() && it.isNotBlank()}) {
filesPresent = false
thumbnail.colorFilter = ColorMatrixColorFilter(object : ColorMatrix() {
init {
setSaturation(0f)
}
})
thumbnail.alpha = 0.7f
btn.backgroundTintList = ContextCompat.getColorStateList(activity, R.color.black)
btn.imageTintList = ContextCompat.getColorStateList(activity, R.color.white)
btn.backgroundTintList = MaterialColors.getColorStateList(activity, R.attr.colorSurface, ContextCompat.getColorStateList(activity, android.R.color.transparent)!!)
}else{
thumbnail.alpha = 1f
btn.backgroundTintList = MaterialColors.getColorStateList(activity, R.attr.colorPrimaryContainer, ContextCompat.getColorStateList(activity, android.R.color.transparent)!!)
}
if (item.type == DownloadViewModel.Type.audio) {
if (filePresent) btn.setImageResource(R.drawable.ic_music_downloaded) else {
if (filesPresent) btn.setImageResource(R.drawable.ic_music_downloaded) else {
btn.setImageResource(R.drawable.ic_music)
}
} else if (item.type == DownloadViewModel.Type.video) {
if (filePresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
if (filesPresent) btn.setImageResource(R.drawable.ic_video_downloaded) else btn.setImageResource(R.drawable.ic_video)
}else{
if (filePresent) btn.setImageResource(R.drawable.ic_terminal) else btn.setImageResource(R.drawable.baseline_code_off_24)
if (filesPresent) btn.setImageResource(R.drawable.ic_terminal) else btn.setImageResource(R.drawable.baseline_code_off_24)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = filePresent
btn.isClickable = filesPresent
if (checkedItems.contains(item.id)) {
card.isChecked = true
@ -144,7 +161,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
card.isChecked = false
card.strokeWidth = 0
}
val finalFilePresent = filePresent
val finalFilePresent = filesPresent
card.setOnLongClickListener {
checkCard(card, item.id)
true

View file

@ -0,0 +1,126 @@
package com.deniscerri.ytdlnis.ui.adapter
import android.app.Activity
import android.content.SharedPreferences
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.SearchSuggestionItem
import com.deniscerri.ytdlnis.database.models.SearchSuggestionType
class SearchSuggestionsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<SearchSuggestionItem, SearchSuggestionsAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
).build()) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
private val sharedPreferences: SharedPreferences
init {
this.onItemClickListener = onItemClickListener
this.activity = activity
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val linear: LinearLayout
init {
linear = itemView.findViewById(R.id.linear)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchSuggestionsAdapter.ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.search_suggestion_item, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) ?: return
val linear = holder.linear
when (item.type){
SearchSuggestionType.SUGGESTION -> {
val textView = linear.findViewById<TextView>(R.id.suggestion_text)
textView.text = item.text
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_search, 0, 0, 0)
textView.setOnClickListener {
onItemClickListener.onSearchSuggestionClick(item.text)
}
textView.setOnLongClickListener { true }
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
}
}
SearchSuggestionType.HISTORY -> {
val textView = linear.findViewById<TextView>(R.id.suggestion_text)
textView.text = item.text
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_restore, 0, 0, 0)
textView.setOnClickListener {
onItemClickListener.onSearchSuggestionClick(item.text)
}
textView.setOnLongClickListener {
onItemClickListener.onSearchSuggestionLongClick(item.text, position)
true
}
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setOnClickListener {
onItemClickListener.onSearchSuggestionAddToSearchBar(item.text)
}
}
SearchSuggestionType.CLIPBOARD -> {
val textView = linear.findViewById<TextView>(R.id.suggestion_text)
textView.text = activity.getString(R.string.link_you_copied)
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_language, 0, 0, 0)
val mb = linear.findViewById<ImageButton>(R.id.set_search_query_button)
mb.setImageResource(R.drawable.ic_plus)
mb.setOnClickListener {
onItemClickListener.onSearchSuggestionAdd(item.text)
}
textView.setOnClickListener {
onItemClickListener.onSearchSuggestionClick(item.text)
}
textView.setOnLongClickListener { true }
}
}
}
interface OnItemClickListener {
fun onSearchSuggestionClick(text: String)
fun onSearchSuggestionAdd(text: String)
fun onSearchSuggestionLongClick(text: String, position: Int)
fun onSearchSuggestionAddToSearchBar(text: String)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<SearchSuggestionItem> = object : DiffUtil.ItemCallback<SearchSuggestionItem>() {
override fun areItemsTheSame(oldItem: SearchSuggestionItem, newItem: SearchSuggestionItem): Boolean {
return oldItem.text == newItem.text && oldItem.type == newItem.type
}
override fun areContentsTheSame(oldItem: SearchSuggestionItem, newItem: SearchSuggestionItem): Boolean {
return oldItem.text == newItem.text && oldItem.type == newItem.type
}
}
}
}

View file

@ -161,7 +161,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
putString("last_used_download_type",
listOf(Type.audio, Type.video, Type.command)[position].toString())
}
updateTitleAuthorWhenSwitching()
updateWhenSwitching()
}
}
})
@ -205,7 +205,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
}
private fun updateTitleAuthorWhenSwitching(){
private fun updateWhenSwitching(){
val prevDownloadItem = getDownloadItem(
if (viewPager2.currentItem == 1) 0 else 1
)
@ -223,6 +223,7 @@ class ConfigureDownloadBottomSheetDialog(private var result: ResultItem, private
1 -> {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
f.updateSelectedAudioFormat(getDownloadItem(0).format)
}
else -> {}
}

View file

@ -70,8 +70,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
): View? {
fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext())
genericAudioFormats = infoUtil.getGenericAudioFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -295,7 +295,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem? = null, private
updateDataClicked = {
CoroutineScope(Dispatchers.IO).launch {
resultItem?.apply {
resultViewModel.updateItemData(this)
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
rsVM.updateItemData(this)
}
}
},

View file

@ -9,29 +9,25 @@ 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
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.Player.Command
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
@ -246,6 +242,9 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
if (tab!!.position == 2 && commandTemplateNr == 0){
tabLayout.selectTab(tabLayout.getTabAt(1))
val s = Snackbar.make(view, getString(R.string.add_template_first), Snackbar.LENGTH_LONG)
val snackbarView: View = s.view
val snackTextView = snackbarView.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView
snackTextView.maxLines = 9999999
s.setAction(R.string.new_template){
UiUtil.showCommandTemplateCreationOrUpdatingSheet(item = null, context = requireActivity(), lifeCycle = this@DownloadBottomSheetDialog, commandTemplateViewModel = commandTemplateViewModel){
commandTemplateNr = 1
@ -279,7 +278,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
putString("last_used_download_type",
listOf(Type.audio, Type.video, Type.command)[position].toString())
}
updateTitleAuthorWhenSwitching()
updateWhenSwitching()
}
}
})
@ -437,6 +436,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
shimmerLoadingSubtitle.visibility = View.VISIBLE
shimmerLoading.startShimmer()
shimmerLoadingSubtitle.startShimmer()
updateItem.visibility = View.GONE
}else{
title.visibility = View.VISIBLE
subtitle.visibility = View.VISIBLE
@ -600,7 +600,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
}
}
private fun updateTitleAuthorWhenSwitching(){
private fun updateWhenSwitching(){
val prevDownloadItem = getDownloadItem(
if (viewPager2.currentItem == 1) 0 else 1
)
@ -617,6 +617,7 @@ class DownloadBottomSheetDialog : BottomSheetDialogFragment() {
kotlin.runCatching {
val f = fragmentManager?.findFragmentByTag("f1") as DownloadVideoFragment
f.updateTitleAuthor(prevDownloadItem.title, prevDownloadItem.author)
f.updateSelectedAudioFormat(getDownloadItem(0).format)
}
}
2 -> {

View file

@ -12,6 +12,7 @@ import android.util.Patterns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
@ -123,6 +124,13 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
}
})
chosenCommandView.editText!!.setSelection(chosenCommandView.editText!!.text.length)
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
chosenCommandView.editText!!.postDelayed({
chosenCommandView.editText!!.requestFocus()
imm.showSoftInput(chosenCommandView.editText, 0)
}, 300)
chosenCommandView.setEndIconOnClickListener {
if(chosenCommandView.editText!!.text.isEmpty()){
val clipboard: ClipboardManager =
@ -252,8 +260,9 @@ class DownloadCommandFragment(private val resultItem: ResultItem? = null, privat
shortcutClicked = {
UiUtil.showShortcuts(requireActivity(), commandTemplateViewModel,
itemSelected = {
chosenCommandView.editText!!.setText("${chosenCommandView.editText!!.text} $it")
downloadItem.format.format_note = chosenCommandView.editText!!.text.toString()
val selectionStart = chosenCommandView.editText!!.selectionStart
chosenCommandView.editText!!.text.insert(selectionStart, it)
chosenCommandView.editText!!.setSelection(selectionStart + it.length)
preferences.edit().putString("lastCommandTemplateUsed", downloadItem.format.format_note).apply()
},
itemRemoved = {removed ->

View file

@ -33,6 +33,7 @@ import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.afollestad.materialdialogs.utils.MDUtil.getStringArray
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.adapter.ConfigureMultipleDownloadsAdapter
@ -136,7 +137,9 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = listAdapter
recyclerView.enableFastScroll()
if(sharedPreferences.getBoolean("swipe_gestures", true)){
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
if (preferences.getStringSet("swipe_gesture", requireContext().getStringArray(R.array.swipe_gestures_values).toSet())!!.toList().contains("multipledownloadcard")){
val itemTouchHelper = ItemTouchHelper(simpleCallback)
itemTouchHelper.attachToRecyclerView(recyclerView)
}
@ -603,7 +606,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
items.forEach {
it.downloadPath = result.data?.data.toString()
}
Toast.makeText(requireContext(), "Changed every item's download path to: ${FileUtil.formatPath(result.data!!.data.toString())}", Toast.LENGTH_LONG).show()
val path = FileUtil.formatPath(result.data!!.data.toString())
Snackbar.make(requireView(),getString(R.string.changed_path_for_everyone_to) + " " + path, Snackbar.LENGTH_LONG).show()
}
}

View file

@ -74,8 +74,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
): View? {
fragmentView = inflater.inflate(R.layout.fragment_download_video, container, false)
activity = getActivity()
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
infoUtil = InfoUtil(requireContext())
genericVideoFormats = infoUtil.getGenericVideoFormats(requireContext().resources)
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
@ -218,8 +218,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
downloadItem.videoPreferences.audioFormatIDs.clear()
downloadItem.videoPreferences.audioFormatIDs.addAll(it)
}
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format, item.first().audioFormats)
UiUtil.populateFormatCard(requireContext(), formatCard, item.first().format,
if(downloadItem.videoPreferences.removeAudio) listOf() else item.first().audioFormats
)
}
override fun onFormatsUpdated(allFormats: List<List<Format>>) {
@ -241,7 +242,9 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
val preferredAudioFormats = downloadViewModel.getPreferredAudioFormats(formats)
downloadItem.format = preferredFormat
downloadItem.allFormats = formats
UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat, formats.filter { preferredAudioFormats.contains(it.format_id) })
UiUtil.populateFormatCard(requireContext(), formatCard, preferredFormat,
if(downloadItem.videoPreferences.removeAudio) listOf() else formats.filter { preferredAudioFormats.contains(it.format_id) }
)
}
}
@ -315,7 +318,8 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
updateDataClicked = {
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
resultItem?.apply {
resultViewModel.updateItemData(this)
val rsVM = ViewModelProvider(requireActivity())[ResultViewModel::class.java]
rsVM.updateItemData(this)
}
}
},
@ -330,6 +334,7 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
},
removeAudioClicked = {
downloadItem.videoPreferences.removeAudio = it
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format, if (it) listOf() else downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
},
extraCommandsClicked = {
val callback = object : ExtraCommandsListener {
@ -366,6 +371,16 @@ class DownloadVideoFragment(private var resultItem: ResultItem? = null, private
onViewCreated(requireView(),savedInstanceState = state)
}
@SuppressLint("RestrictedApi")
fun updateSelectedAudioFormat(format: Format){
downloadItem.videoPreferences.audioFormatIDs.clear()
downloadItem.videoPreferences.audioFormatIDs.addAll(arrayListOf(format.format_id))
val formatCard = requireView().findViewById<MaterialCardView>(R.id.format_card_constraintLayout)
UiUtil.populateFormatCard(requireContext(), formatCard, downloadItem.format,
if(downloadItem.videoPreferences.removeAudio) listOf() else listOf(format)
)
}
private var pathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->

View file

@ -7,22 +7,31 @@ import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.Window
import android.widget.*
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.repository.HistoryRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.util.InfoUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.facebook.shimmer.ShimmerFrameLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -35,10 +44,11 @@ import java.util.*
class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, private var formats: List<List<Format>>, private val listener: OnFormatClickListener) : BottomSheetDialogFragment() {
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var infoUtil: InfoUtil
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var sharedPreferences: SharedPreferences
private lateinit var formatCollection: MutableList<List<Format>>
private lateinit var chosenFormats: List<Format>
private lateinit var selectedVideo : Format
private var selectedVideo : Format? = null
private lateinit var selectedAudios : MutableList<Format>
private lateinit var videoFormatList : LinearLayout
@ -48,15 +58,23 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
private lateinit var audioTitle : TextView
private lateinit var sortBy : FormatSorting
private lateinit var filterBy : FormatCategory
private lateinit var filterBtn : Button
private lateinit var continueInBackgroundSnackBar : Snackbar
private lateinit var view: View
private var updateFormatsJob: Job? = null
private var hasGenericFormats: Boolean = false
enum class FormatSorting {
filesize, container, codec, id
}
enum class FormatCategory {
ALL, SUGGESTED, SMALLEST, GENERIC
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
infoUtil = InfoUtil(requireActivity().applicationContext)
@ -64,6 +82,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
chosenFormats = listOf()
selectedAudios = mutableListOf()
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
}
@ -74,6 +93,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
dialog.setContentView(view)
sortBy = FormatSorting.valueOf(sharedPreferences.getString("format_order", "filesize")!!)
filterBy = FormatCategory.valueOf(sharedPreferences.getString("format_filter", "ALL")!!)
filterBtn = view.findViewById(R.id.format_filter)
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
@ -92,7 +113,9 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
okBtn = view.findViewById(R.id.format_ok)
shimmers.visibility = View.GONE
val hasGenericFormats = formats.first().isEmpty() || formats.last().any { it.format_id == "best" || it.format_id == "ba" }
hasGenericFormats = formats.first().isEmpty() || formats.last().any { it.format_id == "best" || it.format_id == "ba" }
filterBtn.isVisible = !hasGenericFormats
if (items.size > 1){
if (!hasGenericFormats){
@ -140,16 +163,18 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
chosenFormats = emptyList()
try {
refreshBtn.isEnabled = false
formatListLinearLayout.visibility = View.GONE
shimmers.visibility = View.VISIBLE
shimmers.startShimmer()
updateFormatsJob = launch(Dispatchers.IO) {
refreshBtn.isEnabled = false
formatListLinearLayout.visibility = View.GONE
shimmers.visibility = View.VISIBLE
shimmers.startShimmer()
updateFormatsJob = launch {
try{
//simple download
if (items.size == 1) {
kotlin.runCatching {
val res = infoUtil.getFormats(items.first()!!.url)
val res = withContext(Dispatchers.IO){
infoUtil.getFormats(items.first()!!.url)
}
res.filter { it.format_note != "storyboard" }
chosenFormats = if (items.first()?.type == Type.audio) {
res.filter { it.format_note.contains("audio", ignoreCase = true) }
@ -172,19 +197,21 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
}
}
//list format filtering
//list format filtering
}else{
var progress = "0/${items.size}"
formatCollection.clear()
withContext(Dispatchers.Main) {
refreshBtn.text = progress
}
infoUtil.getFormatsMultiple(items.map { it!!.url }) {
if (isActive) {
lifecycleScope.launch(Dispatchers.Main) {
formatCollection.add(it)
progress = "${formatCollection.size}/${items.size}"
refreshBtn.text = progress
withContext(Dispatchers.IO){
infoUtil.getFormatsMultiple(items.map { it!!.url }) {
if (isActive) {
lifecycleScope.launch(Dispatchers.Main) {
formatCollection.add(it)
progress = "${formatCollection.size}/${items.size}"
refreshBtn.text = progress
}
}
}
}
@ -218,32 +245,34 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
.sumOf { itt -> itt.filesize }
}
}
hasGenericFormats = false
withContext(Dispatchers.Main){
shimmers.visibility = View.GONE
shimmers.stopShimmer()
filterBtn.isVisible = true
addFormatsToView()
refreshBtn.visibility = View.GONE
formatListLinearLayout.visibility = View.VISIBLE
}
}
updateFormatsJob?.start()
}catch (e: Exception){
runCatching {
refreshBtn.isEnabled = true
refreshBtn.text = getString(R.string.update_formats)
formatListLinearLayout.visibility = View.VISIBLE
shimmers.visibility = View.GONE
shimmers.stopShimmer()
}catch (e: Exception){
withContext(Dispatchers.Main) {
refreshBtn.isEnabled = true
refreshBtn.text = getString(R.string.update_formats)
formatListLinearLayout.visibility = View.VISIBLE
shimmers.visibility = View.GONE
shimmers.stopShimmer()
e.printStackTrace()
Toast.makeText(context, getString(R.string.error_updating_formats), Toast.LENGTH_SHORT).show()
e.printStackTrace()
Toast.makeText(context, getString(R.string.error_updating_formats), Toast.LENGTH_SHORT).show()
}
}
}
updateFormatsJob?.start()
}
}
okBtn.setOnClickListener {
if (!::selectedVideo.isInitialized) {
if (selectedVideo == null) {
selectedVideo =
chosenFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.maxByOrNull { it.filesize }!!
}
@ -254,21 +283,85 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
// if (sharedPreferences.getBoolean("update_formats", false) && refreshBtn.isVisible && items.size == 1){
// refreshBtn.performClick()
// }
initFilter()
}
private fun initFilter(){
filterBtn.setOnClickListener {
val filterSheet = BottomSheetDialog(requireContext())
filterSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
filterSheet.setContentView(R.layout.format_category_sheet)
val all = filterSheet.findViewById<TextView>(R.id.all)
val suggested = filterSheet.findViewById<TextView>(R.id.suggested)
val smallest = filterSheet.findViewById<TextView>(R.id.smallest)
val generic = filterSheet.findViewById<TextView>(R.id.generic)
val filterOptions = listOf(all!!, suggested!!,smallest!!, generic!!)
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
when(filterBy) {
FormatCategory.ALL -> all.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
FormatCategory.SUGGESTED -> {
suggested.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
}
FormatCategory.SMALLEST -> {
smallest.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
}
FormatCategory.GENERIC -> {
generic.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
}
}
all.setOnClickListener {
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
filterBy = FormatCategory.ALL
all.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
addFormatsToView()
filterSheet.dismiss()
}
suggested.setOnClickListener {
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
filterBy = FormatCategory.SUGGESTED
suggested.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
addFormatsToView()
filterSheet.dismiss()
}
smallest.setOnClickListener {
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
filterBy = FormatCategory.SMALLEST
smallest.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
addFormatsToView()
filterSheet.dismiss()
}
generic.setOnClickListener {
filterOptions.forEach { it.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.empty,0,0,0) }
filterBy = FormatCategory.GENERIC
generic.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_check, 0,0,0)
addFormatsToView()
filterSheet.dismiss()
}
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
filterSheet.behavior.peekHeight = displayMetrics.heightPixels
filterSheet.show()
}
}
private fun returnFormats(){
//simple video format selection
if (items.size == 1){
listener.onFormatClick(listOf(FormatTuple(selectedVideo, selectedAudios)))
listener.onFormatClick(listOf(FormatTuple(selectedVideo!!, selectedAudios.ifEmpty { listOf(downloadViewModel.getFormat(chosenFormats, Type.audio)) })))
}else{
//playlist format selection
val selectedFormats = mutableListOf<Format>()
formatCollection.forEach {
selectedFormats.add(it.first{ f -> f.format_id == selectedVideo.format_id})
selectedFormats.add(it.first{ f -> f.format_id == selectedVideo!!.format_id})
}
if (selectedFormats.isEmpty()) {
items.forEach { _ ->
selectedFormats.add(selectedVideo)
selectedFormats.add(selectedVideo!!)
}
}
listener.onFormatClick(selectedFormats.map { FormatTuple(it, selectedAudios) })
@ -292,6 +385,53 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
FormatSorting.filesize -> chosenFormats
}
//filter category
when(filterBy){
FormatCategory.ALL -> {}
FormatCategory.SUGGESTED -> {
finalFormats = if (items.first()?.type == Type.audio){
val req = downloadViewModel.getPreferredAudioRequirements()
finalFormats
.filter { f -> req.count{req -> req(f)} >= 1 }
.sortedByDescending { f -> req.count{req -> req(f)} }
.ifEmpty { listOf(downloadViewModel.getFormat(finalFormats, Type.audio)) }
}else{
val req = downloadViewModel.getPreferredVideoRequirements()
req.addAll(downloadViewModel.getPreferredAudioRequirements())
finalFormats
.filter { f -> req.count{req -> req(f)} >= 1 }
.sortedByDescending { f -> req.count{req -> req(f)} }
.ifEmpty { listOf(downloadViewModel.getFormat(finalFormats, Type.video)) }
}
}
FormatCategory.SMALLEST -> {
val tmpFormats = finalFormats
.asSequence()
.map { it.copy() }
.filter { it.filesize > 0 }
.onEach {
var tmp = it.format_note
//formats that end like 1080P
if (tmp.endsWith("060")){
tmp = tmp.removeSuffix("60")
}
tmp = tmp.removeSuffix("p")
//formats that are written like 1920X1080
val split = tmp.split("x")
if (split.size > 1){
tmp = split[1]
}
it.format_note = tmp
}
.groupBy { it.format_note }
.map { it.value.minBy { it2 -> it2.filesize } }.toList()
finalFormats = finalFormats.filter { tmpFormats.map { it2 -> it2.format_id }.contains(it.format_id) }
}
FormatCategory.GENERIC -> {
finalFormats = listOf()
}
}
val canMultiSelectAudio = items.first()?.type == Type.video && finalFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null
videoFormatList.removeAllViews()
audioFormatList.removeAllViews()
@ -315,6 +455,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
}
}
videoFormatList.removeAllViews()
audioFormatList.removeAllViews()
if (finalFormats.isEmpty()){
finalFormats = if (items.first()?.type == Type.audio){
infoUtil.getGenericAudioFormats(requireContext().resources)
@ -328,6 +472,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
val formatItem = LayoutInflater.from(context).inflate(R.layout.format_item, null)
formatItem.tag = "${format.format_id}${format.format_note}"
UiUtil.populateFormatCard(requireContext(), formatItem as MaterialCardView, format, null)
if (selectedVideo == format) formatItem.isChecked = true
if (selectedAudios.any { it == format }) formatItem.isChecked = true
formatItem.setOnClickListener{ clickedformat ->
//if the context is behind a video or playlist, allow the ability to multiselect audio formats
if (canMultiSelectAudio){
@ -380,6 +526,31 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
}else{
videoFormatList.addView(formatItem)
}
}
if (items.first()?.type == Type.video){
selectedVideo = null
run breaking@{
videoFormatList.children.forEach {
val card = it as MaterialCardView
if (card.isChecked){
selectedVideo = finalFormats.first { format -> "${format.format_id}${format.format_note}" == card.tag }
return@breaking
}
}
}
}else{
selectedAudios = mutableListOf()
run breaking@{
audioFormatList.children.forEach {
val card = it as MaterialCardView
if (card.isChecked){
selectedAudios.add(finalFormats.first { format -> "${format.format_id}${format.format_note}" == card.tag })
}
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.ui.downloads
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
@ -103,6 +104,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
return fragmentView
}
@SuppressLint("RestrictedApi")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -519,7 +521,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
true
}
R.id.share -> {
UiUtil.shareFileIntent(requireContext(), selectedObjects!!.map { it.downloadPath })
UiUtil.shareFileIntent(requireContext(), selectedObjects!!.map { it.downloadPath }.flatten())
clearCheckedItems()
actionMode?.finish()
true
@ -633,7 +635,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
override fun onButtonClick(itemID: Long, isPresent: Boolean) {
if (isPresent){
UiUtil.shareFileIntent(requireContext(), listOf(historyList!!.first { it!!.id == itemID }!!.downloadPath))
UiUtil.shareFileIntent(requireContext(), historyList!!.first { it!!.id == itemID }!!.downloadPath)
}
}
companion object {

View file

@ -4,42 +4,32 @@ import android.annotation.SuppressLint
import android.app.ActionBar
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.graphics.Color
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.text.method.MovementMethod
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import androidx.core.view.children
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
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.Extensions.enableTextHighlight
import com.deniscerri.ytdlnis.util.Extensions.setCustomTextSize
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.slider.Slider
import com.google.android.material.snackbar.Snackbar
import com.neo.highlight.core.Highlight
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
class DownloadLogFragment : Fragment() {
@ -105,7 +95,7 @@ class DownloadLogFragment : Fragment() {
content.isFocusable = true
content.enableTextHighlight()
val slider = view.findViewById<Slider>(R.id.textsize_seekbar)
bottomAppBar?.setOnMenuItemClickListener { m: MenuItem ->
when(m.itemId){
R.id.wrap -> {
@ -140,10 +130,23 @@ class DownloadLogFragment : Fragment() {
m.isVisible = false
contentScrollView.fullScroll(View.FOCUS_DOWN)
}
R.id.text_size -> {
slider!!.isVisible = !slider.isVisible
}
}
true
}
slider?.apply {
this.valueFrom = 0f
this.valueTo = 10f
this.value = 2f
this.addOnChangeListener { slider, value, fromUser ->
content.setCustomTextSize(value + 13f)
}
}
contentScrollView.setOnScrollChangeListener { view, sx, sy, osx, osy ->
if (sy < osy){
bottomAppBar?.menu?.get(1)?.isVisible = contentScrollView.canScrollVertically(1)

View file

@ -25,6 +25,7 @@ import androidx.preference.PreferenceManager
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.deniscerri.ytdlnis.BuildConfig
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.CookieItem
@ -201,7 +202,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
saveFile.writeText(GsonBuilder().setPrettyPrinting().create().toJson(json))
val s = Snackbar.make(requireView(), getString(R.string.backup_created_successfully), Snackbar.LENGTH_LONG)
s.setAction(R.string.Open_File){
UiUtil.openFileIntent(requireContext(), saveFile.absolutePath)
UiUtil.openFileIntent(requireActivity(), saveFile.absolutePath)
}
s.show()
}
@ -600,10 +601,11 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
builder.setPositiveButton(
getString(R.string.restart)
) { _: DialogInterface?, _: Int ->
val intent: Intent? = requireContext().packageManager
.getLaunchIntentForPackage(requireContext().packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent!!)
val intent = Intent(requireContext(), MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
requireActivity().finishAffinity()
if(json.has("settings")){
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(preferences.getString("app_language", "en")))
}

View file

@ -8,7 +8,6 @@ import android.content.Context.INPUT_METHOD_SERVICE
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MenuItem
@ -23,6 +22,7 @@ import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
@ -38,15 +38,16 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.NotificationUtil
import com.deniscerri.ytdlnis.util.UiUtil
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
import com.deniscerri.ytdlnis.util.Extensions.setCustomTextSize
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.slider.Slider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.Executor
import kotlin.properties.Delegates
@ -236,6 +237,8 @@ class TerminalFragment : Fragment() {
topAppBar?.menu?.get(0)?.isVisible = false
topAppBar?.menu?.get(1)?.isVisible = true
topAppBar?.menu?.get(2)?.isVisible = true
topAppBar?.menu?.get(3)?.isVisible = true
val slider = requireActivity().findViewById<Slider>(R.id.textsize_seekbar)
topAppBar?.setOnMenuItemClickListener { m: MenuItem ->
when(m.itemId){
R.id.wrap -> {
@ -264,9 +267,23 @@ class TerminalFragment : Fragment() {
clipboard.setText(output?.text)
}
}
R.id.text_size -> {
slider?.isVisible = !slider.isVisible
}
}
true
}
slider?.apply {
this.valueFrom = 0f
this.valueTo = 10f
this.value = 2f
this.addOnChangeListener { slider, value, fromUser ->
output?.setCustomTextSize(value + 13f)
input?.setCustomTextSize(value + 13f)
}
}
}
private fun hideCancelFab() {
kotlin.runCatching {
@ -276,7 +293,7 @@ class TerminalFragment : Fragment() {
}
private fun showCancelFab() {
kotlin.runCatching {
fab!!.text = getString(R.string.cancel_download)
fab!!.text = getString(R.string.cancel_task)
fab!!.setIconResource(R.drawable.ic_cancel)
}
}

View file

@ -11,7 +11,9 @@ import android.graphics.drawable.shapes.OvalShape
import android.media.MediaMetadataRetriever
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
import android.net.Uri
import android.os.Build
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@ -25,6 +27,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.whenStarted
import androidx.lifecycle.withStarted
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.neo.highlight.core.Highlight
@ -32,6 +35,7 @@ import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import org.json.JSONObject
import java.io.File
import java.util.regex.Pattern
@ -158,4 +162,29 @@ object Extensions {
}
}
}
fun JSONObject.getIntByAny(vararg tags:String): Int {
tags.forEach {
runCatching {
return this.getInt(it)
}
}
return -1
}
fun JSONObject.getStringByAny(vararg tags:String): String {
tags.forEach {
runCatching {
val tmp = this.getString(it)
if (tmp != "null") return tmp
}
}
return ""
}
fun TextView.setCustomTextSize(newSize: Float){
this.setTextSize(TypedValue.COMPLEX_UNIT_SP, newSize)
}
}

View file

@ -253,7 +253,7 @@ object FileUtil {
MediaScannerConnection.scanFile(context, arrayOf(it), null, null)
}
}
return paths
return paths.sortedBy { File(it).lastModified() }
}catch (e: Exception){
e.printStackTrace()
}

View file

@ -18,6 +18,8 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.Extensions.getIntByAny
import com.deniscerri.ytdlnis.util.Extensions.getStringByAny
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
@ -617,27 +619,13 @@ class InfoUtil(private val context: Context) {
for (result in results) {
if (result.isNullOrBlank()) continue
val jsonObject = JSONObject(result)
val title = if (jsonObject.has("title")) {
if (jsonObject.getString("title") == "[Private video]") continue
jsonObject.getString("title")
} else {
jsonObject.getString("webpage_url_basename")
}
var author: String = if (jsonObject.has("uploader")) jsonObject.getString("uploader") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("uploader_id")) jsonObject.getString("uploader_id") else ""
}
}
}
var duration = ""
runCatching {
if (jsonObject.has("duration")) {
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
}
val title = jsonObject.getStringByAny("track", "alt_title", "title", "webpage_url_basename")
if (title == "[Private video]") continue
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
var duration = jsonObject.getIntByAny("duration").toString()
if (duration != "-1"){
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
}
var thumb: String? = ""
@ -649,16 +637,15 @@ class InfoUtil(private val context: Context) {
thumb = thumbs.getJSONObject(thumbs.length() - 1).getString("url")
}
}
val website = jsonObject.getString(listOf("ie_key", "extractor_key", "extractor").first { jsonObject.has(it) })
var playlistTitle: String? = ""
val website = jsonObject.getStringByAny("ie_key", "extractor_key", "extractor")
var playlistTitle = jsonObject.getStringByAny("playlist_title")
var playlistURL: String? = ""
var playlistIndex: Int? = null
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
if(playlistTitle?.removeSurrounding("\"").equals(query)) playlistTitle = ""
if(playlistTitle.removeSurrounding("\"") == query) playlistTitle = ""
if (playlistTitle?.isNotBlank() == true){
if (playlistTitle.isNotBlank()){
playlistURL = query
kotlin.runCatching { playlistIndex = jsonObject.getInt("playlist_index") }
}
@ -703,7 +690,7 @@ class InfoUtil(private val context: Context) {
duration,
thumb!!,
website,
playlistTitle!!,
playlistTitle,
formats,
urls,
chapters,
@ -788,22 +775,12 @@ class InfoUtil(private val context: Context) {
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val jsonObject = JSONObject(youtubeDLResponse.out)
var author: String = if (jsonObject.has("uploader")) jsonObject.getString("uploader") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("channel")) jsonObject.getString("channel") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("playlist_uploader")) jsonObject.getString("playlist_uploader") else ""
if (author.isEmpty() || author == "null"){
author = if (jsonObject.has("uploader_id")) jsonObject.getString("uploader_id") else ""
}
}
}
val title = jsonObject.getStringByAny("track", "alt_title", "title", "webpage_url_basename")
val author = jsonObject.getStringByAny("uploader", "channel", "playlist_uploader", "uploader_id")
var duration = ""
runCatching {
if (jsonObject.has("duration")) {
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
}
var duration = jsonObject.getIntByAny("duration").toString()
if (duration != "-1"){
duration = formatIntegerDuration(jsonObject.getInt("duration"), Locale.US)
}
var thumb: String? = ""
@ -820,11 +797,7 @@ class InfoUtil(private val context: Context) {
return ResultItem(
0,
url,
if (isPlaylist){
""
}else{
jsonObject.getString("title")
},
if (isPlaylist) "" else title,
author,
duration,
thumb!!,
@ -986,8 +959,7 @@ class InfoUtil(private val context: Context) {
YoutubeDLRequest(downloadItem.url)
}else{
YoutubeDLRequest(downloadItem.playlistURL!!).apply {
addOption("--playlist-start", downloadItem.playlistIndex!!)
addOption("--playlist-end", downloadItem.playlistIndex!!)
addOption("-I", "${downloadItem.playlistIndex!!}:${downloadItem.playlistIndex}")
}
}
@ -1117,7 +1089,7 @@ class InfoUtil(private val context: Context) {
"%(section_title&{} |)s$filenameTemplate"
}
if (downloadItem.downloadSections.split(";").size > 1){
filenameTemplate = "%(autonumber)d. %(section_start>%H:%M:%S)s $filenameTemplate"
filenameTemplate = "%(autonumber)d. %(section_start>%H%M%S)s $filenameTemplate"
}
}
@ -1145,11 +1117,11 @@ class InfoUtil(private val context: Context) {
}
if (downloadItem.playlistTitle.isNotBlank()){
request.addOption("--parse-metadata","${downloadItem.playlistTitle}:%(playlist)s")
request.addCommands(listOf("--replace-in-metadata", "playlist", ".+", downloadItem.playlistTitle))
runCatching {
request.addOption("--parse-metadata",
downloadItem.playlistIndex.toString() + ":%(playlist_index)s"
)
if (downloadItem.playlistIndex != null){
request.addOption("--parse-metadata", downloadItem.playlistIndex.toString() + ":%(playlist_index)s")
}
}
}
@ -1209,6 +1181,7 @@ class InfoUtil(private val context: Context) {
request.addOption("--embed-metadata")
request.addOption("--parse-metadata", "%(release_year,release_date>%Y,upload_date>%Y)s:%(meta_date)s")
request.addOption("--parse-metadata", "video:%(uploader)s:%(album_artist)s")
if (downloadItem.playlistTitle.isNotEmpty()) {
request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s")
@ -1279,6 +1252,10 @@ class InfoUtil(private val context: Context) {
val f = StringBuilder()
val preferredCodec = sharedPreferences.getString("video_codec", "")
val vCodecPrefIndex = context.getStringArray(R.array.video_codec_values).indexOf(preferredCodec)
val vCodecPref = context.getStringArray(R.array.video_codec_values_ytdlp)[vCodecPrefIndex]
val defaultFormats = context.resources.getStringArray(R.array.video_formats_values)
val usingGenericFormat = defaultFormats.contains(videoF) || downloadItem.allFormats.isEmpty() || downloadItem.allFormats == getGenericVideoFormats(context.resources)
if (!usingGenericFormat){
@ -1333,9 +1310,7 @@ class InfoUtil(private val context: Context) {
request.addOption("--audio-multistreams")
}
val preferredCodec = sharedPreferences.getString("video_codec", "")
val vCodecPrefIndex = context.getStringArray(R.array.video_codec_values).indexOf(preferredCodec)
val vCodecPref = context.getStringArray(R.array.video_codec_values_ytdlp)[vCodecPrefIndex]
preferredFormatIDs.forEach { v ->
preferredAudioFormatIDs.forEach { a ->
val aa = if (a.isNotBlank()) "+$a" else ""
@ -1345,13 +1320,21 @@ class InfoUtil(private val context: Context) {
if (!downloadItem.videoPreferences.removeAudio){
//build format with audio with preferred language
if (preferredAudioLanguage.isNotBlank()){
val al = "$v+ba[language^=$preferredAudioLanguage]/"
val al = if (v == "wv"){
"$v+wa[language^=$preferredAudioLanguage]/"
}else{
"$v+ba[language^=$preferredAudioLanguage]/"
}
if (!f.contains(al)) f.append(al)
}
//build format with best audio
if (!f.contains("$v+ba/") && !f.contains("wa")) f.append("$v+ba/")
//build format with standalone video
f.append("$v/")
if (v == "wv"){
f.append("w/")
}else{
if (v != "bv") f.append("$v/")
}
}
}
@ -1360,15 +1343,15 @@ class InfoUtil(private val context: Context) {
f.append("b")
}
StringBuilder().apply {
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
if (cont.isNotBlank()) append(",ext:$cont")
if (this.isNotBlank()){
request.addOption("-S", "+hasaud$this")
}
}
}
StringBuilder().apply {
if (vCodecPref.isNotBlank()) append(",vcodec:$vCodecPref")
if (aCodecPref.isNotBlank()) append(",acodec:$aCodecPref")
if (cont.isNotBlank()) append(",ext:$cont")
if (this.isNotBlank()){
request.addOption("-S", "+hasaud$this")
}
}

View file

@ -550,7 +550,7 @@ class NotificationUtil(var context: Context) {
}
fun showQueriesFinished() {
val notificationBuilder = getBuilder(DOWNLOAD_FINISHED_CHANNEL_ID)
val notificationBuilder = getBuilder(DOWNLOAD_MISC_CHANNEL_ID)
val openMultipleDownloads = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)

View file

@ -18,6 +18,7 @@ import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.Button
@ -26,17 +27,15 @@ import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.ui.text.font.Typeface
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.toUpperCase
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doOnTextChanged
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentManager
@ -57,6 +56,9 @@ import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.ConfigureDownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.VideoCutListener
import com.deniscerri.ytdlnis.util.Extensions.enableTextHighlight
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
@ -81,6 +83,7 @@ import kotlinx.coroutines.withContext
import java.io.File
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
@ -380,6 +383,46 @@ object UiUtil {
}
}
private fun openMultipleFilesIntent(context: Activity, path: List<String>){
val bottomSheet = BottomSheetDialog(context)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.filepathlist)
val list = bottomSheet.findViewById<LinearLayout>(R.id.filepath_list)
list?.apply {
path.forEach {path ->
val file = File(path)
val item = context.layoutInflater.inflate(R.layout.filepath_card, list, false)
item.apply {
findViewById<TextView>(R.id.file_name).text = file.nameWithoutExtension
findViewById<TextView>(R.id.filesize).text = FileUtil.convertFileSize(file.length())
findViewById<TextView>(R.id.extension).text = file.extension.uppercase()
if (!file.exists()){
isEnabled = false
alpha = 0.7f
}
isEnabled = file.exists()
setOnClickListener {
openFileIntent(context, path)
bottomSheet.dismiss()
}
}
list.addView(item)
}
}
bottomSheet.show()
val displayMetrics = DisplayMetrics()
context.windowManager.defaultDisplay.getMetrics(displayMetrics)
bottomSheet.behavior.peekHeight = displayMetrics.heightPixels
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
fun shareFileIntent(context: Context, paths: List<String>){
val uris : ArrayList<Uri> = arrayListOf()
paths.runCatching {
@ -479,14 +522,16 @@ object UiUtil {
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)}`" }
setOnClickListener{
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)}`" }
setOnClickListener{
setOnLongClickListener{
showFullTextDialog(context, text.toString(), context.getString(R.string.author))
true
}
}
@ -682,8 +727,25 @@ object UiUtil {
btn?.setImageResource(typeImageResource)
if (isPresent){
btn?.setOnClickListener {
shareFileIntent(context, listOf(item.downloadPath))
btn?.apply {
if (item.downloadPath.size > 1){
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
@OptIn(ExperimentalBadgeUtils::class) override fun onGlobalLayout() {
val badgeDrawable = BadgeDrawable.create(context)
badgeDrawable.number = item.downloadPath.size
//Important to change the position of the Badge
badgeDrawable.horizontalOffset = 25
badgeDrawable.verticalOffset = 25
BadgeUtils.attachBadgeDrawable(badgeDrawable, btn, null)
btn.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
}
setOnClickListener {
shareFileIntent(context, item.downloadPath)
}
}
}
@ -693,7 +755,7 @@ object UiUtil {
val codec = bottomSheet.findViewById<TextView>(R.id.codec)
val fileSize = bottomSheet.findViewById<TextView>(R.id.file_size)
val command = bottomSheet.findViewById<Chip>(R.id.command)
val file = File(item.downloadPath)
val file = File(item.downloadPath.first())
val calendar = Calendar.getInstance()
calendar.timeInMillis = item.time * 1000L
@ -708,7 +770,7 @@ object UiUtil {
formatNote?.isVisible = false
}
if (item.format.container != "") {
if (item.format.container != "" && item.downloadPath.size == 1) {
container!!.text = if (file.exists()) file.extension.uppercase() else item.format.container.uppercase()
container.setChipIconResource(typeImageResource)
}else {
@ -723,7 +785,7 @@ object UiUtil {
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
if (codecText == "" || codecText == "none" || item.downloadPath.size > 1){
codec!!.visibility = View.GONE
}else{
codec!!.visibility = View.VISIBLE
@ -731,7 +793,7 @@ object UiUtil {
}
val fileSizeReadable = FileUtil.convertFileSize(if (file.exists()) file.length() else item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = View.GONE
if (fileSizeReadable == "?" || item.downloadPath.size > 1) fileSize!!.visibility = View.GONE
else fileSize!!.text = fileSizeReadable
command?.setOnClickListener {
@ -761,7 +823,11 @@ object UiUtil {
val openFile = bottomSheet.findViewById<Button>(R.id.bottomsheet_open_file_button)
openFile!!.tag = item.id
openFile.setOnClickListener{
openFileIntent(context, item.downloadPath)
if (item.downloadPath.size == 1) {
openFileIntent(context, item.downloadPath.first())
}else{
openMultipleFilesIntent(context, item.downloadPath)
}
}
val redownload = bottomSheet.findViewById<Button>(R.id.bottomsheet_redownload_button)
@ -1044,11 +1110,8 @@ object UiUtil {
val chip = activity.layoutInflater.inflate(R.layout.suggestion_chip, chipGroup, false) as Chip
chip.text = shortcut.content
chip.setOnClickListener {
if (chip.isChecked){
itemSelected(shortcut.content)
}else{
itemRemoved(shortcut.content)
}
chip.isChecked = false
itemSelected(shortcut.content)
}
chipGroup.addView(chip)
}
@ -1081,6 +1144,8 @@ object UiUtil {
extraCommandsClicked: () -> Unit,
updateDataClicked: () -> Unit
){
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val embedSubs = view.findViewById<Chip>(R.id.embed_subtitles)
val saveSubtitles = view.findViewById<Chip>(R.id.save_subtitles)
val subtitleLanguages = view.findViewById<Chip>(R.id.subtitle_languages)
@ -1127,6 +1192,7 @@ object UiUtil {
}
val sponsorBlock = view.findViewById<Chip>(R.id.sponsorblock_filters)
sponsorBlock.isEnabled = sharedPreferences.getBoolean("use_sponsorblock", true)
sponsorBlock!!.setOnClickListener {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.select_sponsorblock_filtering))
@ -1228,7 +1294,6 @@ object UiUtil {
}
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
items.forEach { it.videoPreferences.subsLanguages = sharedPreferences.getString("subs_lang", "en.*,.*-orig")!! }
if (items.all { it.videoPreferences.writeSubs}) {
@ -1638,8 +1703,7 @@ object UiUtil {
val deleteDialog = MaterialAlertDialogBuilder(context)
deleteDialog.setTitle(context.getString(R.string.you_are_going_to_delete) + " \"" + item.title + "\"!")
val path = item.downloadPath
val file = File(path)
if (file.exists() && path.isNotEmpty()) {
if (path.any { File(it).exists() && it.isNotEmpty() }) {
deleteDialog.setMultiChoiceItems(
arrayOf(context.getString(R.string.delete_file_too)),
booleanArrayOf(false)
@ -1839,14 +1903,14 @@ object UiUtil {
builder.setView(view)
builder.setPositiveButton(
context.getString(android.R.string.copy)
context.getString(R.string.dismiss)
) { _: DialogInterface?, _: Int ->
copyToClipboard(txt, context)
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
context.getString(android.R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()

View file

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.work
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
@ -46,6 +47,7 @@ class DownloadWorker(
private val context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
@SuppressLint("RestrictedApi")
override suspend fun doWork(): Result {
if (isStopped) return Result.success()
@ -126,7 +128,7 @@ class DownloadWorker(
val logString = StringBuilder("\n ${commandString}\n\n")
val logItem = LogItem(
0,
downloadItem.title.ifEmpty { downloadItem.url },
downloadItem.title.ifBlank { downloadItem.url },
"Downloading:\n" +
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
@ -168,10 +170,12 @@ class DownloadWorker(
if (noCache){
setProgressAsync(workDataOf("progress" to 100, "output" to "Scanning Files", "id" to downloadItem.id))
finalPaths = it.out.split("\n")
.asSequence()
.filter { it.startsWith("'/storage") }
.map { it.removePrefix("'") }
.map { it.removeSuffix("\n") }
.map { it.removeSuffix("'") }
.sortedBy { File(it).lastModified() }
.toList()
// finalPaths = File(FileUtil.formatPath(downloadLocation))
// .walkTopDown()
@ -191,7 +195,7 @@ class DownloadWorker(
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id))
}
}
}.filter { !it.matches("\\.(description)|(txt)\$".toRegex()) }
if (finalPaths.isNotEmpty()){
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id))
@ -207,6 +211,7 @@ class DownloadWorker(
}
}
finalPaths = finalPaths?.filter { !it.matches("(\\.description)|(\\.srt)|(\\.ass)|(\\.lrc)|(\\.vtt)|(\\.txt)|(\\.jpg)|(\\.png)\$".toRegex()) }
FileUtil.deleteConfigFiles(request)
//put download in history
@ -218,29 +223,27 @@ class DownloadWorker(
}
}else{
val unixTime = System.currentTimeMillis() / 1000
finalPaths?.forEachIndexed {idx, ff ->
val file = File(ff)
var index = ""
var duration = downloadItem.duration
if (idx > 0) {
index = "[${idx + 1}] "
downloadItem.author = file.nameWithoutExtension
}
val d = file.getMediaDuration(context)
if (d > 0) duration = infoUtil.formatIntegerDuration(d, Locale.US)
finalPaths?.apply {
this.first().apply {
val file = File(this)
var duration = downloadItem.duration
val d = file.getMediaDuration(context)
if (d > 0) duration = infoUtil.formatIntegerDuration(d, Locale.US)
downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension
downloadItem.duration = duration
}
downloadItem.format.filesize = file.length()
downloadItem.format.container = file.extension
downloadItem.duration = duration
val historyItem = HistoryItem(0,
downloadItem.url,
index + downloadItem.title,
downloadItem.title,
downloadItem.author,
downloadItem.duration,
downloadItem.thumb,
downloadItem.type,
unixTime,
ff,
this,
downloadItem.website,
downloadItem.format,
downloadItem.id,

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" 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="M4,22H2V2h2V22zM22,7H6v3h16V7zM16,14H6v3h10V14z"/>
</vector>

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="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
</vector>

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="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>

View file

@ -17,106 +17,65 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="@+id/search_history_scroll_view"
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
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" />
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
app:singleLine="false" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
app:singleSelection="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-10dp"
android:visibility="gone"
android:id="@+id/link_you_copied"
layout="@layout/search_suggestion_item"
/>
<LinearLayout
android:id="@+id/search_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/search_suggestions_scroll_view"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_suggestions_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/search_suggestions_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
android:layout_height="wrap_content" />
</LinearLayout>
@ -269,31 +228,37 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_all_coordinator"
<LinearLayout
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/copied_url_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="5dp"
android:contentDescription="@string/link_you_copied"
android:src="@drawable/ic_clipboard"
android:text="@string/download_all"
app:useCompatPadding="true" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_all_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="16dp"
android:text="@string/download_all"
app:icon="@drawable/ic_down"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_selected_coordinator"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
app:icon="@drawable/ic_down" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_selected_fab"
android:visibility="gone"
app:elevation="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -303,9 +268,7 @@
android:text="@string/download"
app:srcCompat="@drawable/ic_music"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,186 +0,0 @@
<?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:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_constraint"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent=".67">
<com.google.android.material.card.MaterialCardView
android:id="@+id/frame_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.media3.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fixed_height"
app:useDefaultControls="true"
app:use_controller="true" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/download_thumb"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_image"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintTop_toTopOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="17sp"
android:textStyle="bold"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.277"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_layout" />
<TextView
android:id="@+id/bottom_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
app:layout_constraintVertical_bias="0.0"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="@id/frame_layout"
app:layout_constraintTop_toBottomOf="@id/frame_layout" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/app_name"
android:textAlignment="textStart"
android:textSize="15sp"
app:icon="@drawable/ic_link"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/download_music"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/info"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_marginHorizontal="10dp"
android:scrollbars="none"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintStart_toEndOf="@id/player_constraint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<include layout="@layout/no_results"
android:visibility="gone" />
<TextView
android:id="@+id/running"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@string/running"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/running_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/queued"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:text="@string/download_queue"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/queued_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -17,106 +17,65 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="@+id/search_history_scroll_view"
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
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" />
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
app:singleLine="false" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
android:paddingStart="0dp"
android:paddingEnd="20dp"
app:singleSelection="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-10dp"
android:visibility="gone"
android:id="@+id/link_you_copied"
layout="@layout/search_suggestion_item"
/>
<LinearLayout
android:id="@+id/search_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/search_suggestions_scroll_view"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_suggestions_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/search_suggestions_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
android:layout_height="wrap_content" />
</LinearLayout>
@ -275,37 +234,42 @@
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_fabs"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_all_coordinator"
<LinearLayout
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/copied_url_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="5dp"
android:contentDescription="@string/link_you_copied"
android:src="@drawable/ic_clipboard"
android:text="@string/download_all"
app:useCompatPadding="true" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_all_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="16dp"
android:text="@string/download_all"
app:icon="@drawable/ic_down"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_selected_coordinator"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
app:icon="@drawable/ic_down" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_selected_fab"
android:visibility="gone"
app:elevation="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -315,9 +279,7 @@
android:text="@string/download"
app:srcCompat="@drawable/ic_music"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -17,106 +17,65 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="@+id/search_history_scroll_view"
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
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" />
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
app:singleLine="false" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
android:paddingEnd="20dp"
android:paddingStart="0dp"
app:singleSelection="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-10dp"
android:visibility="gone"
android:id="@+id/link_you_copied"
layout="@layout/search_suggestion_item"
/>
<LinearLayout
android:id="@+id/search_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/search_suggestions_scroll_view"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_suggestions_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/search_suggestions_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
android:layout_height="wrap_content" />
</LinearLayout>
@ -269,31 +228,37 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_all_coordinator"
<LinearLayout
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/copied_url_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="5dp"
android:contentDescription="@string/link_you_copied"
android:src="@drawable/ic_clipboard"
android:text="@string/download_all"
app:useCompatPadding="true" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_all_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="16dp"
android:text="@string/download_all"
app:icon="@drawable/ic_down"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_selected_coordinator"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
app:icon="@drawable/ic_down" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_selected_fab"
android:visibility="gone"
app:elevation="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -303,9 +268,7 @@
android:text="@string/download"
app:srcCompat="@drawable/ic_music"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -17,106 +17,65 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="@+id/search_history_scroll_view"
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
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" />
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
app:singleLine="false" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-10dp"
android:visibility="gone"
android:id="@+id/link_you_copied"
layout="@layout/search_suggestion_item"
/>
<LinearLayout
android:id="@+id/search_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/search_suggestions_scroll_view"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_suggestions_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/search_suggestions_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
android:layout_height="wrap_content" />
</LinearLayout>
@ -294,37 +253,42 @@
</com.facebook.shimmer.ShimmerFrameLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/home_fabs"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_all_coordinator"
<LinearLayout
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/copied_url_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="5dp"
android:contentDescription="@string/link_you_copied"
android:src="@drawable/ic_clipboard"
android:text="@string/download_all"
app:useCompatPadding="true" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_all_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="16dp"
android:text="@string/download_all"
app:icon="@drawable/ic_down"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_selected_coordinator"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
app:icon="@drawable/ic_down" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_selected_fab"
android:visibility="gone"
app:elevation="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -334,10 +298,9 @@
android:text="@string/download"
app:srcCompat="@drawable/ic_music"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -28,6 +28,18 @@
app:title="@string/terminal" />
<com.google.android.material.slider.Slider
android:visibility="gone"
android:id="@+id/textsize_seekbar"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbar" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView

View file

@ -25,9 +25,10 @@
<com.facebook.shimmer.ShimmerFrameLayout
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_marginTop="4dp"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
@ -37,7 +38,7 @@
<TextView
android:id="@+id/bottom_sheet_loading_title"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:singleLine="false"
@ -64,9 +65,10 @@
<com.facebook.shimmer.ShimmerFrameLayout
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.0"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_schedule_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/shimmer_loading_title"
@ -74,7 +76,7 @@
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:singleLine="false"

View file

@ -1,61 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="match_parent">
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filename"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filename_edittext"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/filename"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/filename_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<TextView
android:id="@+id/suggested"
android:layout_width="0dp"
android:padding="10dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/filename"
android:text="@string/suggested" />
</com.google.android.material.textfield.TextInputLayout>
<ScrollView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintHeight_max="200dp"
android:paddingHorizontal="10dp"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/suggested">
<TextView
android:id="@+id/suggested"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/filename"
android:text="@string/suggested" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/filename_suggested_chipgroup"
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="-7dp"
app:chipSpacingHorizontal="5dp"
app:selectionRequired="false"
app:singleSelection="false">
app:layout_constraintHeight_max="200dp"
android:paddingHorizontal="10dp"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/suggested">
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.ChipGroup
android:id="@+id/filename_suggested_chipgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="-7dp"
app:chipSpacingHorizontal="5dp"
app:selectionRequired="false"
app:singleSelection="false">
</ScrollView>
</com.google.android.material.chip.ChipGroup>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/download_item_data"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="0.7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/file_name"
app:layout_constraintVertical_bias="0.0">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingStart="5dp"
android:paddingEnd="0dp"
android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/filesize"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorSecondary"
android:clickable="false"
android:ellipsize="end"
android:gravity="center"
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/extension"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="@drawable/rounded_corner"
android:backgroundTint="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</HorizontalScrollView>
<com.google.android.material.button.MaterialButton
android:id="@+id/action_button"
style="?attr/materialIconButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:padding="0dp"
app:cornerRadius="10dp"
app:iconTint="?attr/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
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:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/Open_File"
android:textSize="25sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:padding="5dp"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:id="@+id/filepath_list"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/all"
android:layout_marginTop="20dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:drawablePadding="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="@string/all"
app:drawableStartCompat="@drawable/ic_check" />
<TextView
android:id="@+id/suggested"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:drawablePadding="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="@string/suggested"
app:drawableStartCompat="@drawable/empty" />
<TextView
android:id="@+id/smallest"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:drawablePadding="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="@string/format_smallest"
app:drawableStartCompat="@drawable/empty" />
<TextView
android:id="@+id/generic"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:paddingVertical="10dp"
android:paddingHorizontal="20dp"
android:drawablePadding="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="15sp"
android:text="@string/generic"
app:drawableStartCompat="@drawable/empty" />
</LinearLayout>
</FrameLayout>

View file

@ -23,49 +23,80 @@
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/format"
android:layout_marginTop="4dp"
android:maxLines="2"
android:layout_marginEnd="20dp"
android:singleLine="false"
android:text="@string/format"
android:textSize="25sp"
app:layout_constraintEnd_toStartOf="@+id/format_buttons"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:layout_marginEnd="20dp"
android:singleLine="false"
android:text="@string/select_format"
android:textSize="12sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/format_buttons"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
<Button
android:id="@+id/format_ok"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
<LinearLayout
android:id="@+id/format_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/ok"
app:icon="@drawable/ic_check"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
>
<Button
android:id="@+id/format_filter"
style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/baseline_align_horizontal_left_24"
app:layout_constraintBottom_toBottomOf="@id/format_refresh"
app:layout_constraintEnd_toStartOf="@+id/format_refresh"
app:layout_constraintTop_toTopOf="@id/format_refresh" />
<Button
android:id="@+id/format_refresh"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/update_formats"
app:icon="@drawable/ic_refresh"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/format_ok"
android:visibility="gone"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/ok"
app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<Button
android:id="@+id/format_refresh"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/update_formats"
app:icon="@drawable/ic_refresh"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -20,6 +20,15 @@
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"/>
<com.google.android.material.slider.Slider
android:visibility="gone"
android:id="@+id/textsize_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appbar" />
</com.google.android.material.appbar.AppBarLayout>

View file

@ -17,106 +17,65 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<ScrollView
android:id="@+id/search_history_scroll_view"
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
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" />
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical">
app:singleLine="false" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
android:paddingStart="0dp"
android:paddingEnd="20dp"
android:layout_width="wrap_content"
app:singleSelection="true"
android:layout_height="wrap_content"
app:singleLine="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
</HorizontalScrollView>
<View
android:id="@+id/chipGroupDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/queries"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/init_search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:singleLine="false" />
<com.google.android.material.button.MaterialButton
android:id="@+id/init_search_query"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:iconSize="20dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_search"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-10dp"
android:visibility="gone"
android:id="@+id/link_you_copied"
layout="@layout/search_suggestion_item"
/>
<LinearLayout
android:id="@+id/search_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</ScrollView>
<ScrollView
android:id="@+id/search_suggestions_scroll_view"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_suggestions_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/search_suggestions_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
android:layout_height="wrap_content" />
</LinearLayout>
@ -198,43 +157,49 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_all_coordinator"
<LinearLayout
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="bottom|end">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/copied_url_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="5dp"
android:contentDescription="@string/link_you_copied"
android:src="@drawable/ic_clipboard"
android:text="@string/download_all"
app:useCompatPadding="true" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_all_fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
android:text="@string/download_all"
app:icon="@drawable/ic_down"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/download_selected_coordinator"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="match_parent">
app:icon="@drawable/ic_down" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/download_selected_fab"
android:visibility="gone"
app:elevation="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
app:icon="@drawable/ic_down"
android:text="@string/download"
app:srcCompat="@drawable/ic_music"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -56,7 +56,6 @@
</ScrollView>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
@ -65,6 +64,9 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
style="@style/Widget.Material3.BottomAppBar"

View file

@ -7,7 +7,7 @@
<RelativeLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,2.66:1"
app:layout_constraintDimensionRatio="H,2.5:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -136,8 +136,8 @@
android:clickable="false"
app:cornerRadius="10dp"
android:elevation="0dp"
app:borderWidth="0dp"
app:elevation="0dp"
app:borderWidth="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,2.5:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/downloads_card_view2"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginHorizontal="35dp"
android:layout_marginVertical="5dp"
android:checkable="false"
app:cardCornerRadius="15dp"
app:cardElevation="5dp"
app:cardMaxElevation="12dp"
app:cardPreventCornerOverlap="true"
app:checkedIcon="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="0dp" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/downloads_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardCornerRadius="20dp"
app:cardElevation="5dp"
app:checkedIcon="@null"
app:cardMaxElevation="12dp"
android:checkable="true"
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="15dp"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/downloads_image_view"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:dividerPadding="5dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/downloads_info_time"
app:layout_constraintEnd_toStartOf="@+id/downloads_download_button_type"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/downloads_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="2"
android:textColor="#FFF"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/downloads_info_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="2"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
android:textColor="#FFF"
android:textSize="11sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/length"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="1"
android:padding="10dp"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="10"
android:textColor="#FFF"
android:textSize="11sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/downloads_info_time"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/downloads_info_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="10dp"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="1"
android:scrollbars="none"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="10"
android:textColor="#FFF"
android:textSize="11sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/downloads_download_button_type"
app:layout_constraintVertical_bias="1.0" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/downloads_download_button_type"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:clickable="false"
app:cornerRadius="10dp"
android:elevation="0dp"
app:elevation="0dp"
app:borderWidth="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,200 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:paddingBottom="20dp"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/frame_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.media3.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fixed_height"
app:useDefaultControls="true"
app:use_controller="true" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/download_thumb"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_image"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintTop_toTopOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<androidx.core.widget.NestedScrollView
android:orientation="vertical"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_layout"
android:paddingBottom="20dp"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/frame_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/list_section"
android:layout_width="match_parent"
<androidx.media3.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fixed_height"
app:useDefaultControls="true"
app:use_controller="true" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/download_thumb"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_image"
app:iconSize="30dp"
app:iconTint="?android:colorAccent"
app:layout_constraintTop_toTopOf="@+id/frame_layout"
app:layout_constraintEnd_toEndOf="@+id/frame_layout" />
<androidx.core.widget.NestedScrollView
android:orientation="vertical"
android:layout_margin="10dp">
android:layout_width="match_parent"
android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/frame_layout"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:textStyle="bold"
android:layout_marginHorizontal="10dp"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/list_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="17sp"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:orientation="vertical"
android:layout_margin="10dp">
<TextView
android:id="@+id/bottom_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
app:layout_constraintVertical_bias="0.0"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/nestedScrollView"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:textStyle="bold"
android:layout_marginHorizontal="10dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="17sp"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:ellipsize="end"
app:layout_constraintVertical_bias="0.0"
android:singleLine="false"
android:text="@string/app_name"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/nestedScrollView"
app:layout_constraintEnd_toStartOf="@+id/download_music"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginHorizontal="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/title_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="bottom_info,download_music" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/title_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="bottom_info,download_music" />
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_barrier">
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_barrier">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/running"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:layout_marginHorizontal="10dp"
android:text="@string/running"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/running_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/running" />
android:layout_height="wrap_content">
<TextView
android:id="@+id/queued"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:layout_marginHorizontal="10dp"
android:text="@string/download_queue"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/running_recycler" />
<TextView
android:id="@+id/running"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:layout_marginHorizontal="10dp"
android:text="@string/running"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/running_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/running" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/queued_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queued" />
<TextView
android:id="@+id/queued"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
android:layout_marginHorizontal="10dp"
android:text="@string/download_queue"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/running_recycler" />
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textAlignment="textStart"
android:textSize="15sp"
app:icon="@drawable/ic_link"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queued_recycler" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/queued_recycler"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queued" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/bottom_sheet_link"
style="@style/Widget.Material3.Button.TextButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:singleLine="true"
android:text="@string/app_name"
android:textAlignment="textStart"
android:textSize="15sp"
app:icon="@drawable/ic_link"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/queued_recycler" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -5,6 +5,7 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="@+id/linear"
android:paddingHorizontal="10dp"
android:paddingVertical="5dp"
android:layout_width="match_parent"

View file

@ -1,61 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="match_parent">
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/subtitle"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/subtitle_edittext"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/subtitle"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/subtitle_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<TextView
android:id="@+id/suggested"
android:layout_width="0dp"
android:padding="10dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/subtitle"
android:text="@string/suggested" />
</com.google.android.material.textfield.TextInputLayout>
<ScrollView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintHeight_max="200dp"
android:paddingHorizontal="10dp"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/suggested">
<TextView
android:id="@+id/suggested"
android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/subtitle"
android:text="@string/suggested" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/subtitle_suggested_chipgroup"
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="-7dp"
app:chipSpacingHorizontal="5dp"
app:selectionRequired="false"
app:singleSelection="false">
app:layout_constraintHeight_max="200dp"
android:paddingHorizontal="10dp"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/suggested">
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.ChipGroup
android:id="@+id/subtitle_suggested_chipgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:chipSpacingVertical="-7dp"
app:chipSpacingHorizontal="5dp"
app:selectionRequired="false"
app:singleSelection="false">
</ScrollView>
</com.google.android.material.chip.ChipGroup>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -14,7 +14,13 @@
android:title=""
android:visible="false"
android:icon="@drawable/ic_down"
app:showAsAction="ifRoom" />
app:showAsAction="always" />
<item
android:id="@+id/text_size"
android:title=""
android:icon="@drawable/ic_textformat"
app:showAsAction="always" />
</menu>

View file

@ -12,6 +12,12 @@
app:showAsAction="always"
android:id="@+id/wrap"/>
<item
android:id="@+id/text_size"
android:title=""
android:icon="@drawable/ic_textformat"
app:showAsAction="always" />
<item
android:id="@+id/export_clipboard"
android:title="@string/export_from_clipboard"

View file

@ -155,9 +155,17 @@
<dialog
android:id="@+id/downloadBottomSheetDialog"
android:name="com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog"
android:label="DownloadBottomSheetDialog" />
android:label="DownloadBottomSheetDialog" >
<action
android:id="@+id/action_downloadBottomSheetDialog_to_cutVideoBottomSheetDialog"
app:destination="@id/cutVideoBottomSheetDialog" />
</dialog>
<dialog
android:id="@+id/downloadMultipleBottomSheetDialog2"
android:name="com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog"
android:label="DownloadMultipleBottomSheetDialog" />
<dialog
android:id="@+id/cutVideoBottomSheetDialog"
android:name="com.deniscerri.ytdlnis.ui.downloadcard.CutVideoBottomSheetDialog"
android:label="CutVideoBottomSheetDialog" />
</navigation>

View file

@ -113,7 +113,7 @@
<string name="video_format">صيغة الفيديو</string>
<string name="audio_quality">جودة الصوت</string>
<string name="audio_quality_summary">0(الأفضل) 10(الأدنى)</string>
<string name="cancel_download">إلغاء التحميل</string>
<string name="cancel_task">إلغاء التحميل</string>
<string name="run_command">تشغيل الأمر</string>
<string name="command_download_notification_channel_name">تحميلات الأوامر</string>
<string name="command_download_notification_channel_description">إشعار يُظهر مستوى التقدم في تحميل ملفات الصوت والفيديو بإستخدام أوامر yt-dlp</string>

View file

@ -54,7 +54,7 @@
<string name="video_format">Video Format</string>
<string name="audio_quality">Səs Keyfiyyəti</string>
<string name="audio_quality_summary">0 (ən yaxşı) 10 (ən pis)</string>
<string name="cancel_download">Yükləməni Ləğv Et</string>
<string name="cancel_task">Yükləməni Ləğv Et</string>
<string name="run_command">Əmri İşlət</string>
<string name="command_download_notification_channel_name">Əmr Yükləmələri</string>
<string name="command_download_notification_channel_description">Yt-dlp əmri işlədərkən yükləmə irəliləyişi göstərən bildiriş</string>

View file

@ -8,7 +8,7 @@
<string name="ytdl_already_updating">নতুন সংস্করণ ইনস্টল করা হচ্ছে</string>
<string name="sponsorblock_fillers">ফিল্টার</string>
<string name="sponsorblock_reminders">সদস্যতা রিমাইন্ডার</string>
<string name="cancel_download">ডাউনলোড বাতিল করুন</string>
<string name="cancel_task">ডাউনলোড বাতিল করুন</string>
<string name="logs">লগ</string>
<string name="in_queue">কিউ তে আছে</string>
<string name="audio_quality">অডিও মান</string>

View file

@ -37,7 +37,7 @@
<string name="video_format">ভিডিও ফরম্যাট</string>
<string name="audio_quality">অডিও মান</string>
<string name="audio_quality_summary">0 (সেরা) 10 (সবচেয়ে খারাপ)</string>
<string name="cancel_download">ডাউনলোড বাতিল করুন</string>
<string name="cancel_task">ডাউনলোড বাতিল করুন</string>
<string name="run_command">কমান্ড চালু করুন</string>
<string name="items_left">বাকি আইটেম(গুলি)</string>
<string name="history_is_empty">ইতিহাস খালি</string>

View file

@ -21,7 +21,7 @@
<string name="save_thumb_summary">Uložit náhled ve složce stahování</string>
<string name="command_download_notification_channel_name">Stažené příkazy</string>
<string name="ytdl_already_updating">Instalace nové verze</string>
<string name="cancel_download">Zrušit stahování</string>
<string name="cancel_task">Zrušit stahování</string>
<string name="audio_quality">Kvalita audia</string>
<string name="audio_format">Audio formát</string>
<string name="couldnt_find_apk">Nepodařilo se najít vhodný APK soubor</string>

View file

@ -79,7 +79,7 @@
<string name="add_chapters">Kapitel in Videos</string>
<string name="embed_thumb_summary">Verwende das Vorschaubild als Cover-Art</string>
<string name="save_thumb">Vorschaubild speichern</string>
<string name="cancel_download">Herunterladen abbrechen</string>
<string name="cancel_task">Herunterladen abbrechen</string>
<string name="you_are_going_to_delete">Löschen</string>
<string name="incognito_summary">Herunterladen ohne es zum Verlauf hinzuzufügen</string>
<string name="video_format">Videoformat</string>

View file

@ -38,7 +38,7 @@
<string name="video_format">Μορφή Βίντεο</string>
<string name="audio_quality">Ποιότητα Ήχου</string>
<string name="audio_quality_summary">0 (καλύτερη) 10 (χειρότερη)</string>
<string name="cancel_download">Ακύρωση Λήψης</string>
<string name="cancel_task">Ακύρωση Λήψης</string>
<string name="run_command">Εκτέλεση Εντολής</string>
<string name="command_download_notification_channel_name">Λήψεις Εντολών</string>
<string name="command_download_notification_channel_description">Ειδοποίηση που δείχνει την πρόοδο της λήψης με χρήση μιας εντολής yt-dlp</string>

View file

@ -61,7 +61,7 @@
<string name="download">Descargar</string>
<string name="save_thumb">Guardar Miniatura</string>
<string name="save_thumb_summary">Guarda la miniatura en el directorio de descarga</string>
<string name="cancel_download">Cancelar Descarga</string>
<string name="cancel_task">Cancelar Descarga</string>
<string name="video_quality">Calidad de Video</string>
<string name="video_format">Formato de Video</string>
<string name="audio_quality">Calidad de Audio</string>

View file

@ -42,7 +42,7 @@
<string name="save_thumb">ذخیره تصویر بندانگشتی</string>
<string name="audio_format">فرمت صدا</string>
<string name="video_format">فرمت ویدئو</string>
<string name="cancel_download">لغو دانلود</string>
<string name="cancel_task">لغو دانلود</string>
<string name="run_command">اجرای دستور</string>
<string name="command_download_notification_channel_name">دستور دانلودها</string>
<string name="command_download_notification_channel_description">اعلان پیشرفت دانلود با استفاده از دستور yt-dlp</string>

View file

@ -54,7 +54,7 @@
<string name="video_format">Format vidéo</string>
<string name="audio_quality">Qualité audio</string>
<string name="audio_quality_summary">0 (meilleur) 10 (pire)</string>
<string name="cancel_download">Annuler le téléchargement</string>
<string name="cancel_task">Annuler le téléchargement</string>
<string name="run_command">Exécuter la commande</string>
<string name="command_download_notification_channel_name">Téléchargements de commandes</string>
<string name="command_download_notification_channel_description">Notification indiquant la progression du téléchargement à l\'aide d\'une commande yt-dlp</string>

View file

@ -194,7 +194,7 @@
<string name="finished_download_notification_channel_description">पूर्ण डाउनलोड के लिए नोटीफिकेशन</string>
<string name="quick_download">त्वरित डाउनलोड</string>
<string name="quick_download_summary">डाउनलोड कार्ड में डेटा न लाएं</string>
<string name="cancel_download">डाउनलोड रद्द करें</string>
<string name="cancel_task">डाउनलोड रद्द करें</string>
<string name="command_download_notification_channel_name">कमांड डाउनलोड</string>
<string name="command_download_notification_channel_description">yt-dlp कमांड का उपयोग करके डाउनलोडिंग की प्रगति दिखाने वाली अधिसूचना</string>
<string name="url">यूआरएल</string>

View file

@ -5,7 +5,7 @@
<string name="ytdl_already_updating">Új verzió telepítése</string>
<string name="sponsorblock_fillers">Fiók</string>
<string name="sponsorblock_reminders">Előfizetési emlékeztetők</string>
<string name="cancel_download">Letöltés megszakítása</string>
<string name="cancel_task">Letöltés megszakítása</string>
<string name="logs">Naplók</string>
<string name="audio_quality">Audio minőség</string>
<string name="audio_format">Audio formátum</string>

View file

@ -79,7 +79,7 @@
<string name="audio_format">Format Audio</string>
<string name="audio_quality">Kualitas Audio</string>
<string name="audio_quality_summary">0 (terbaik) 10 (terburuk)</string>
<string name="cancel_download">Batalkan Pengunduhan</string>
<string name="cancel_task">Batalkan Pengunduhan</string>
<string name="api_key">Gunakan Kunci API</string>
<string name="ytdl_version">Versi yt-dlp</string>
<string name="Open_File">Buka Berkas</string>

View file

@ -64,7 +64,7 @@
<string name="video_format">Formato video</string>
<string name="audio_quality">Qualità audio</string>
<string name="audio_quality_summary">0 (migliore) 10 (peggiore)</string>
<string name="cancel_download">Annulla download</string>
<string name="cancel_task">Annulla download</string>
<string name="run_command">Esegui comando</string>
<string name="command_download_notification_channel_name">Download di comandi</string>
<string name="command_download_notification_channel_description">Notifica che mostra l\'avanzamento del download utilizzando un comando yt-dlp</string>

View file

@ -4,7 +4,7 @@
<string name="sponsorblock_intro">פתיח</string>
<string name="ytdl_already_updating">מעדכן גירסה חדשה</string>
<string name="sponsorblock_reminders">הזכרות הרשמה</string>
<string name="cancel_download">בטל הורדה</string>
<string name="cancel_task">בטל הורדה</string>
<string name="audio_quality">איכות אודיו</string>
<string name="audio_format">פורמט אודיו</string>
<string name="title">כותרת</string>

View file

@ -77,7 +77,7 @@
<string name="audio">音声</string>
<string name="aria2_summary">ダウンローダーとして Aria2c を使用します</string>
<string name="audio_quality">音質</string>
<string name="cancel_download">ダウンロードをキャンセル</string>
<string name="cancel_task">ダウンロードをキャンセル</string>
<string name="save_thumb">サムネイルを保存</string>
<string name="add_chapters_summary">YouTube / SponsorBlock の登録シーンを動画のチャプターとして表示</string>
<string name="run_command">コマンドを実行</string>

View file

@ -56,7 +56,7 @@
<string name="aria2_summary">기본값 대신 aria2c를 다운로더로 사용</string>
<string name="embed_subs_summary">비디오에 자막 추가</string>
<string name="audio_quality_summary">최고 (0) 최저 (10)</string>
<string name="cancel_download">다운로드 취소</string>
<string name="cancel_task">다운로드 취소</string>
<string name="command_download_notification_channel_name">명령 다운로드</string>
<string name="items_left">항목 남음</string>
<string name="history_is_empty">기록이 비어 있습니다</string>

View file

@ -135,7 +135,7 @@
<string name="home">Sākums</string>
<string name="title">Nosaukums</string>
<string name="video_format">Video Formāts</string>
<string name="cancel_download">Atcelt Lejupielādi</string>
<string name="cancel_task">Atcelt Lejupielādi</string>
<string name="limit_rate_summary">Maksimālais lejupielādes ātrums (baitos sekundē), piem., 50K vai 4.2M</string>
<string name="aria2_summary">Izmantot aria2c kā lejupielādētāju, nevis noklusējuma</string>
<string name="add_chapters_summary">Atzīmē YouTube / SponsorBlock segmentus kā video nodaļas</string>

View file

@ -24,7 +24,7 @@
<string name="embed_thumb">Miniatyrbildeomslag</string>
<string name="save_thumb_summary">Lagre miniatyrbildet i nedlastingsmappen</string>
<string name="audio_quality_summary">0 (best) 10 (verst)</string>
<string name="cancel_download">Avbryt nedlasting</string>
<string name="cancel_task">Avbryt nedlasting</string>
<string name="command_download_notification_channel_description">Merknad som viser nedlastingsfremdrift for bruk av yt-dlp -kommando</string>
<string name="history_is_empty">Historikken er tom.</string>
<string name="api_key_summary">Raskere resultater fra YouTube</string>

View file

@ -4,7 +4,7 @@
<string name="command_download_notification_channel_name">Commando Downloads</string>
<string name="sponsorblock_intro">Intro</string>
<string name="ytdl_already_updating">Nieuwe versie aan het installeren</string>
<string name="cancel_download">Download Annuleren</string>
<string name="cancel_task">Download Annuleren</string>
<string name="audio_quality">Audio Kwaliteit</string>
<string name="audio_format">Audio Formaat</string>
<string name="title">Titel</string>

View file

@ -47,7 +47,7 @@
<string name="save_thumb_summary">Gøym småbilete i lag med videoar/lydar</string>
<string name="audio_format">Lydformat</string>
<string name="embed_subs_summary">Bygg inn undertekster i videoar</string>
<string name="cancel_download">Avbryt hentinga</string>
<string name="cancel_task">Avbryt hentinga</string>
<string name="run_command">Køyr påbod</string>
<string name="command_download_notification_channel_name">Henta med påbod</string>
<string name="command_download_notification_channel_description">Varsel som viser kvar langt hentingar med yt-dlp-påbod har kome</string>

View file

@ -75,7 +75,7 @@
<string name="run_command">ਕਮਾਂਡ ਚਲਾਓ</string>
<string name="command_download_notification_channel_description">ਇੱਕ yt-dlp ਕਮਾਂਡ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਡਾਊਨਲੋਡ ਕਰਨ ਦੀ ਪ੍ਰਗਤੀ ਨੂੰ ਦਰਸਾਉਂਦਾ ਨੋਟੀਫਿਕੇਸ਼ਨ</string>
<string name="sort_by">ਛਾਂਟੀ</string>
<string name="cancel_download">ਡਾਊਨਲੋਡ ਰੱਦ ਕਰੋ</string>
<string name="cancel_task">ਡਾਊਨਲੋਡ ਰੱਦ ਕਰੋ</string>
<string name="command_download_notification_channel_name">ਕਮਾਂਡ ਡਾਊਨਲੋਡ</string>
<string name="link_copied_to_clipboard">ਲਿੰਕ ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ</string>
<string name="api_key">API ਕੁੰਜੀ ਦੀ ਵਰਤੋਂ ਕਰੋ</string>

View file

@ -81,7 +81,7 @@
<string name="audio_format">Format dźwięku</string>
<string name="video_format">Format filmu</string>
<string name="audio_quality_summary">0 (najlepsze) 10 (najgorsze)</string>
<string name="cancel_download">Anuluj pobieranie</string>
<string name="cancel_task">Anuluj pobieranie</string>
<string name="command_download_notification_channel_name">Pobrania przy użyciu poleceń</string>
<string name="history_is_empty">Historia jest pusta</string>
<string name="you_are_going_to_delete">Usuwanie</string>

View file

@ -84,7 +84,7 @@
<string name="author">Autor</string>
<string name="run_command">Executar comando</string>
<string name="audio_quality_summary">0 (melhor) 10 (pior)</string>
<string name="cancel_download">Cancelar download</string>
<string name="cancel_task">Cancelar download</string>
<string name="update_app_summary">Verifica se há atualizações ao iniciar o aplicativo</string>
<string name="configure_download">Ajustar download</string>
<string name="save_thumb_summary">Salve a miniatura na pasta de download</string>

View file

@ -60,7 +60,7 @@
<string name="audio">Áudio</string>
<string name="save_thumb">Guardar miniatura</string>
<string name="save_thumb_summary">Guardar miniatura na pasta de descargas</string>
<string name="cancel_download">Cancelar descarga</string>
<string name="cancel_task">Cancelar descarga</string>
<string name="run_command">Executar comando</string>
<string name="command_download_notification_channel_name">Comando e descargas</string>
<string name="command_download_notification_channel_description">Notificação que mostra o progresso das descargas usando um comando yt-dlp</string>

View file

@ -61,7 +61,7 @@
<string name="export_from_clipboard">Exportă în clipboard</string>
<string name="create_template">Creați un șablon</string>
<string name="audio_quality">Calitate audio</string>
<string name="cancel_download">Anulați descărcarea</string>
<string name="cancel_task">Anulați descărcarea</string>
<string name="defaultValue">Valoare implicită</string>
<string name="template">Șablon</string>
<string name="date_added">Data adăugării</string>

View file

@ -39,7 +39,7 @@
<string name="video_format">Видео формат</string>
<string name="audio_quality">Качество аудио</string>
<string name="audio_quality_summary">0 (лучшее) 10 (худшее)</string>
<string name="cancel_download">Отменить загрузку</string>
<string name="cancel_task">Отменить загрузку</string>
<string name="run_command">Запустить команду</string>
<string name="items_left">фрагмент(ов) осталось</string>
<string name="you_are_going_to_delete">Удаление</string>

View file

@ -2,7 +2,7 @@
<resources>
<string name="save_thumb_summary">Uložiť miniatúru do priečinka prevzatých súborov</string>
<string name="ytdl_already_updating">Inštaluje sa nová verzia</string>
<string name="cancel_download">Zrušiť Sťahovanie</string>
<string name="cancel_task">Zrušiť Sťahovanie</string>
<string name="audio_quality">Kvalita Zvuku</string>
<string name="audio_format">Formát Zvuku</string>
<string name="music_directory">Hudobný priečinok</string>

View file

@ -69,7 +69,7 @@
<string name="video_format">Formati i Videos</string>
<string name="audio_quality">Kualiteti i Audios</string>
<string name="audio_quality_summary">0 (Më i miri) 10 (Më i keqi)</string>
<string name="cancel_download">Anulo Shkarkimin</string>
<string name="cancel_task">Anulo Shkarkimin</string>
<string name="run_command">Ekzekuto Komandën</string>
<string name="command_download_notification_channel_name">Shkarkimet e Skedarëve nga Komanda</string>
<string name="command_download_notification_channel_description">Njoftimi i cili tregon progresin e shkarkimeve te skedarëve audio dhe video nga komanda</string>

View file

@ -13,7 +13,7 @@
<string name="embed_metadata_summary">Рашчланите и уградите метаподатке у фајл аудио снимка</string>
<string name="sponsorblock_fillers">Попуњавања</string>
<string name="sponsorblock_reminders">Подсетници о праћењу</string>
<string name="cancel_download">Откажи преузимање</string>
<string name="cancel_task">Откажи преузимање</string>
<string name="logs">Евиденције</string>
<string name="export_file">Извези фајл</string>
<string name="in_queue">У редоследу</string>

View file

@ -32,7 +32,7 @@
<string name="audio_format">รูปแบบเสียง</string>
<string name="video_format">รูปแบบวิดีโอ</string>
<string name="audio_quality">คุณภาพเสียง</string>
<string name="cancel_download">ยกเลิกการดาวน์โหลด</string>
<string name="cancel_task">ยกเลิกการดาวน์โหลด</string>
<string name="run_command">เรียกใช้คำสั่ง</string>
<string name="items_left">รายการที่เหลือ</string>
<string name="history_is_empty">ประวัติว่างเปล่า</string>

View file

@ -55,7 +55,7 @@
<string name="video_format">Video Formatı</string>
<string name="audio_quality">Ses Kalitesi</string>
<string name="audio_quality_summary">0 (en iyi) 10 (en kötü)</string>
<string name="cancel_download">İndirmeyi İptal Et</string>
<string name="cancel_task">İndirmeyi İptal Et</string>
<string name="run_command">Komutu Çalıştır</string>
<string name="command_download_notification_channel_name">Komut İndirmeleri</string>
<string name="command_download_notification_channel_description">Yt-dlp komutu kullanarak indirme işlemi ilerleyişini gösteren bildirim</string>

View file

@ -65,7 +65,7 @@
<string name="audio_format">Формат аудіо</string>
<string name="save_thumb_summary">Зберегти мініатюру в папці завантаження</string>
<string name="audio_quality_summary">0 (найкраще) 10 (найгірше)</string>
<string name="cancel_download">Скасувати завантаження</string>
<string name="cancel_task">Скасувати завантаження</string>
<string name="run_command">Виконати команду</string>
<string name="command_download_notification_channel_name">Команда завантажує</string>
<string name="items_left">елементів залишилося</string>

View file

@ -43,7 +43,7 @@
<string name="video_format">Định dạng cuộn băng</string>
<string name="audio_quality">Chất lượng âm thanh</string>
<string name="audio_quality_summary">0 (tốt nhất) 10 (tệ nhất)</string>
<string name="cancel_download">Hủy tải xuống</string>
<string name="cancel_task">Hủy tải xuống</string>
<string name="run_command">Chạy lệnh</string>
<string name="command_download_notification_channel_name">Lệnh tải xuống</string>
<string name="command_download_notification_channel_description">Thông báo hiển thị tiến trình tải xuống bằng lệnh yt-dlp</string>

View file

@ -50,7 +50,7 @@
<string name="embed_thumb">缩略图封面</string>
<string name="save_thumb">保存缩略图</string>
<string name="video_format">视频格式</string>
<string name="cancel_download">取消下载</string>
<string name="cancel_task">取消下载</string>
<string name="you_are_going_to_delete">删除</string>
<string name="downloads">下载</string>
<string name="sort_by">排序</string>

View file

@ -35,7 +35,7 @@
<string name="add_chapters">影片章節</string>
<string name="save_thumb_summary">將縮圖保存在下載資料夾中</string>
<string name="audio_format">音訊格式</string>
<string name="cancel_download">取消下載</string>
<string name="cancel_task">取消下載</string>
<string name="run_command">執行命令</string>
<string name="link_copied_to_clipboard">連結已複製到剪貼簿</string>
<string name="api_key">使用 API 金鑰</string>

View file

@ -1086,6 +1086,7 @@
<item>@string/saved</item>
<item>@string/command_templates</item>
<item>@string/cookies</item>
<item>@string/multiple_download_card</item>
</array>
<array name="swipe_gestures_values">
@ -1096,6 +1097,7 @@
<item>saved</item>
<item>templates</item>
<item>cookies</item>
<item>multipledownloadcard</item>
</array>
<array name="ytdlp_source">
@ -1335,4 +1337,17 @@
<item>Zulu</item>
</string-array>
<array name="format_filtering">
<item>@string/all</item>
<item>@string/suggested</item>
<item>@string/format_smallest</item>
</array>
<array name="format_filtering_values">
<item>ALL</item>
<item>SUGGESTED</item>
<item>SMALLEST</item>
</array>
</resources>

View file

@ -58,7 +58,7 @@
<string name="video_format">Video Format</string>
<string name="audio_quality">Audio Quality</string>
<string name="audio_quality_summary">0 (best) 10 (worst)</string>
<string name="cancel_download">Cancel Download</string>
<string name="cancel_task">Cancel Task</string>
<string name="run_command">Run Command</string>
<string name="command_download_notification_channel_name">Command Downloads</string>
<string name="command_download_notification_channel_description">Notification showing the progress of downloading using a yt-dlp command</string>
@ -85,8 +85,8 @@
<string name="video_quality">Video Quality</string>
<string name="embed_subtitles">Embed Subtitles</string>
<string name="add_chapter">Add Chapters</string>
<string name="show_download_card">Show Options for Downloading</string>
<string name="download_card_summary">Settings for how to download items</string>
<string name="show_download_card">Show Download Card</string>
<string name="download_card_summary">Modify settings for that item before downloading. Disabling this option starts the download immediately</string>
<string name="Open_File">Open File</string>
<string name="you_are_going_to_delete_multiple_items">Deleting multiple items</string>
<string name="delete_file_too">Delete the file from the device</string>
@ -331,4 +331,11 @@
<string name="source">Source</string>
<string name="app">App</string>
<string name="cut_unavailable">You need to update the item data in order to be able to use cut feature</string>
<string name="changed_path_for_everyone_to">Changed every item\'s download path to:</string>
<string name="dismiss">Dismiss</string>
<string name="all">All</string>
<string name="generic">Generic</string>
<string name="multiple_download_card">Multiple Download Card</string>
<string name="format_filter">Format Filter</string>
<string name="format_smallest">Smallest format (per Resolution)</string>
</resources>

View file

@ -10,7 +10,7 @@
app:title="@string/ytdl_version"/>
<ListPreference
android:defaultValue="nightly"
android:defaultValue="stable"
android:entries="@array/ytdlp_source"
android:entryValues="@array/ytdlp_source_values"
android:icon="@drawable/baseline_source_24"
@ -49,6 +49,15 @@
app:useSimpleSummaryProvider="true"
app:title="@string/format_order" />
<ListPreference
android:defaultValue="all"
android:entries="@array/format_filtering"
android:entryValues="@array/format_filtering_values"
android:icon="@drawable/baseline_align_horizontal_left_24"
app:key="format_filter"
app:useSimpleSummaryProvider="true"
app:title="@string/format_filter" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"

View file

@ -0,0 +1,17 @@
## What's New
- Increased the height of the history card a slight bit
- Fixed app not using the -S format sorter in normal GUI downloads
- Added the multiple download card as an option to disable the swipe gestures
- Added a recyclerview to the search results because they were buggy before and would not show sometimes
- Added a new history item card design in cases if you quick-downloaded a playlist or if you made multiple cuts of a file. Instead of being separate, they are bundled and you can share them all at once and if you want to open the file a new dialog will show telling you to choose which one. If any of them is deleted, they are greyed out
- Fixed the shortcuts behaviour by allowing duplicate shortcuts to be added to textbox and to not check
- Added format filtering in the format list bottom sheet. You can filter between ALL, SUGGESTED, SMALLEST, GENERIC. Suggested formats are built upon your app preferences like your preferred codec, container, quality. Smallest formats are all formats for each resolution with the smallest filesize. Generic are formats you see if an item has not updated the formats yet, in case you want to use those for some reason
- Added clipboard button right in the home screen for quick access to the url you copied instead of going to the searchview. That item is also available there aswell
- Added ability to show the URL in a history item if the download produced no title for it
- Added zoom-in zoom-out slider for logs and terminal screens.
- Added more options of datafetching for titles and authors, like track, alt_title to avoid the default youtube naming scheme when available. Useful for audio downloads
- Since some people are confused. In the download card each tab item is independent and they each create a distinct download item and when you press download the app picks from the tab you are currently. If you want to select the audio format in the video tab you can just open the format list and scroll to the bottom for the audio formats. You can select multiple if you want and then press ok. But since some people thought that selecting the audio in audio tab would apply to the video tab, i added the ability to synchronise that format automatically in case people dont know about this lol
- Other small bug fixes
The history item has been updated in this version so old backups will not work. Please make a backup of your stuff in the settings before updating. In case this update fails, notify people in telegram and revert back to the past version and use your backup and also give us the backup to reproduce the error if it happens.
Happy Downloading & Happy belated New Year :)