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:
parent
bb282424a8
commit
4227b1e43c
23 changed files with 448 additions and 226 deletions
|
|
@ -10,7 +10,7 @@ def properties = new Properties()
|
|||
def versionMajor = 1
|
||||
def versionMinor = 6
|
||||
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 = ""
|
||||
|
||||
if (versionBuild > 0){
|
||||
|
|
@ -206,4 +206,6 @@ dependencies {
|
|||
// For RTSP playback support with ExoPlayer
|
||||
implementation("androidx.media3:media3-exoplayer-rtsp:$media3_version")
|
||||
implementation("androidx.media3:media3-datasource-cronet:$media3_version")
|
||||
|
||||
implementation "com.anggrayudi:storage:1.5.5"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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.WRITE_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.REQUEST_INSTALL_PACKAGES" />
|
||||
<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" />
|
||||
|
||||
|
||||
|
|
@ -38,8 +31,7 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/BaseTheme"
|
||||
tools:ignore="DataExtractionRules"
|
||||
tools:targetApi="tiramisu"
|
||||
android:banner="@mipmap/ic_launcher">
|
||||
tools:targetApi="tiramisu">
|
||||
<profileable
|
||||
android:shell="true"
|
||||
tools:targetApi="29" />
|
||||
|
|
@ -49,18 +41,7 @@
|
|||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:exported="true"
|
||||
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
|
||||
android:name=".receiver.ShareActivity"
|
||||
|
|
@ -114,7 +95,7 @@
|
|||
<activity-alias
|
||||
android:name=".terminalShareAlias"
|
||||
android:targetActivity=".ui.more.TerminalActivity"
|
||||
android:enabled="true"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
|
@ -126,17 +107,20 @@
|
|||
<activity-alias
|
||||
android:name=".Default"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:configChanges="smallestScreenSize|layoutDirection|locale|orientation|screenSize"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<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>
|
||||
</activity-alias>
|
||||
|
||||
|
|
@ -152,9 +136,12 @@
|
|||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<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>
|
||||
</activity-alias>
|
||||
|
||||
|
|
@ -169,9 +156,12 @@
|
|||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<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>
|
||||
</activity-alias>
|
||||
|
||||
|
|
|
|||
|
|
@ -71,8 +71,12 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
|
|||
if (!hasCookies()) throw Exception("There is no cookies in the database!")
|
||||
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(
|
||||
"/data/data/com.deniscerri.ytdl/app_webview/Default/Cookies", null, OPEN_READONLY
|
||||
dbPath, null, OPEN_READONLY
|
||||
).run {
|
||||
val projection = arrayOf(
|
||||
CookieObject.HOST,
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
private var defaultVideoFormats : MutableList<Format>
|
||||
|
||||
private val videoQualityPreference: String
|
||||
private val formatIDPreference: String
|
||||
private val audioFormatIDPreference: String
|
||||
private val formatIDPreference: List<String>
|
||||
private val audioFormatIDPreference: List<String>
|
||||
private val resources : Resources
|
||||
enum class Type {
|
||||
audio, video, command
|
||||
|
|
@ -92,8 +92,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
erroredDownloads = repository.erroredDownloads.asLiveData()
|
||||
|
||||
videoQualityPreference = sharedPreferences.getString("video_quality", application.getString(R.string.best_quality)).toString()
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString()
|
||||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString()
|
||||
formatIDPreference = sharedPreferences.getString("format_id", "").toString().split(",")
|
||||
audioFormatIDPreference = sharedPreferences.getString("format_id_audio", "").toString().split(",")
|
||||
|
||||
val confTmp = Configuration(application.resources.configuration)
|
||||
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 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,
|
||||
resultItem.url,
|
||||
|
|
@ -317,7 +319,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
return cloneFormat (
|
||||
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){
|
||||
formats.last { it.format_note.contains("audio", ignoreCase = true) }
|
||||
}
|
||||
|
|
@ -332,7 +334,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
try {
|
||||
val theFormats = formats.ifEmpty { defaultVideoFormats }
|
||||
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){
|
||||
when (videoQualityPreference) {
|
||||
"worst" -> {
|
||||
|
|
@ -397,7 +399,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
|
||||
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
|
||||
val list : MutableList<DownloadItem> = mutableListOf()
|
||||
val preferredType = sharedPreferences.getString("preferred_download_type", "video");
|
||||
val preferredType = sharedPreferences.getString("preferred_download_type", "video")
|
||||
items.forEach {
|
||||
list.add(createDownloadItemFromResult(it!!, Type.valueOf(preferredType!!)))
|
||||
}
|
||||
|
|
@ -454,12 +456,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
val activeAndQueuedDownloads = repository.getActiveAndQueuedDownloads()
|
||||
val allowMeteredNetworks = sharedPreferences.getBoolean("metered_networks", true)
|
||||
val queuedItems = mutableListOf<DownloadItem>()
|
||||
var lastDownloadId = repository.getLastDownloadId()
|
||||
|
||||
var exists = false
|
||||
|
||||
items.forEach {
|
||||
lastDownloadId++
|
||||
it.status = DownloadRepository.Status.Queued.toString()
|
||||
if (activeAndQueuedDownloads.firstOrNull{d ->
|
||||
d.id = 0
|
||||
|
|
@ -469,7 +468,6 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
exists = true
|
||||
}else{
|
||||
if (it.id == 0L){
|
||||
it.id = lastDownloadId
|
||||
val insert = async {repository.insert(it)}
|
||||
val id = insert.await()
|
||||
it.id = id
|
||||
|
|
@ -492,7 +490,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
var delay = if (it.downloadStartTime != 0L){
|
||||
it.downloadStartTime - currentTime
|
||||
} else 0
|
||||
if (delay < 0L) delay = 0L
|
||||
if (delay < 0L) delay = 1L
|
||||
|
||||
val workConstraints = Constraints.Builder()
|
||||
if (!allowMeteredNetworks) workConstraints.setRequiredNetworkType(NetworkType.UNMETERED)
|
||||
|
|
@ -501,7 +499,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
|
|||
.setInputData(Data.Builder().putLong("id", it.id).build())
|
||||
.addTag("download")
|
||||
.setConstraints(workConstraints.build())
|
||||
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
|
||||
.setInitialDelay(delay, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).beginUniqueWork(
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ class ResultViewModel(application: Application) : AndroidViewModel(application)
|
|||
}
|
||||
private fun getQueryType(inputQuery: String) : String {
|
||||
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)
|
||||
if (m.find()) {
|
||||
type = "YT_Video"
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
res
|
||||
}
|
||||
if (chosenFormats.isEmpty()) throw Exception()
|
||||
|
||||
formats = listOf(res)
|
||||
|
||||
//playlist format filtering
|
||||
}else{
|
||||
var progress = "0/${items.size}"
|
||||
|
|
@ -154,6 +157,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
formatCollection.add(it)
|
||||
}
|
||||
}
|
||||
formats = formatCollection
|
||||
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 }
|
||||
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
|
||||
if (items.size == 1){
|
||||
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(selectedVideo, selectedAudios)))
|
||||
listener.onFormatClick(formats, listOf(FormatTuple(selectedVideo, selectedAudios)))
|
||||
}else{
|
||||
//playlist format selection
|
||||
val selectedFormats = mutableListOf<Format>()
|
||||
|
|
@ -208,7 +212,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
selectedFormats.add(selectedVideo)
|
||||
}
|
||||
}
|
||||
listener.onFormatClick(formatCollection, selectedFormats.map { FormatTuple(it, selectedAudios) })
|
||||
listener.onFormatClick(formats, selectedFormats.map { FormatTuple(it, selectedAudios) })
|
||||
}
|
||||
|
||||
dismiss()
|
||||
|
|
@ -260,7 +264,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
val clickedCard = (clickedformat as MaterialCardView)
|
||||
if (format.vcodec.isNotBlank() && format.vcodec != "none") {
|
||||
if (clickedCard.isChecked) {
|
||||
listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null)))
|
||||
listener.onFormatClick(formats, listOf(FormatTuple(format, null)))
|
||||
dismiss()
|
||||
}
|
||||
videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
|
||||
|
|
@ -279,7 +283,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
}
|
||||
}else{
|
||||
if (items.size == 1){
|
||||
listener.onFormatClick(List(items.size){finalFormats}, listOf(FormatTuple(format, null)))
|
||||
listener.onFormatClick(formats, listOf(FormatTuple(format, null)))
|
||||
}else{
|
||||
val selectedFormats = mutableListOf<Format>()
|
||||
formatCollection.forEach {
|
||||
|
|
@ -290,7 +294,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
|
|||
selectedFormats.add(format)
|
||||
}
|
||||
}
|
||||
listener.onFormatClick(formatCollection, selectedFormats.map { FormatTuple(it, null) })
|
||||
listener.onFormatClick(formats, selectedFormats.map { FormatTuple(it, null) })
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -447,10 +447,15 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), DownloadItem::class.java)
|
||||
item.id = 0L
|
||||
cancelled.add(item)
|
||||
|
||||
}
|
||||
|
||||
cancelled.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(item)
|
||||
downloadViewModel.insert(f)
|
||||
}
|
||||
}
|
||||
|
||||
if(cancelled.isNotEmpty()){
|
||||
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)
|
||||
item.id = 0L
|
||||
errored.add(item)
|
||||
|
||||
}
|
||||
|
||||
errored.asReversed().forEach { f ->
|
||||
withContext(Dispatchers.IO){
|
||||
downloadViewModel.insert(item)
|
||||
downloadViewModel.insert(f)
|
||||
}
|
||||
}
|
||||
|
||||
if(errored.isNotEmpty()){
|
||||
finalMessage.append("${getString(R.string.errored)}: ${errored.count()}\n")
|
||||
}
|
||||
|
|
@ -491,13 +501,20 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
|
|||
//command template restore
|
||||
if(json.has("templates")){
|
||||
val items = json.getAsJsonArray("templates")
|
||||
val templates = mutableListOf<CommandTemplate>()
|
||||
items.forEach {
|
||||
val item = Gson().fromJson(it.toString().replace("^\"|\"$", ""), CommandTemplate::class.java)
|
||||
item.id = 0L
|
||||
templates.add(item)
|
||||
|
||||
}
|
||||
|
||||
templates.asReversed().forEach { t ->
|
||||
withContext(Dispatchers.IO){
|
||||
commandTemplateViewModel.insert(item)
|
||||
commandTemplateViewModel.insert(t)
|
||||
}
|
||||
}
|
||||
|
||||
if(items.count() > 0){
|
||||
finalMessage.append("${getString(R.string.command_templates)}: ${items.count()}\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,21 @@ package com.deniscerri.ytdlnis.util
|
|||
|
||||
import android.content.Context
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
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.database.models.DownloadItem
|
||||
import okhttp3.internal.closeQuietly
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
|
@ -70,81 +76,105 @@ object FileUtil {
|
|||
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun moveFile(originDir: File, context: Context, destDir: String, keepCache: Boolean, progress: (p: Int) -> Unit) : List<String> {
|
||||
val fileList = mutableListOf<File>()
|
||||
val dir = File(formatPath(destDir))
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
var currentDirectory = dir
|
||||
originDir.walk().forEach {
|
||||
var destFile = File(dir.absolutePath + "/${it.absolutePath.removePrefix(originDir.absolutePath)}")
|
||||
if (it.isDirectory) {
|
||||
destFile.mkdirs()
|
||||
currentDirectory = destFile
|
||||
return@forEach
|
||||
}
|
||||
try {
|
||||
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)".toRegex())){
|
||||
it.delete()
|
||||
suspend fun moveFile(originDir: File, context: Context, destDir: String, keepCache: Boolean, progress: (p: Int) -> Unit) : List<String> {
|
||||
return withContext(Dispatchers.Main){
|
||||
val fileList = mutableListOf<String>()
|
||||
val dir = File(formatPath(destDir))
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
originDir.walk().forEach {
|
||||
if (it.isDirectory && it.absolutePath == originDir.absolutePath) return@forEach
|
||||
var destFile: DocumentFile
|
||||
try {
|
||||
if (it.name.matches("(^config.*.\\.txt\$)|(rList)|(part-Frag)".toRegex())){
|
||||
it.delete()
|
||||
return@forEach
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
val paths = files.map { it.absolutePath }.toTypedArray()
|
||||
MediaScannerConnection.scanFile(context, paths, null, null)
|
||||
return files.sortedByDescending { it.length() }.map { it.absolutePath }
|
||||
val paths = files.sortedByDescending { File(it).length() }
|
||||
MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null)
|
||||
return paths
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun searchFromPiped(query: String): ArrayList<ResultItem?> {
|
||||
val data = genericRequest("$pipedURL/search?q=$query&filter=videos")
|
||||
val data = genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCODE}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||
for (i in 0 until dataArray.length()) {
|
||||
|
|
@ -86,7 +86,7 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
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®ion=${countryCODE}")
|
||||
val dataArray = data.getJSONArray("items")
|
||||
if (dataArray.length() == 0) return getFromYTDL(query)
|
||||
for (i in 0 until dataArray.length()) {
|
||||
|
|
@ -350,9 +350,44 @@ class InfoUtil(private val context: Context) {
|
|||
@Throws(JSONException::class)
|
||||
fun getTrending(): ArrayList<ResultItem?> {
|
||||
items = ArrayList()
|
||||
if (sharedPreferences.getString("api_key", "")!!.isNotBlank()){
|
||||
return getTrendingFromYoutubeAPI()
|
||||
}
|
||||
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®ionCode=$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®ionCode=$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?> {
|
||||
val url = "$pipedURL/trending?region=$countryCODE"
|
||||
val res = genericArrayRequest(url)
|
||||
|
|
@ -881,7 +916,10 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
|
||||
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 request = YoutubeDLRequest(url)
|
||||
|
|
@ -925,7 +963,12 @@ class InfoUtil(private val context: Context) {
|
|||
|
||||
if (sponsorBlockFilters.isNotEmpty()) {
|
||||
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()){
|
||||
|
|
@ -936,7 +979,7 @@ class InfoUtil(private val context: Context) {
|
|||
}
|
||||
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
|
||||
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()){
|
||||
downloadItem.downloadSections.split(";").forEach {
|
||||
|
|
@ -946,7 +989,7 @@ class InfoUtil(private val context: Context) {
|
|||
else
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
@ -1019,7 +1062,9 @@ class InfoUtil(private val context: Context) {
|
|||
config.writeText(configData)
|
||||
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ object ThemeUtil {
|
|||
"green" -> activity.setTheme(R.style.Theme_Green)
|
||||
"purple" -> activity.setTheme(R.style.Theme_Purple)
|
||||
"yellow" -> activity.setTheme(R.style.Theme_Yellow)
|
||||
"orange" -> activity.setTheme(R.style.Theme_Orange)
|
||||
"monochrome" -> activity.setTheme(R.style.Theme_Monochrome)
|
||||
}
|
||||
|
||||
|
|
@ -71,37 +72,41 @@ object ThemeUtil {
|
|||
when (sharedPreferences.getString("ytdlnis_theme", "System")!!) {
|
||||
"System" -> {
|
||||
//set dynamic icon
|
||||
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.Default")
|
||||
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
activity.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(activity.packageName, "com.deniscerri.ytdlnis.Default"),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
"Light" -> {
|
||||
//set light icon
|
||||
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.LightIcon")
|
||||
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
activity.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(activity.packageName, "com.deniscerri.ytdlnis.LightIcon"),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
}
|
||||
"Dark" -> {
|
||||
//set dark icon
|
||||
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.DarkIcon")
|
||||
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
activity.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(activity.packageName, "com.deniscerri.ytdlnis.DarkIcon"),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
}
|
||||
else -> {
|
||||
//set dynamic icon
|
||||
val aliasComponentName = ComponentName(activity, "com.deniscerri.ytdlnis.Default")
|
||||
activity.packageManager.setComponentEnabledSetting(aliasComponentName,
|
||||
activity.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(activity.packageName, "com.deniscerri.ytdlnis.Default"),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
|
||||
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 accent = sharedPreferences.getString("theme_accent", "blue")
|
||||
return if (accent == "Default" || accent == "blue"){
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
|
@ -133,44 +134,47 @@ class DownloadWorker(
|
|||
}.onSuccess {
|
||||
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
|
||||
|
||||
var finalPaths : List<String>?
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
|
||||
try {
|
||||
finalPaths = 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, "log" to logDownloads))
|
||||
}
|
||||
if (finalPaths.isNotEmpty()){
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads))
|
||||
}else{
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var finalPaths : List<String>?
|
||||
//move file from internal to set download directory
|
||||
setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
|
||||
try {
|
||||
finalPaths = withContext(Dispatchers.IO){
|
||||
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, "log" to logDownloads))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
if (!incognito) {
|
||||
val unixtime = System.currentTimeMillis() / 1000
|
||||
val file = File(finalPaths?.first()!!)
|
||||
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)
|
||||
runBlocking {
|
||||
//put download in history
|
||||
val incognito = sharedPreferences.getBoolean("incognito", false)
|
||||
if (!incognito) {
|
||||
val unixtime = System.currentTimeMillis() / 1000
|
||||
val file = File(finalPaths?.first()!!)
|
||||
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)
|
||||
historyDao.insert(historyItem)
|
||||
}
|
||||
}
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
notificationUtil.cancelDownloadNotification(downloadItem.id.toInt())
|
||||
notificationUtil.createDownloadFinished(
|
||||
downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
|
||||
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
|
||||
)
|
||||
}
|
||||
|
||||
if (wasQuickDownloaded){
|
||||
runCatching {
|
||||
|
|
|
|||
|
|
@ -116,16 +116,18 @@ class TerminalDownloadWorker(
|
|||
}
|
||||
}
|
||||
}.onSuccess {
|
||||
//move file from internal to set download directory
|
||||
try {
|
||||
FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation!!, false){ p ->
|
||||
setProgressAsync(workDataOf("progress" to p))
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
//move file from internal to set download directory
|
||||
try {
|
||||
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){
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="200dp"
|
||||
android:layout_marginBottom="200dp"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
|
|
|
|||
|
|
@ -28,23 +28,18 @@
|
|||
android:label="MainSettingsFragment" >
|
||||
<action
|
||||
android:id="@+id/action_mainSettingsFragment_to_appearanceSettingsFragment"
|
||||
app:destination="@id/appearanceSettingsFragment"
|
||||
app:enterAnim="@anim/fade_in" />
|
||||
app:destination="@id/appearanceSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_mainSettingsFragment_to_folderSettingsFragment"
|
||||
app:destination="@id/folderSettingsFragment"
|
||||
app:enterAnim="@anim/fade_in" />
|
||||
app:destination="@id/folderSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_mainSettingsFragment_to_downloadSettingsFragment"
|
||||
app:destination="@id/downloadSettingsFragment"
|
||||
app:enterAnim="@anim/fade_in" />
|
||||
app:destination="@id/downloadSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_mainSettingsFragment_to_processingSettingsFragment"
|
||||
app:destination="@id/processingSettingsFragment"
|
||||
app:enterAnim="@anim/fade_in" />
|
||||
app:destination="@id/processingSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_mainSettingsFragment_to_updateSettingsFragment"
|
||||
app:destination="@id/updateSettingsFragment"
|
||||
app:enterAnim="@anim/fade_in" />
|
||||
app:destination="@id/updateSettingsFragment" />
|
||||
</fragment>
|
||||
</navigation>
|
||||
|
|
@ -203,6 +203,35 @@
|
|||
|
||||
</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">
|
||||
|
||||
<item name="android:colorBackground">@android:color/black</item>
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@
|
|||
<string name="red">E Kuqe</string>
|
||||
<string name="purple">Vjollcë</string>
|
||||
<string name="yellow">E Verdhë</string>
|
||||
<string name="orange">Portokalli</string>
|
||||
<string name="monochrome">Monokrom</string>
|
||||
<string name="green">Jeshile</string>
|
||||
<string name="downloaded">"U shkarkua: "</string>
|
||||
|
|
@ -262,7 +263,7 @@
|
|||
<string name="misc">Të Tjera</string>
|
||||
<string name="audio_only_item">Ky artikull përmban vetëm formate audio</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_summary">Rrëshqitni artikujt për veprime të caktuara</string>
|
||||
<string name="crop_thumb">Preje Thumbnailin</string>
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@
|
|||
<item>@string/green</item>
|
||||
<item>@string/purple</item>
|
||||
<item>@string/yellow</item>
|
||||
<item>@string/orange</item>
|
||||
<item>@string/monochrome</item>
|
||||
</array>
|
||||
|
||||
|
|
@ -236,6 +237,7 @@
|
|||
<item>green</item>
|
||||
<item>purple</item>
|
||||
<item>yellow</item>
|
||||
<item>orange</item>
|
||||
<item>monochrome</item>
|
||||
</array>
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
<color name="red_md_theme_dark_inverseSurface">#EDE0DE</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_primaryContainer">#FFE08D</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_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
|
||||
<color name="monochrome_theme_light_primary">#616161</color>
|
||||
// LibreTube Logo, Hyperlinks and on-off switch (on status: filling) (also influence on the
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<string name="url">URL</string>
|
||||
<string name="link_copied_to_clipboard">Link copied to clipboard</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="downloads">Downloads</string>
|
||||
<string name="remove_deleted">Remove Deleted</string>
|
||||
|
|
@ -232,6 +232,7 @@
|
|||
<string name="green">Green</string>
|
||||
<string name="purple">Purple</string>
|
||||
<string name="yellow">Yellow</string>
|
||||
<string name="orange">Orange</string>
|
||||
<string name="monochrome">Monochrome</string>
|
||||
<string name="downloaded">"Downloaded: "</string>
|
||||
<string name="finished_download_notification_channel_name">Finished Downloads</string>
|
||||
|
|
|
|||
|
|
@ -205,6 +205,35 @@
|
|||
|
||||
</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">
|
||||
|
||||
<item name="android:colorBackground">@android:color/white</item>
|
||||
|
|
|
|||
|
|
@ -35,14 +35,6 @@
|
|||
app:summary="@string/download_over_metered_networks_summary"
|
||||
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
|
||||
android:icon="@drawable/baseline_network_locked_24"
|
||||
|
|
|
|||
|
|
@ -59,6 +59,12 @@
|
|||
app:summary="@string/video_recommendations_summary"
|
||||
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
|
||||
android:defaultValue=""
|
||||
|
|
@ -106,7 +112,7 @@
|
|||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
app:defaultValue="true"
|
||||
android:icon="@drawable/baseline_share_24"
|
||||
android:key="hide_terminal"
|
||||
app:title="@string/hide_terminal" />
|
||||
|
|
|
|||
|
|
@ -43,6 +43,16 @@
|
|||
app:defaultValue="all"
|
||||
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
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="true"
|
||||
|
|
@ -60,6 +70,7 @@
|
|||
app:summary="@string/crop_thumb_summary"
|
||||
app:title="@string/crop_thumb" />
|
||||
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
app:defaultValue="false"
|
||||
|
|
@ -68,22 +79,6 @@
|
|||
app:summary="@string/force_keyframes_summary"
|
||||
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
|
||||
android:widgetLayout="@layout/preferece_material_switch"
|
||||
|
|
@ -93,6 +88,15 @@
|
|||
app:summary="@string/save_thumb_summary"
|
||||
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
|
||||
android:defaultValue="Default"
|
||||
android:entries="@array/audio_containers"
|
||||
|
|
|
|||
Loading…
Reference in a new issue