CHANGELOG

- added orange theme (its like yellow but slightly darker)
- added enforce keyframes at cuts preferences (it will also apply when you remove sponsorblock items)
- fixed cookies not getting saved on older droids
- fixed app making new folder instead of merging when moving files
- added ability to put multiple preferred format ids separated by commas for both audio and video
- added support for piped links
- fixed bug where if an item has no formats and you update audio formats and then go to video tab it will not show the video formats too
- made terminal share icon hidden by default
This commit is contained in:
deniscerri 2023-07-20 22:38:45 +02:00
parent bb282424a8
commit 4227b1e43c
No known key found for this signature in database
GPG key ID: 95C43D517D830350
23 changed files with 448 additions and 226 deletions

View file

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 6 def versionMinor = 6
def versionPatch = 3 def versionPatch = 3
def versionBuild = 5 // bump for dogfood builds, public betas, etc. def versionBuild = 9 // bump for dogfood builds, public betas, etc.
def versionExt = "" def versionExt = ""
if (versionBuild > 0){ if (versionBuild > 0){
@ -206,4 +206,6 @@ dependencies {
// For RTSP playback support with ExoPlayer // For RTSP playback support with ExoPlayer
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version") implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
implementation("androidx.media3:media3-datasource-cronet:$media3_version") implementation("androidx.media3:media3-datasource-cronet:$media3_version")
implementation "com.anggrayudi:storage:1.5.5"
} }

View file

@ -2,13 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -19,7 +12,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" /> <uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -38,8 +31,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/BaseTheme" android:theme="@style/BaseTheme"
tools:ignore="DataExtractionRules" tools:ignore="DataExtractionRules"
tools:targetApi="tiramisu" tools:targetApi="tiramisu">
android:banner="@mipmap/ic_launcher">
<profileable <profileable
android:shell="true" android:shell="true"
tools:targetApi="29" /> tools:targetApi="29" />
@ -49,18 +41,7 @@
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:exported="true" android:exported="true"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/txt" />
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".receiver.ShareActivity" android:name=".receiver.ShareActivity"
@ -114,7 +95,7 @@
<activity-alias <activity-alias
android:name=".terminalShareAlias" android:name=".terminalShareAlias"
android:targetActivity=".ui.more.TerminalActivity" android:targetActivity=".ui.more.TerminalActivity"
android:enabled="true" android:enabled="false"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -126,17 +107,20 @@
<activity-alias <activity-alias
android:name=".Default" android:name=".Default"
android:enabled="true" android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize" android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
android:targetActivity=".MainActivity" android:targetActivity=".MainActivity"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/txt" />
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
@ -152,9 +136,12 @@
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/txt" />
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>
@ -169,9 +156,12 @@
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/txt" />
</intent-filter> </intent-filter>
</activity-alias> </activity-alias>

View file

@ -71,8 +71,12 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
if (!hasCookies()) throw Exception("There is no cookies in the database!") if (!hasCookies()) throw Exception("There is no cookies in the database!")
flush() flush()
} }
var dbPath = "/data/data/com.deniscerri.ytdl/app_webview/Cookies"
if (!File(dbPath).exists()) dbPath = "/data/data/com.deniscerri.ytdl/app_webview/Default/Cookies"
SQLiteDatabase.openDatabase( SQLiteDatabase.openDatabase(
"/data/data/com.deniscerri.ytdl/app_webview/Default/Cookies", null, OPEN_READONLY dbPath, null, OPEN_READONLY
).run { ).run {
val projection = arrayOf( val projection = arrayOf(
CookieObject.HOST, CookieObject.HOST,

View file

@ -68,8 +68,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private var defaultVideoFormats : MutableList<Format> private var defaultVideoFormats : MutableList<Format>
private val videoQualityPreference: String private val videoQualityPreference: String
private val formatIDPreference: String private val formatIDPreference: List<String>
private val audioFormatIDPreference: String private val audioFormatIDPreference: List<String>
private val resources : Resources private val resources : Resources
enum class Type { enum class Type {
audio, video, command audio, video, command
@ -92,8 +92,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
erroredDownloads = repository.erroredDownloads.asLiveData() erroredDownloads = repository.erroredDownloads.asLiveData()
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString() videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
formatIDPreference = sharedPreferences.getString("format_id", "").toString() formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",")
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString() audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",")
val confTmp = Configuration(application.resources.configuration) val confTmp = Configuration(application.resources.configuration)
confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!) confTmp.locale = Locale(sharedPreferences.getString("app_language", "en")!!)
@ -170,7 +170,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!)) val audioPreferences = AudioPreferences(embedThumb, false, ArrayList(sponsorblock!!))
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, audioFormatIDs = ArrayList(resultItem.formats.filter { it.format_id == audioFormatIDPreference }.map { it.format_id }))
val hasPreferredAudioFormats = resultItem.formats.filter { audioFormatIDPreference.contains(it.format_id) }
val videoPreferences = VideoPreferences(embedSubs, addChapters, false, ArrayList(sponsorblock), saveSubs, audioFormatIDs = if (hasPreferredAudioFormats.isEmpty()) arrayListOf() else arrayListOf(hasPreferredAudioFormats.map { it.format_id }.first()))
return DownloadItem(0, return DownloadItem(0,
resultItem.url, resultItem.url,
@ -317,7 +319,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return cloneFormat ( return cloneFormat (
try { try {
try{ try{
formats.first { it.format_note.contains("audio", ignoreCase = true) && it.format_id == audioFormatIDPreference } formats.first { it.format_note.contains("audio", ignoreCase = true) && audioFormatIDPreference.contains(it.format_id)}
}catch (e: Exception){ }catch (e: Exception){
formats.last { it.format_note.contains("audio", ignoreCase = true) } formats.last { it.format_note.contains("audio", ignoreCase = true) }
} }
@ -332,7 +334,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
try { try {
val theFormats = formats.ifEmpty { defaultVideoFormats } val theFormats = formats.ifEmpty { defaultVideoFormats }
try { try {
formats.first { !it.format_note.contains("audio", ignoreCase = true) && it.format_id == formatIDPreference } formats.first { !it.format_note.contains("audio", ignoreCase = true) && formatIDPreference.contains(it.format_id)}
}catch (e: Exception){ }catch (e: Exception){
when (videoQualityPreference) { when (videoQualityPreference) {
"worst" -> { "worst" -> {
@ -397,7 +399,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> { fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
val list : MutableList<DownloadItem> = mutableListOf() val list : MutableList<DownloadItem> = mutableListOf()
val preferredType = sharedPreferences.getString("preferred_download_type", "video"); val preferredType = sharedPreferences.getString("preferred_download_type", "video")
items.forEach { items.forEach {
list.add(createDownloadItemFromResult(it!!, Type.valueOf(preferredType!!))) list.add(createDownloadItemFromResult(it!!, Type.valueOf(preferredType!!)))
} }
@ -454,12 +456,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads() val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true) val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
val queuedItems = mutableListOf<DownloadItem>() val queuedItems = mutableListOf<DownloadItem>()
var lastDownloadId = repository.getLastDownloadId()
var exists = false var exists = false
items.forEach { items.forEach {
lastDownloadId++
it.status = DownloadRepository.Status.Queued.toString() it.status = DownloadRepository.Status.Queued.toString()
if (activeAndQueuedDownloads.firstOrNull{d -> if (activeAndQueuedDownloads.firstOrNull{d ->
d.id = 0 d.id = 0
@ -469,7 +468,6 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
exists = true exists = true
}else{ }else{
if (it.id == 0L){ if (it.id == 0L){
it.id = lastDownloadId
val insert = async {repository.insert(it)} val insert = async {repository.insert(it)}
val id = insert.await() val id = insert.await()
it.id = id it.id = id
@ -492,7 +490,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
var delay = if (it.downloadStartTime != 0L){ var delay = if (it.downloadStartTime != 0L){
it.downloadStartTime - currentTime it.downloadStartTime - currentTime
} else 0 } else 0
if (delay < 0L) delay = 0L if (delay < 0L) delay = 1L
val workConstraints = Constraints.Builder() val workConstraints = Constraints.Builder()
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED) if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
@ -501,7 +499,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
.setInputData(Data.Builder().putLong("id", it.id).build()) .setInputData(Data.Builder().putLong("id", it.id).build())
.addTag("download") .addTag("download")
.setConstraints(workConstraints.build()) .setConstraints(workConstraints.build())
.setInitialDelay(delay, TimeUnit.MILLISECONDS) .setInitialDelay(delay, TimeUnit.SECONDS)
.build() .build()
WorkManager.getInstance(context).beginUniqueWork( WorkManager.getInstance(context).beginUniqueWork(

View file

@ -103,7 +103,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
} }
private fun getQueryType(inputQuery: String) : String { private fun getQueryType(inputQuery: String) : String {
var type = "Search" var type = "Search"
val p = Pattern.compile("^(https?)://(www.)?(music.)?youtu(.be)?") val p = Pattern.compile("(^(https?)://(www.)?(music.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
val m = p.matcher(inputQuery) val m = p.matcher(inputQuery)
if (m.find()) { if (m.find()) {
type = "YT_Video" type = "YT_Video"

View file

@ -140,6 +140,9 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
res res
} }
if (chosenFormats.isEmpty()) throw Exception() if (chosenFormats.isEmpty()) throw Exception()
formats = listOf(res)
//playlist format filtering //playlist format filtering
}else{ }else{
var progress = "0/${items.size}" var progress = "0/${items.size}"
@ -154,6 +157,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
formatCollection.add(it) formatCollection.add(it)
} }
} }
formats = formatCollection
val flatFormatCollection = formatCollection.flatten() val flatFormatCollection = formatCollection.flatten()
val commonFormats = flatFormatCollection.groupingBy { it.format_id }.eachCount().filter { it.value == items.size }.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }.map { it.value } val commonFormats = flatFormatCollection.groupingBy { it.format_id }.eachCount().filter { it.value == items.size }.mapValues { flatFormatCollection.first { f -> f.format_id == it.key } }.map { it.value }
chosenFormats = commonFormats.filter { it.filesize != 0L }.mapTo(mutableListOf()) {it.copy()} chosenFormats = commonFormats.filter { it.filesize != 0L }.mapTo(mutableListOf()) {it.copy()}
@ -196,7 +200,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
//simple video format selection //simple video format selection
if (items.size == 1){ if (items.size == 1){
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(selectedVideo, selectedAudios))) listener.onFormatClick(formats, listOf(FormatTuple(selectedVideo, selectedAudios)))
}else{ }else{
//playlist format selection //playlist format selection
val selectedFormats = mutableListOf<Format>() val selectedFormats = mutableListOf<Format>()
@ -208,7 +212,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
selectedFormats.add(selectedVideo) selectedFormats.add(selectedVideo)
} }
} }
listener.onFormatClick(formatCollection, selectedFormats.map { FormatTuple(it, selectedAudios) }) listener.onFormatClick(formats, selectedFormats.map { FormatTuple(it, selectedAudios) })
} }
dismiss() dismiss()
@ -260,7 +264,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
val clickedCard = (clickedformat as MaterialCardView) val clickedCard = (clickedformat as MaterialCardView)
if (format.vcodec.isNotBlank() && format.vcodec != "none") { if (format.vcodec.isNotBlank() && format.vcodec != "none") {
if (clickedCard.isChecked) { if (clickedCard.isChecked) {
listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null))) listener.onFormatClick(formats, listOf(FormatTuple(format, null)))
dismiss() dismiss()
} }
videoFormatList.forEach { (it as MaterialCardView).isChecked = false } videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
@ -279,7 +283,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
} }
}else{ }else{
if (items.size == 1){ if (items.size == 1){
listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null))) listener.onFormatClick(formats, listOf(FormatTuple(format, null)))
}else{ }else{
val selectedFormats = mutableListOf<Format>() val selectedFormats = mutableListOf<Format>()
formatCollection.forEach { formatCollection.forEach {
@ -290,7 +294,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
selectedFormats.add(format) selectedFormats.add(format)
} }
} }
listener.onFormatClick(formatCollection, selectedFormats.map { FormatTuple(it, null) }) listener.onFormatClick(formats, selectedFormats.map { FormatTuple(it, null) })
} }
dismiss() dismiss()
} }

View file

@ -447,10 +447,15 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java) val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
item.id = 0L item.id = 0L
cancelled.add(item) cancelled.add(item)
}
cancelled.asReversed().forEach { f ->
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.insert(item) downloadViewModel.insert(f)
} }
} }
if(cancelled.isNotEmpty()){ if(cancelled.isNotEmpty()){
finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n") finalMessage.append("${getString(R.string.cancelled)}: ${cancelled.count()}\n")
} }
@ -464,10 +469,15 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java) val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
item.id = 0L item.id = 0L
errored.add(item) errored.add(item)
}
errored.asReversed().forEach { f ->
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.insert(item) downloadViewModel.insert(f)
} }
} }
if(errored.isNotEmpty()){ if(errored.isNotEmpty()){
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n") finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
} }
@ -491,13 +501,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
//command template restore //command template restore
if(json.has("templates")){ if(json.has("templates")){
val items = json.getAsJsonArray("templates") val items = json.getAsJsonArray("templates")
val templates = mutableListOf<CommandTemplate>()
items.forEach { items.forEach {
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java) val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
item.id = 0L item.id = 0L
templates.add(item)
}
templates.asReversed().forEach { t ->
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
commandTemplateViewModel.insert(item) commandTemplateViewModel.insert(t)
} }
} }
if(items.count() > 0){ if(items.count() > 0){
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n") finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
} }

View file

@ -2,15 +2,21 @@ package com.deniscerri.ytdlnis.util
import android.content.Context import android.content.Context
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.DocumentsContract
import android.util.Log import android.util.Log
import android.webkit.MimeTypeMap import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.callback.FileCallback
import com.anggrayudi.storage.callback.FolderCallback
import com.anggrayudi.storage.file.copyFileTo
import com.anggrayudi.storage.file.copyFolderTo
import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo
import com.anggrayudi.storage.file.moveFolderTo
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import kotlinx.coroutines.Dispatchers
import okhttp3.internal.closeQuietly import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -70,81 +76,105 @@ object FileUtil {
@Throws(Exception::class) @Throws(Exception::class)
fun moveFile(originDir: File, context: Context, destDir: String, keepCache: Boolean, progress: (p: Int) -> Unit) : List<String> { suspend fun moveFile(originDir: File, context: Context, destDir: String, keepCache: Boolean, progress: (p: Int) -> Unit) : List<String> {
val fileList = mutableListOf<File>() return withContext(Dispatchers.Main){
val dir = File(formatPath(destDir)) val fileList = mutableListOf<String>()
if (!dir.exists()) dir.mkdirs() val dir = File(formatPath(destDir))
var currentDirectory = dir if (!dir.exists()) dir.mkdirs()
originDir.walk().forEach { originDir.walk().forEach {
var destFile = File(dir.absolutePath + "/${it.absolutePath.removePrefix(originDir.absolutePath)}") if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
if (it.isDirectory) { var destFile: DocumentFile
destFile.mkdirs() try {
currentDirectory = destFile if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)".toRegex())){
return@forEach it.delete()
} return@forEach
try { }
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)".toRegex())){
it.delete() if(it.name.contains(".part-Frag")) return@forEach
val curr = DocumentFile.fromFile(it)
val dst = DocumentFile.fromTreeUri(context, destDir.toUri())
if (it.isDirectory){
withContext(Dispatchers.IO){
curr.copyFolderTo(context, dst!!, skipEmptyFiles = false, callback = object : FolderCallback() {
override fun onStart(folder: DocumentFile, totalFilesToCopy: Int, workerThread: Thread): Long {
return 1000 // update progress every 1 second
}
override fun onParentConflict(destinationFolder: DocumentFile, action: ParentFolderConflictAction, canMerge: Boolean) {
if (canMerge){
action.confirmResolution(ConflictResolution.MERGE)
}else{
action.confirmResolution(ConflictResolution.CREATE_NEW)
}
}
override fun onReport(report: Report) {
progress(report.progress.toInt())
}
override fun onCompleted(result: Result) {
fileList.addAll(result.folder.listFiles().map { f -> f.getAbsolutePath(context) })
it.deleteRecursively()
}
})
}
}else{
withContext(Dispatchers.IO){
curr.copyFileTo(context, dst!!, callback = object : FileCallback() {
override fun onStart(file: Any, workerThread: Thread): Long {
return 1000 // update progress every 1 second
}
override fun onReport(report: Report) {
progress(report.progress.toInt())
}
override fun onCompleted(result: Any) {
destFile = (result as DocumentFile)
fileList.add(destFile.getAbsolutePath(context))
it.deleteRecursively()
super.onCompleted(result)
}
})
}
}
}catch (e: Exception) {
Log.e("error", e.message.toString())
val files = it.listFiles()?.filter { fil -> !fil.isDirectory }?.toTypedArray() ?: arrayOf(it)
for (ff in files){
val newFile = File(dir.absolutePath + "/${ff.absolutePath.removePrefix(originDir.absolutePath)}")
newFile.mkdirs()
if (Build.VERSION.SDK_INT >= 26 ) {
Files.move(
ff.toPath(),
newFile.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}else{
ff.renameTo(newFile)
}
fileList.add(newFile.absolutePath)
}
return@forEach return@forEach
} }
if(it.name.contains(".part-Frag")) return@forEach
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
destFile = File(currentDirectory.absolutePath + "/${it.name}")
val dest = Uri.parse(destDir).run {
DocumentsContract.buildDocumentUriUsingTree(
this,
DocumentsContract.getTreeDocumentId(this)
)
}
val destUri = DocumentsContract.createDocument(
context.contentResolver,
dest,
mimeType,
it.name
) ?: return@forEach
val inputStream = it.inputStream()
val outputStream =
context.contentResolver.openOutputStream(destUri) ?: return@forEach
inputStream.copyTo(outputStream)
inputStream.closeQuietly()
outputStream.closeQuietly()
fileList.add(destFile)
}catch (e: java.lang.Exception) {
Log.e("error", e.message.toString())
if (destFile.absolutePath.contains("/storage/emulated/0/Download")
|| destFile.absolutePath.contains("/storage/emulated/0/Documents")
){
if (Build.VERSION.SDK_INT >= 26 ){
Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}else{
it.renameTo(destFile)
}
fileList.add(destFile)
}
return@forEach
}catch(e : Exception){
Log.e("error", e.message.toString())
return@forEach
} }
if (!keepCache){
originDir.deleteRecursively()
}
return@withContext scanMedia(fileList, context)
} }
if (!keepCache){
originDir.deleteRecursively()
}
return scanMedia(fileList, context)
} }
private fun scanMedia(files: List<File>, context: Context) : List<String> { private fun scanMedia(files: List<String>, context: Context) : List<String> {
try { try {
val paths = files.map { it.absolutePath }.toTypedArray() val paths = files.sortedByDescending { File(it).length() }
MediaScannerConnection.scanFile(context, paths, null, null) MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null)
return files.sortedByDescending { it.length() }.map { it.absolutePath } return paths
}catch (e: Exception){ }catch (e: Exception){
e.printStackTrace() e.printStackTrace()
} }

View file

@ -68,7 +68,7 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class) @Throws(JSONException::class)
fun searchFromPiped(query: String): ArrayList<ResultItem?> { fun searchFromPiped(query: String): ArrayList<ResultItem?> {
val data = genericRequest("$pipedURL/search?q=$query&filter=videos") val data = genericRequest("$pipedURL/search?q=$query&filter=videos&region=${countryCODE}")
val dataArray = data.getJSONArray("items") val dataArray = data.getJSONArray("items")
if (dataArray.length() == 0) return getFromYTDL(query) if (dataArray.length() == 0) return getFromYTDL(query)
for (i in 0 until dataArray.length()) { for (i in 0 until dataArray.length()) {
@ -86,7 +86,7 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class) @Throws(JSONException::class)
fun searchFromPipedMusic(query: String): ArrayList<ResultItem?> { fun searchFromPipedMusic(query: String): ArrayList<ResultItem?> {
val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs") val data = genericRequest("$pipedURL/search?q=$query=&filter=music_songs&region=${countryCODE}")
val dataArray = data.getJSONArray("items") val dataArray = data.getJSONArray("items")
if (dataArray.length() == 0) return getFromYTDL(query) if (dataArray.length() == 0) return getFromYTDL(query)
for (i in 0 until dataArray.length()) { for (i in 0 until dataArray.length()) {
@ -350,9 +350,44 @@ class InfoUtil(private val context: Context) {
@Throws(JSONException::class) @Throws(JSONException::class)
fun getTrending(): ArrayList<ResultItem?> { fun getTrending(): ArrayList<ResultItem?> {
items = ArrayList() items = ArrayList()
if (sharedPreferences.getString("api_key", "")!!.isNotBlank()){
return getTrendingFromYoutubeAPI()
}
return getTrendingFromPiped() return getTrendingFromPiped()
} }
@Throws(JSONException::class)
fun getTrendingFromYoutubeAPI(): ArrayList<ResultItem?> {
val key = sharedPreferences.getString("api_key", "")!!
val url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode=$countryCODE&maxResults=25&key=$key"
//short data
val res = genericRequest(url)
//extra data from the same videos
val contentDetails =
genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10&regionCode=$countryCODE&maxResults=25&key=$key")
if (!contentDetails.has("items")) return ArrayList()
val dataArray = res.getJSONArray("items")
val extraDataArray = contentDetails.getJSONArray("items")
for (i in 0 until dataArray.length()) {
val element = dataArray.getJSONObject(i)
val snippet = element.getJSONObject("snippet")
var duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails")
.getString("duration")
duration = formatDuration(duration)
snippet.put("videoID", element.getString("id"))
snippet.put("duration", duration)
fixThumbnail(snippet)
val v = createVideofromJSON(snippet)
if (v == null || v.thumb.isEmpty()) {
continue
}
v.playlistTitle = context.getString(R.string.trendingPlaylist)
items.add(v)
}
return items
}
private fun getTrendingFromPiped(): ArrayList<ResultItem?> { private fun getTrendingFromPiped(): ArrayList<ResultItem?> {
val url = "$pipedURL/trending?region=$countryCODE" val url = "$pipedURL/trending?region=$countryCODE"
val res = genericArrayRequest(url) val res = genericArrayRequest(url)
@ -881,7 +916,10 @@ class InfoUtil(private val context: Context) {
} }
fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{ fun buildYoutubeDLRequest(downloadItem: DownloadItem) : YoutubeDLRequest{
val tempFileDir = File(context.cacheDir.absolutePath + "/downloads/" + downloadItem.id) val cacheDir = File(context.cacheDir.absolutePath + "/downloads/")
val tempFileDir = File(cacheDir, downloadItem.id.toString())
tempFileDir.delete()
tempFileDir.mkdirs()
val url = downloadItem.url val url = downloadItem.url
val request = YoutubeDLRequest(url) val request = YoutubeDLRequest(url)
@ -925,7 +963,12 @@ class InfoUtil(private val context: Context) {
if (sponsorBlockFilters.isNotEmpty()) { if (sponsorBlockFilters.isNotEmpty()) {
val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() }) val filters = java.lang.String.join(",", sponsorBlockFilters.filter { it.isNotBlank() })
if (filters.isNotBlank()) request.addOption("--sponsorblock-remove", filters) if (filters.isNotBlank()) {
request.addOption("--sponsorblock-remove", filters)
if (sharedPreferences.getBoolean("force_keyframes", false)){
request.addOption("--force-keyframes-at-cuts")
}
}
} }
if(downloadItem.title.isNotBlank()){ if(downloadItem.title.isNotBlank()){
@ -936,7 +979,7 @@ class InfoUtil(private val context: Context) {
} }
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$","")) request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader|${downloadItem.author})s - %(title)s" if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader|${downloadItem.author})s - %(title)s"
downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s") downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s")
if (downloadItem.downloadSections.isNotBlank()){ if (downloadItem.downloadSections.isNotBlank()){
downloadItem.downloadSections.split(";").forEach { downloadItem.downloadSections.split(";").forEach {
@ -946,7 +989,7 @@ class InfoUtil(private val context: Context) {
else else
request.addOption("--download-sections", it) request.addOption("--download-sections", it)
if (sharedPreferences.getBoolean("force_keyframes", false)){ if (sharedPreferences.getBoolean("force_keyframes", false) && !request.hasOption("--force-keyframes-at-cuts")){
request.addOption("--force-keyframes-at-cuts") request.addOption("--force-keyframes-at-cuts")
} }
} }
@ -1019,7 +1062,9 @@ class InfoUtil(private val context: Context) {
config.writeText(configData) config.writeText(configData)
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1") request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
request.addOption("--config", config.absolutePath) request.addOption("--config", config.absolutePath)
} catch (ignored: Exception) {} } catch (e: Exception) {
e.printStackTrace()
}
} }
} }
request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s") request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s")

View file

@ -48,6 +48,7 @@ object ThemeUtil {
"green" -> activity.setTheme(R.style.Theme_Green) "green" -> activity.setTheme(R.style.Theme_Green)
"purple" -> activity.setTheme(R.style.Theme_Purple) "purple" -> activity.setTheme(R.style.Theme_Purple)
"yellow" -> activity.setTheme(R.style.Theme_Yellow) "yellow" -> activity.setTheme(R.style.Theme_Yellow)
"orange" -> activity.setTheme(R.style.Theme_Orange)
"monochrome" -> activity.setTheme(R.style.Theme_Monochrome) "monochrome" -> activity.setTheme(R.style.Theme_Monochrome)
} }
@ -71,37 +72,41 @@ object ThemeUtil {
when (sharedPreferences.getString("ytdlnis_theme", "System")!!) { when (sharedPreferences.getString("ytdlnis_theme", "System")!!) {
"System" -> { "System" -> {
//set dynamic icon //set dynamic icon
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.Default") activity.packageManager.setComponentEnabledSetting(
activity.packageManager.setComponentEnabledSetting(aliasComponentName, ComponentName(activity.packageName, "com.deniscerri.ytdlnis.Default"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP) PackageManager.DONT_KILL_APP
)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
} }
"Light" -> { "Light" -> {
//set light icon //set light icon
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.LightIcon") activity.packageManager.setComponentEnabledSetting(
activity.packageManager.setComponentEnabledSetting(aliasComponentName, ComponentName(activity.packageName, "com.deniscerri.ytdlnis.LightIcon"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP) PackageManager.DONT_KILL_APP
)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
} }
"Dark" -> { "Dark" -> {
//set dark icon //set dark icon
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.DarkIcon") activity.packageManager.setComponentEnabledSetting(
activity.packageManager.setComponentEnabledSetting(aliasComponentName, ComponentName(activity.packageName, "com.deniscerri.ytdlnis.DarkIcon"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP) PackageManager.DONT_KILL_APP
)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} }
else -> { else -> {
//set dynamic icon //set dynamic icon
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.Default") activity.packageManager.setComponentEnabledSetting(
activity.packageManager.setComponentEnabledSetting(aliasComponentName, ComponentName(activity.packageName, "com.deniscerri.ytdlnis.Default"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP) PackageManager.DONT_KILL_APP
)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
} }
@ -109,7 +114,7 @@ object ThemeUtil {
} }
fun getThemeColor(context: Context, colorCode: Int): Int { private fun getThemeColor(context: Context, colorCode: Int): Int {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val accent = sharedPreferences.getString("theme_accent", "blue") val accent = sharedPreferences.getString("theme_accent", "blue")
return if (accent == "Default" || accent == "blue"){ return if (accent == "Default" || accent == "blue"){

View file

@ -29,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
@ -133,44 +134,47 @@ class DownloadWorker(
}.onSuccess { }.onSuccess {
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao) val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
var finalPaths : List<String>? CoroutineScope(Dispatchers.IO).launch {
//move file from internal to set download directory var finalPaths : List<String>?
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) //move file from internal to set download directory
try { setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p -> try {
setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) finalPaths = withContext(Dispatchers.IO){
} FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p ->
if (finalPaths.isNotEmpty()){ setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads)) }
}else{ }
if (finalPaths.isNotEmpty()){
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
}else{
finalPaths = listOf(context.getString(R.string.unfound_file))
}
}catch (e: Exception){
finalPaths = listOf(context.getString(R.string.unfound_file)) finalPaths = listOf(context.getString(R.string.unfound_file))
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
} }
}catch (e: Exception){
finalPaths = listOf(context.getString(R.string.unfound_file))
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
}
//put download in history //put download in history
val incognito = sharedPreferences.getBoolean("incognito", false) val incognito = sharedPreferences.getBoolean("incognito", false)
if (!incognito) { if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000 val unixtime = System.currentTimeMillis() / 1000
val file = File(finalPaths?.first()!!) val file = File(finalPaths?.first()!!)
downloadItem.format.filesize = file.length() downloadItem.format.filesize = file.length()
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id) val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
runBlocking {
historyDao.insert(historyItem) historyDao.insert(historyItem)
} }
}
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
notificationUtil.createDownloadFinished( notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, notificationUtil.createDownloadFinished(
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
) NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
)
}
if (wasQuickDownloaded){ if (wasQuickDownloaded){
runCatching { runCatching {

View file

@ -116,16 +116,18 @@ class TerminalDownloadWorker(
} }
} }
}.onSuccess { }.onSuccess {
//move file from internal to set download directory CoroutineScope(Dispatchers.IO).launch {
try { //move file from internal to set download directory
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation!!, false){ p -> try {
setProgressAsync(workDataOf("progress" to p)) FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation!!, false){ p ->
setProgressAsync(workDataOf("progress" to p))
}
}catch (e: Exception){
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
} }
}catch (e: Exception){
e.printStackTrace()
handler.postDelayed({
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}, 1000)
} }
if (it.out.length > 200){ if (it.out.length > 200){

View file

@ -3,7 +3,7 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="200dp" android:layout_marginBottom="200dp"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">

View file

@ -28,23 +28,18 @@
android:label="MainSettingsFragment" > android:label="MainSettingsFragment" >
<action <action
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment" android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
app:destination="@id/appearanceSettingsFragment" app:destination="@id/appearanceSettingsFragment" />
app:enterAnim="@anim/fade_in" />
<action <action
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment" android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
app:destination="@id/folderSettingsFragment" app:destination="@id/folderSettingsFragment" />
app:enterAnim="@anim/fade_in" />
<action <action
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment" android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
app:destination="@id/downloadSettingsFragment" app:destination="@id/downloadSettingsFragment" />
app:enterAnim="@anim/fade_in" />
<action <action
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment" android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
app:destination="@id/processingSettingsFragment" app:destination="@id/processingSettingsFragment" />
app:enterAnim="@anim/fade_in" />
<action <action
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment" android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
app:destination="@id/updateSettingsFragment" app:destination="@id/updateSettingsFragment" />
app:enterAnim="@anim/fade_in" />
</fragment> </fragment>
</navigation> </navigation>

View file

@ -203,6 +203,35 @@
</style> </style>
<style name="Theme.Orange" parent="BaseTheme">
<item name="colorPrimary">@color/orange_md_theme_dark_primary</item>
<item name="colorOnPrimary">@color/orange_md_theme_dark_onPrimary</item>
<item name="colorPrimaryContainer">@color/orange_md_theme_dark_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/orange_md_theme_dark_onPrimaryContainer</item>
<item name="colorSecondary">@color/orange_md_theme_dark_secondary</item>
<item name="colorOnSecondary">@color/orange_md_theme_dark_onSecondary</item>
<item name="colorSecondaryContainer">@color/orange_md_theme_dark_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/orange_md_theme_dark_onSecondaryContainer</item>
<item name="colorTertiary">@color/orange_md_theme_dark_tertiary</item>
<item name="colorOnTertiary">@color/orange_md_theme_dark_onTertiary</item>
<item name="colorTertiaryContainer">@color/orange_md_theme_dark_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/orange_md_theme_dark_onTertiaryContainer</item>
<item name="colorError">@color/orange_md_theme_dark_error</item>
<item name="colorErrorContainer">@color/orange_md_theme_dark_errorContainer</item>
<item name="colorOnError">@color/orange_md_theme_dark_onError</item>
<item name="colorOnErrorContainer">@color/orange_md_theme_dark_onErrorContainer</item>
<item name="android:colorBackground">@color/orange_md_theme_dark_background</item>
<item name="colorOnBackground">@color/orange_md_theme_dark_onBackground</item>
<item name="colorSurface">@color/orange_md_theme_dark_surface</item>
<item name="colorOnSurface">@color/orange_md_theme_dark_onSurface</item>
<item name="colorSurfaceVariant">@color/orange_md_theme_dark_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/orange_md_theme_dark_onSurfaceVariant</item>
<item name="colorOutline">@color/orange_md_theme_dark_outline</item>
<item name="colorOnSurfaceInverse">@color/orange_md_theme_dark_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/orange_md_theme_dark_inverseSurface</item>
<item name="colorPrimaryInverse">@color/orange_md_theme_dark_inversePrimary</item>
</style>
<style name="Pure"> <style name="Pure">
<item name="android:colorBackground">@android:color/black</item> <item name="android:colorBackground">@android:color/black</item>

View file

@ -224,6 +224,7 @@
<string name="red">E Kuqe</string> <string name="red">E Kuqe</string>
<string name="purple">Vjollcë</string> <string name="purple">Vjollcë</string>
<string name="yellow">E Verdhë</string> <string name="yellow">E Verdhë</string>
<string name="orange">Portokalli</string>
<string name="monochrome">Monokrom</string> <string name="monochrome">Monokrom</string>
<string name="green">Jeshile</string> <string name="green">Jeshile</string>
<string name="downloaded">"U shkarkua: "</string> <string name="downloaded">"U shkarkua: "</string>
@ -262,7 +263,7 @@
<string name="misc">Të Tjera</string> <string name="misc">Të Tjera</string>
<string name="audio_only_item">Ky artikull përmban vetëm formate audio</string> <string name="audio_only_item">Ky artikull përmban vetëm formate audio</string>
<string name="locale">Lokal</string> <string name="locale">Lokal</string>
<string name="general">Gjeneral</string> <string name="general">Të Përgjithshme</string>
<string name="swipe_gestures">Gjestet e rrëshqitjes</string> <string name="swipe_gestures">Gjestet e rrëshqitjes</string>
<string name="swipe_gestures_summary">Rrëshqitni artikujt për veprime të caktuara</string> <string name="swipe_gestures_summary">Rrëshqitni artikujt për veprime të caktuara</string>
<string name="crop_thumb">Preje Thumbnailin</string> <string name="crop_thumb">Preje Thumbnailin</string>

View file

@ -226,6 +226,7 @@
<item>@string/green</item> <item>@string/green</item>
<item>@string/purple</item> <item>@string/purple</item>
<item>@string/yellow</item> <item>@string/yellow</item>
<item>@string/orange</item>
<item>@string/monochrome</item> <item>@string/monochrome</item>
</array> </array>
@ -236,6 +237,7 @@
<item>green</item> <item>green</item>
<item>purple</item> <item>purple</item>
<item>yellow</item> <item>yellow</item>
<item>orange</item>
<item>monochrome</item> <item>monochrome</item>
</array> </array>

View file

@ -114,7 +114,7 @@
<color name="red_md_theme_dark_inverseSurface">#EDE0DE</color> <color name="red_md_theme_dark_inverseSurface">#EDE0DE</color>
<color name="red_md_theme_dark_inversePrimary">#9F4034</color> <color name="red_md_theme_dark_inversePrimary">#9F4034</color>
<color name="yellow_md_theme_light_primary">#745B00</color> <color name="yellow_md_theme_light_primary">#cf9b02</color>
<color name="yellow_md_theme_light_onPrimary">#FFFFFF</color> <color name="yellow_md_theme_light_onPrimary">#FFFFFF</color>
<color name="yellow_md_theme_light_primaryContainer">#FFE08D</color> <color name="yellow_md_theme_light_primaryContainer">#FFE08D</color>
<color name="yellow_md_theme_light_onPrimaryContainer">#241A00</color> <color name="yellow_md_theme_light_onPrimaryContainer">#241A00</color>
@ -273,6 +273,68 @@
<color name="purple_md_theme_dark_inverseSurface">#EAE0E3</color> <color name="purple_md_theme_dark_inverseSurface">#EAE0E3</color>
<color name="purple_md_theme_dark_inversePrimary">#9B3489</color> <color name="purple_md_theme_dark_inversePrimary">#9B3489</color>
<color name="orange_md_theme_light_primary">#855400</color>
<color name="orange_md_theme_light_onPrimary">#FFFFFF</color>
<color name="orange_md_theme_light_primaryContainer">#FFDDB7</color>
<color name="orange_md_theme_light_onPrimaryContainer">#2A1700</color>
<color name="orange_md_theme_light_secondary">#705B41</color>
<color name="orange_md_theme_light_onSecondary">#FFFFFF</color>
<color name="orange_md_theme_light_secondaryContainer">#FCDEBC</color>
<color name="orange_md_theme_light_onSecondaryContainer">#281905</color>
<color name="orange_md_theme_light_tertiary">#53643E</color>
<color name="orange_md_theme_light_onTertiary">#FFFFFF</color>
<color name="orange_md_theme_light_tertiaryContainer">#D6E9B9</color>
<color name="orange_md_theme_light_onTertiaryContainer">#121F03</color>
<color name="orange_md_theme_light_error">#BA1A1A</color>
<color name="orange_md_theme_light_errorContainer">#FFDAD6</color>
<color name="orange_md_theme_light_onError">#FFFFFF</color>
<color name="orange_md_theme_light_onErrorContainer">#410002</color>
<color name="orange_md_theme_light_background">#FFFBFF</color>
<color name="orange_md_theme_light_onBackground">#1F1B16</color>
<color name="orange_md_theme_light_surface">#FFFBFF</color>
<color name="orange_md_theme_light_onSurface">#1F1B16</color>
<color name="orange_md_theme_light_surfaceVariant">#F0E0D0</color>
<color name="orange_md_theme_light_onSurfaceVariant">#504539</color>
<color name="orange_md_theme_light_outline">#827568</color>
<color name="orange_md_theme_light_inverseOnSurface">#F9EFE7</color>
<color name="orange_md_theme_light_inverseSurface">#352F2A</color>
<color name="orange_md_theme_light_inversePrimary">#FFB95C</color>
<color name="orange_md_theme_light_shadow">#000000</color>
<color name="orange_md_theme_light_surfaceTint">#855400</color>
<color name="orange_md_theme_light_outlineVariant">#D4C4B5</color>
<color name="orange_md_theme_light_scrim">#000000</color>
<color name="orange_md_theme_dark_primary">#FFB95C</color>
<color name="orange_md_theme_dark_onPrimary">#462A00</color>
<color name="orange_md_theme_dark_primaryContainer">#653E00</color>
<color name="orange_md_theme_dark_onPrimaryContainer">#FFDDB7</color>
<color name="orange_md_theme_dark_secondary">#DFC2A2</color>
<color name="orange_md_theme_dark_onSecondary">#3F2D17</color>
<color name="orange_md_theme_dark_secondaryContainer">#57432B</color>
<color name="orange_md_theme_dark_onSecondaryContainer">#FCDEBC</color>
<color name="orange_md_theme_dark_tertiary">#BACD9F</color>
<color name="orange_md_theme_dark_onTertiary">#263514</color>
<color name="orange_md_theme_dark_tertiaryContainer">#3C4C28</color>
<color name="orange_md_theme_dark_onTertiaryContainer">#D6E9B9</color>
<color name="orange_md_theme_dark_error">#FFB4AB</color>
<color name="orange_md_theme_dark_errorContainer">#93000A</color>
<color name="orange_md_theme_dark_onError">#690005</color>
<color name="orange_md_theme_dark_onErrorContainer">#FFDAD6</color>
<color name="orange_md_theme_dark_background">#1F1B16</color>
<color name="orange_md_theme_dark_onBackground">#EBE1D9</color>
<color name="orange_md_theme_dark_surface">#1F1B16</color>
<color name="orange_md_theme_dark_onSurface">#EBE1D9</color>
<color name="orange_md_theme_dark_surfaceVariant">#504539</color>
<color name="orange_md_theme_dark_onSurfaceVariant">#D4C4B5</color>
<color name="orange_md_theme_dark_outline">#9C8E80</color>
<color name="orange_md_theme_dark_inverseOnSurface">#1F1B16</color>
<color name="orange_md_theme_dark_inverseSurface">#EBE1D9</color>
<color name="orange_md_theme_dark_inversePrimary">#855400</color>
<color name="orange_md_theme_dark_shadow">#000000</color>
<color name="orange_md_theme_dark_surfaceTint">#FFB95C</color>
<color name="orange_md_theme_dark_outlineVariant">#504539</color>
<color name="orange_md_theme_dark_scrim">#000000</color>
// Colors without comments seem to be not used // Colors without comments seem to be not used
<color name="monochrome_theme_light_primary">#616161</color> <color name="monochrome_theme_light_primary">#616161</color>
// LibreTube Logo, Hyperlinks and on-off switch (on status: filling) (also influence on the // LibreTube Logo, Hyperlinks and on-off switch (on status: filling) (also influence on the

View file

@ -71,7 +71,7 @@
<string name="url">URL</string> <string name="url">URL</string>
<string name="link_copied_to_clipboard">Link copied to clipboard</string> <string name="link_copied_to_clipboard">Link copied to clipboard</string>
<string name="api_key">Use API Key</string> <string name="api_key">Use API Key</string>
<string name="api_key_summary">Faster results from YouTube</string> <string name="api_key_summary">Video Recommendations from Youtube API</string>
<string name="update_app_summary">Announce new versions of the app when it is opened</string> <string name="update_app_summary">Announce new versions of the app when it is opened</string>
<string name="downloads">Downloads</string> <string name="downloads">Downloads</string>
<string name="remove_deleted">Remove Deleted</string> <string name="remove_deleted">Remove Deleted</string>
@ -232,6 +232,7 @@
<string name="green">Green</string> <string name="green">Green</string>
<string name="purple">Purple</string> <string name="purple">Purple</string>
<string name="yellow">Yellow</string> <string name="yellow">Yellow</string>
<string name="orange">Orange</string>
<string name="monochrome">Monochrome</string> <string name="monochrome">Monochrome</string>
<string name="downloaded">"Downloaded: "</string> <string name="downloaded">"Downloaded: "</string>
<string name="finished_download_notification_channel_name">Finished Downloads</string> <string name="finished_download_notification_channel_name">Finished Downloads</string>

View file

@ -205,6 +205,35 @@
</style> </style>
<style name="Theme.Orange" parent="BaseTheme">
<item name="colorPrimary">@color/orange_md_theme_light_primary</item>
<item name="colorOnPrimary">@color/orange_md_theme_light_onPrimary</item>
<item name="colorPrimaryContainer">@color/orange_md_theme_light_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/orange_md_theme_light_onPrimaryContainer</item>
<item name="colorSecondary">@color/orange_md_theme_light_secondary</item>
<item name="colorOnSecondary">@color/orange_md_theme_light_onSecondary</item>
<item name="colorSecondaryContainer">@color/orange_md_theme_light_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/orange_md_theme_light_onSecondaryContainer</item>
<item name="colorTertiary">@color/orange_md_theme_light_tertiary</item>
<item name="colorOnTertiary">@color/orange_md_theme_light_onTertiary</item>
<item name="colorTertiaryContainer">@color/orange_md_theme_light_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/orange_md_theme_light_onTertiaryContainer</item>
<item name="colorError">@color/orange_md_theme_light_error</item>
<item name="colorErrorContainer">@color/orange_md_theme_light_errorContainer</item>
<item name="colorOnError">@color/orange_md_theme_light_onError</item>
<item name="colorOnErrorContainer">@color/orange_md_theme_light_onErrorContainer</item>
<item name="android:colorBackground">@color/orange_md_theme_light_background</item>
<item name="colorOnBackground">@color/orange_md_theme_light_onBackground</item>
<item name="colorSurface">@color/orange_md_theme_light_surface</item>
<item name="colorOnSurface">@color/orange_md_theme_light_onSurface</item>
<item name="colorSurfaceVariant">@color/orange_md_theme_light_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/orange_md_theme_light_onSurfaceVariant</item>
<item name="colorOutline">@color/orange_md_theme_light_outline</item>
<item name="colorOnSurfaceInverse">@color/orange_md_theme_light_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/orange_md_theme_light_inverseSurface</item>
<item name="colorPrimaryInverse">@color/orange_md_theme_light_inversePrimary</item>
</style>
<style name="Pure"> <style name="Pure">
<item name="android:colorBackground">@android:color/white</item> <item name="android:colorBackground">@android:color/white</item>

View file

@ -35,14 +35,6 @@
app:summary="@string/download_over_metered_networks_summary" app:summary="@string/download_over_metered_networks_summary"
app:title="@string/download_over_metered_networks" /> app:title="@string/download_over_metered_networks" />
<MultiSelectListPreference
app:icon="@drawable/baseline_hide_image_24"
app:dialogTitle="@string/hide_thumbnails"
app:entries="@array/hide_thumbnail"
app:entryValues="@array/hide_thumbnail_values"
app:key="hide_thumbnails"
app:title="@string/hide_thumbnails" />
<EditTextPreference <EditTextPreference
android:icon="@drawable/baseline_network_locked_24" android:icon="@drawable/baseline_network_locked_24"

View file

@ -59,6 +59,12 @@
app:summary="@string/video_recommendations_summary" app:summary="@string/video_recommendations_summary"
app:title="@string/video_recommendations" /> app:title="@string/video_recommendations" />
<EditTextPreference
android:icon="@drawable/ic_key"
app:key="api_key"
android:summary="@string/api_key_summary"
app:title="@string/api_key" />
<ListPreference <ListPreference
android:defaultValue="" android:defaultValue=""
@ -106,7 +112,7 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false" app:defaultValue="true"
android:icon="@drawable/baseline_share_24" android:icon="@drawable/baseline_share_24"
android:key="hide_terminal" android:key="hide_terminal"
app:title="@string/hide_terminal" /> app:title="@string/hide_terminal" />

View file

@ -43,6 +43,16 @@
app:defaultValue="all" app:defaultValue="all"
app:title="@string/subtitle_languages" /> app:title="@string/subtitle_languages" />
<ListPreference
android:defaultValue="srt"
android:entries="@array/sub_formats"
android:entryValues="@array/sub_formats"
android:icon="@drawable/baseline_format_quote_24"
app:key="sub_format"
app:useSimpleSummaryProvider="true"
app:title="@string/subtitle_format" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true" app:defaultValue="true"
@ -60,6 +70,7 @@
app:summary="@string/crop_thumb_summary" app:summary="@string/crop_thumb_summary"
app:title="@string/crop_thumb" /> app:title="@string/crop_thumb" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="false" app:defaultValue="false"
@ -68,22 +79,6 @@
app:summary="@string/force_keyframes_summary" app:summary="@string/force_keyframes_summary"
app:title="@string/force_keyframes" /> app:title="@string/force_keyframes" />
<ListPreference
android:defaultValue="srt"
android:entries="@array/sub_formats"
android:entryValues="@array/sub_formats"
android:icon="@drawable/baseline_format_quote_24"
app:key="sub_format"
app:useSimpleSummaryProvider="true"
app:title="@string/subtitle_format" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_chapters"
app:key="add_chapters"
app:summary="@string/add_chapters_summary"
app:title="@string/add_chapters" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
@ -93,6 +88,15 @@
app:summary="@string/save_thumb_summary" app:summary="@string/save_thumb_summary"
app:title="@string/save_thumb" /> app:title="@string/save_thumb" />
<SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true"
app:icon="@drawable/ic_chapters"
app:key="add_chapters"
app:summary="@string/add_chapters_summary"
app:title="@string/add_chapters" />
<ListPreference <ListPreference
android:defaultValue="Default" android:defaultValue="Default"
android:entries="@array/audio_containers" android:entries="@array/audio_containers"