Fix format details fps in arabic being rtl
Removed h264 from codec
Fixed Search engine not getting updated in home when changing in settings fragment.
Added language preference in android 13 and up
Added multiple selection for command templates
Fix issue when app crashes when creating config files for items with weird titles
Fix auto preferred download type not working correctly in yt music
pull/283/head
deniscerri 3 years ago
parent 25f53aeb87
commit 2d08d2f643
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

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

@ -17,13 +17,14 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso
class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<DownloadItem, GenericDownloadAdapter.ViewHolder>(DIFF_CALLBACK) {
class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : PagingDataAdapter<DownloadItemSimple, GenericDownloadAdapter.ViewHolder>(DIFF_CALLBACK) {
private val onItemClickListener: OnItemClickListener
private val activity: Activity
val checkedItems: ArrayList<Long>
@ -73,7 +74,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
}
val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item!!.duration
duration.text = item.duration
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title)
@ -204,13 +205,13 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItemSimple> = object : DiffUtil.ItemCallback<DownloadItemSimple>() {
override fun areItemsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
override fun areContentsTheSame(oldItem: DownloadItemSimple, newItem: DownloadItemSimple): Boolean {
return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb
}
}

@ -3,6 +3,7 @@ package com.deniscerri.ytdlnis.database.dao
import androidx.paging.PagingSource
import androidx.room.*
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.models.Format
import kotlinx.coroutines.flow.Flow
@ -34,7 +35,7 @@ interface DownloadDao {
fun getActiveAndQueuedDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Queued' or status='QueuedPaused' ORDER BY downloadStartTime, id")
fun getQueuedDownloads() : PagingSource<Int, DownloadItem>
fun getQueuedDownloads() : PagingSource<Int, DownloadItemSimple>
@Query("SELECT format FROM downloads WHERE status='Queued' or status='QueuedPaused'")
fun getSelectedFormatFromQueued() : List<Format>
@ -49,7 +50,7 @@ interface DownloadDao {
fun getQueuedDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
fun getCancelledDownloads() : PagingSource<Int, DownloadItem>
fun getCancelledDownloads() : PagingSource<Int, DownloadItemSimple>
@Query("SELECT * FROM downloads WHERE status='Cancelled' ORDER BY id DESC")
fun getCancelledDownloadsList() : List<DownloadItem>
@ -58,13 +59,13 @@ interface DownloadDao {
fun getPausedDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
fun getErroredDownloads() : PagingSource<Int, DownloadItem>
fun getErroredDownloads() : PagingSource<Int, DownloadItemSimple>
@Query("SELECT * FROM downloads WHERE status='Error' ORDER BY id DESC")
fun getErroredDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
fun getSavedDownloads() : PagingSource<Int, DownloadItem>
fun getSavedDownloads() : PagingSource<Int, DownloadItemSimple>
@Query("SELECT * FROM downloads WHERE status='Saved' ORDER BY id DESC")
fun getSavedDownloadsList() : List<DownloadItem>
@ -115,6 +116,9 @@ interface DownloadDao {
@Query("DELETE FROM downloads WHERE status='Saved'")
suspend fun deleteSaved()
@Query("DELETE FROM downloads WHERE id in (:list)")
suspend fun deleteAllWithIDs(list: List<Long>)
@Query("UPDATE downloads SET status='Cancelled' WHERE status='Queued' or status='QueuedPaused'")
suspend fun cancelQueued()

@ -0,0 +1,21 @@
package com.deniscerri.ytdlnis.database.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
@Entity(tableName = "downloads")
data class DownloadItemSimple(
@PrimaryKey(autoGenerate = true)
var id: Long,
var url: String,
var title: String,
var author: String,
var thumb: String,
var duration: String,
var format: Format,
@ColumnInfo(defaultValue = "Queued")
var status: String,
var logID: Long?
)

@ -5,6 +5,7 @@ import androidx.paging.PagingConfig
import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.database.dao.DownloadDao
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.util.FileUtil
import kotlinx.coroutines.flow.Flow
import java.io.File
@ -16,19 +17,19 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
pagingSourceFactory = {downloadDao.getAllDownloads()}
)
val activeDownloads : Flow<List<DownloadItem>> = downloadDao.getActiveDownloads()
val queuedDownloads : Pager<Int, DownloadItem> = Pager(
val queuedDownloads : Pager<Int, DownloadItemSimple> = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getQueuedDownloads()}
)
val cancelledDownloads : Pager<Int, DownloadItem> = Pager(
val cancelledDownloads : Pager<Int, DownloadItemSimple> = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getCancelledDownloads()}
)
val erroredDownloads : Pager<Int, DownloadItem> = Pager(
val erroredDownloads : Pager<Int, DownloadItemSimple> = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getErroredDownloads()}
)
val savedDownloads : Pager<Int, DownloadItem> = Pager(
val savedDownloads : Pager<Int, DownloadItemSimple> = Pager(
config = PagingConfig(pageSize = 20, initialLoadSize = 20, prefetchDistance = 1),
pagingSourceFactory = {downloadDao.getSavedDownloads()}
)
@ -114,6 +115,11 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.deleteSaved()
}
suspend fun deleteAllWithIDs(ids: List<Long>){
downloadDao.deleteAllWithIDs(ids)
}
suspend fun cancelQueued(){
downloadDao.cancelQueued()
}

@ -8,6 +8,7 @@ import android.content.res.Resources
import android.net.ConnectivityManager
import android.os.Looper
import android.util.DisplayMetrics
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
@ -31,6 +32,7 @@ import com.deniscerri.ytdlnis.database.dao.ResultDao
import com.deniscerri.ytdlnis.database.models.AudioPreferences
import com.deniscerri.ytdlnis.database.models.CommandTemplate
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.DownloadItemSimple
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.HistoryItem
import com.deniscerri.ytdlnis.database.models.ResultItem
@ -62,12 +64,12 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
private val commandTemplateDao: CommandTemplateDao
private val infoUtil : InfoUtil
val allDownloads : Flow<PagingData<DownloadItem>>
val queuedDownloads : Flow<PagingData<DownloadItem>>
val queuedDownloads : Flow<PagingData<DownloadItemSimple>>
val activeDownloads : LiveData<List<DownloadItem>>
val activeDownloadsCount : LiveData<Int>
val cancelledDownloads : Flow<PagingData<DownloadItem>>
val erroredDownloads : Flow<PagingData<DownloadItem>>
val savedDownloads : Flow<PagingData<DownloadItem>>
val cancelledDownloads : Flow<PagingData<DownloadItemSimple>>
val erroredDownloads : Flow<PagingData<DownloadItemSimple>>
val savedDownloads : Flow<PagingData<DownloadItemSimple>>
private var bestVideoFormat : Format
private var bestAudioFormat : Format
@ -125,35 +127,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
resources = Resources(application.assets, metrics, confTmp)
val videoFormatValues = resources.getStringArray(R.array.video_formats_values)
videoContainer = sharedPreferences.getString("video_format", "Default")
defaultVideoFormats = mutableListOf()
videoFormatValues.forEach {
val tmp = Format(
it,
videoContainer!!,
"",
"",
"",
0,
it
)
defaultVideoFormats.add(tmp)
}
formatIDPreference.reversed().forEach {
val tmp = Format(
it,
videoContainer!!,
"",
"",
"",
0,
it
)
defaultVideoFormats.add(tmp)
}
defaultVideoFormats = getGenericVideoFormats()
bestVideoFormat = defaultVideoFormats.last()
audioContainer = sharedPreferences.getString("audio_format", "mp3")
@ -200,11 +175,17 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
return repository.getItemByID(id)
}
private fun getSuitableDownloadType(url: String): Type{
if (urlsForAudioType.any { url.contains(it) }){
return Type.audio
fun getDownloadType(t: Type, url: String) : Type {
return when(t){
Type.auto -> {
if (urlsForAudioType.any { url.contains(it) }){
Type.audio
}else{
Type.video
}
}
else -> t
}
return Type.video
}
fun createDownloadItemFromResult(resultItem: ResultItem, givenType: Type) : DownloadItem {
@ -214,10 +195,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val saveThumb = sharedPreferences.getBoolean("write_thumbnail", false)
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
var type = givenType
if (type == Type.auto){
type = getSuitableDownloadType(resultItem.url)
}
var type = getDownloadType(givenType, resultItem.url)
if(type == Type.command && commandTemplateDao.getTotalNumber() == 0) type = Type.video
val customFileNameTemplate = when(type) {
@ -466,8 +444,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val audioFormats = resources.getStringArray(R.array.audio_formats)
val formats = mutableListOf<Format>()
val containerPreference = sharedPreferences.getString("audio_format", "")
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
audioFormats.forEach { formats.add(Format(it, containerPreference!!,"","", "",0, it)) }
audioFormatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"","", "",1, it)) }
return formats
}
@ -475,8 +453,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val videoFormats = resources.getStringArray(R.array.video_formats_values)
val formats = mutableListOf<Format>()
val containerPreference = sharedPreferences.getString("video_format", "")
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
videoFormats.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",0, it)) }
formatIDPreference.forEach { formats.add(Format(it, containerPreference!!,"Default","", "",1, it)) }
return formats
}
@ -489,8 +467,8 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val list : MutableList<DownloadItem> = mutableListOf()
var preferredType = sharedPreferences.getString("preferred_download_type", "video")
items.forEach {
if (preferredType == Type.auto.toString()) preferredType = getSuitableDownloadType(it!!.url).toString()
list.add(createDownloadItemFromResult(it!!, Type.valueOf(preferredType!!)))
preferredType = getDownloadType(Type.valueOf(preferredType!!), it!!.url).toString()
list.add(createDownloadItemFromResult(it, Type.valueOf(preferredType!!)))
}
return list
}
@ -525,6 +503,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
repository.deleteSaved()
}
fun deleteAllWithID(ids: List<Long>) = viewModelScope.launch(Dispatchers.IO){
Log.e("Aa", ids.size.toString())
repository.deleteAllWithIDs(ids)
}
fun cancelQueued() = viewModelScope.launch(Dispatchers.IO) {
repository.cancelQueued()
}

@ -164,7 +164,7 @@ class ShareActivity : BaseActivity() {
private fun showDownloadSheet(it: ResultItem){
if (sharedPreferences.getBoolean("download_card", true)){
val bottomSheet = DownloadBottomSheetDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!), null, quickDownload)
val bottomSheet = DownloadBottomSheetDialog(it,downloadViewModel.getDownloadType(DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!), it.url), null, quickDownload)
bottomSheet.show(supportFragmentManager, "downloadSingleSheet")
}else{
lifecycleScope.launch(Dispatchers.IO){

@ -23,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.forEachIndexed
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
@ -283,7 +284,6 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
val providersChipGroup = searchView!!.findViewById<ChipGroup>(R.id.providers)
val providers = resources.getStringArray(R.array.search_engines)
val providersValues = resources.getStringArray(R.array.search_engines_values).toMutableList()
val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch")
for(i in providersValues.indices){
val provider = providers[i]
@ -291,8 +291,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
val tmp = layoutinflater!!.inflate(R.layout.filter_chip, providersChipGroup, false) as Chip
tmp.text = provider
tmp.id = i
if (currentProvider == providerValue) tmp.isChecked = true
tmp.tag = providersValues[i]
tmp.setOnClickListener {
val editor = sharedPreferences?.edit()
@ -308,6 +307,15 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
infoUtil = InfoUtil(requireContext())
searchView!!.addTransitionListener { _, _, newState ->
if (newState == SearchView.TransitionState.SHOWN) {
val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch")
providersChipGroup.children.forEach {
val tmp = providersChipGroup.findViewById<Chip>(it.id)
if (tmp.tag == currentProvider) {
tmp.isChecked = true
return@forEach
}
}
try{
val clipboard =
requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
@ -596,7 +604,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
}
private fun showSingleDownloadSheet(resultItem : ResultItem, type: DownloadViewModel.Type, quickDownload: Boolean){
val bottomSheet = DownloadBottomSheetDialog(resultItem, type, null, quickDownload)
val bottomSheet = DownloadBottomSheetDialog(resultItem, downloadViewModel.getDownloadType(type, resultItem.url), null, quickDownload)
bottomSheet.show(parentFragmentManager, "downloadSingleSheet")
}

@ -127,8 +127,10 @@ class AddExtraCommandsDialog(private val item: DownloadItem?, private val callba
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ template ->
text.text.insert(text.selectionStart, "${template.content} ")
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){ templates ->
templates.forEach {
text.text.insert(text.selectionStart, "${it.content} ")
}
text.postDelayed({
text.requestFocus()
imm.showSoftInput(text, 0)

@ -132,16 +132,19 @@ class DownloadCommandFragment(private val resultItem: ResultItem, private var cu
commandTemplateCard.setOnClickListener {
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel){
chosenCommandView.editText!!.setText(it.content)
populateCommandCard(commandTemplateCard, it)
chosenCommandView.editText!!.text.clear()
it.forEach { c ->
chosenCommandView.editText!!.text.insert(chosenCommandView.editText!!.selectionStart, "${c.content} ")
}
populateCommandCard(commandTemplateCard, it.first())
downloadItem.format = Format(
it.title,
it.first().title,
"",
"",
"",
"",
0,
it.content
it.map { c -> c.content }.joinToString { " " }
)
}
}

@ -12,6 +12,7 @@ import android.graphics.Color
import android.os.Bundle
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MenuItem
@ -328,7 +329,16 @@ class DownloadMultipleBottomSheetDialog(private var items: MutableList<DownloadI
if (items.first().type == DownloadViewModel.Type.command){
lifecycleScope.launch {
UiUtil.showCommandTemplates(requireActivity(), commandTemplateViewModel) {
val format = downloadViewModel.generateCommandFormat(it)
val format = Format(
it.first().title,
"",
"",
"",
"",
0,
it.joinToString(" ") { c -> c.content }
)
items.forEach { f ->
f.format = format
}

@ -202,9 +202,7 @@ class CancelledDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClic
}
adapter.clearCheckedItems()
for (id in selectedObjects){
downloadViewModel.deleteDownload(id)
}
downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish()
}
}

@ -208,9 +208,7 @@ class ErroredDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickL
adapter.checkedItems.toList()
}
adapter.clearCheckedItems()
for (id in selectedObjects){
downloadViewModel.deleteDownload(id)
}
downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish()
}
}

@ -260,8 +260,8 @@ class QueuedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLi
YoutubeDL.getInstance().destroyProcessById(id.toInt().toString())
WorkManager.getInstance(requireContext()).cancelAllWorkByTag(id.toInt().toString())
notificationUtil.cancelDownloadNotification(id.toInt())
downloadViewModel.deleteDownload(id)
}
downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish()
}

@ -222,9 +222,7 @@ class SavedDownloadsFragment : Fragment(), GenericDownloadAdapter.OnItemClickLis
adapter.checkedItems.toList()
}
adapter.clearCheckedItems()
withContext(Dispatchers.IO) {
downloadViewModel.reQueueDownloadItems(selectedObjects)
}
downloadViewModel.deleteAllWithID(selectedObjects)
actionMode?.finish()
}

@ -1,5 +1,6 @@
package com.deniscerri.ytdlnis.ui.more
import android.app.ActionBar.LayoutParams
import android.app.Activity
import android.content.ClipboardManager
import android.content.Context
@ -8,8 +9,8 @@ import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.FileObserver
import android.os.PersistableBundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.MenuItem
import android.view.View
@ -22,7 +23,6 @@ import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.forEach
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
@ -62,8 +62,8 @@ class TerminalActivity : BaseActivity() {
private lateinit var sharedPreferences: SharedPreferences
private var downloadID by Delegates.notNull<Int>()
private lateinit var downloadFile : File
private lateinit var observer: FileObserver
private lateinit var imm : InputMethodManager
private lateinit var metrics: DisplayMetrics
var context: Context? = null
public override fun onCreate(savedInstanceState: Bundle?) {
@ -82,6 +82,8 @@ class TerminalActivity : BaseActivity() {
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
bottomAppBar = findViewById(R.id.bottomAppBar)
@ -113,8 +115,10 @@ class TerminalActivity : BaseActivity() {
Toast.makeText(context, getString(R.string.add_template_first), Toast.LENGTH_SHORT).show()
}else{
lifecycleScope.launch {
UiUtil.showCommandTemplates(this@TerminalActivity, commandTemplateViewModel){ template ->
input!!.text.insert(input!!.selectionStart, template.content + " ")
UiUtil.showCommandTemplates(this@TerminalActivity, commandTemplateViewModel){ templates ->
templates.forEach {c ->
input!!.text.insert(input!!.selectionStart, c.content + " ")
}
input!!.postDelayed({
input!!.requestFocus()
imm.showSoftInput(input!!, 0)
@ -146,7 +150,7 @@ class TerminalActivity : BaseActivity() {
output = findViewById(R.id.custom_command_output)
output!!.setTextIsSelectable(true)
output!!.setHorizontallyScrolling(true)
output!!.layoutParams!!.width = LayoutParams.WRAP_CONTENT
input = findViewById(R.id.command_edittext)
input!!.requestFocus()
fab = findViewById(R.id.command_fab)
@ -168,38 +172,6 @@ class TerminalActivity : BaseActivity() {
notificationUtil = NotificationUtil(this)
handleIntent(intent)
if(Build.VERSION.SDK_INT < 29){
observer = object : FileObserver(downloadFile.absolutePath, MODIFY) {
override fun onEvent(event: Int, p: String?) {
runOnUiThread{
try {
val newText = downloadFile.readText()
if (newText.isBlank()) return@runOnUiThread
output!!.append(newText)
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
}catch (ignored: Exception) {}
}
}
}
observer.startWatching()
}else{
observer = object : FileObserver(downloadFile, MODIFY) {
override fun onEvent(event: Int, p: String?) {
runOnUiThread{
try {
val newText = downloadFile.readText()
if (newText.isBlank()) return@runOnUiThread
output!!.append(newText)
output!!.scrollTo(0, output!!.height)
scrollView!!.fullScroll(View.FOCUS_DOWN)
}catch (ignored: Exception) {}
}
}
}
observer.startWatching()
}
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData(downloadID.toString())
.observe(this){ list ->
@ -269,7 +241,6 @@ class TerminalActivity : BaseActivity() {
scrollView.id = R.id.horizontalscroll_output
parent.addView(scrollView, 0)
}
output?.setHorizontallyScrolling(output?.canScrollHorizontally(1) != true)
}
R.id.export_clipboard -> {
lifecycleScope.launch(Dispatchers.IO){
@ -317,12 +288,10 @@ class TerminalActivity : BaseActivity() {
private fun hideCancelFab() {
fab!!.text = getString(R.string.run_command)
fab!!.setIconResource(R.drawable.ic_baseline_keyboard_arrow_right_24)
topAppBar!!.menu.forEach { it.isEnabled = true }
}
private fun showCancelFab() {
fab!!.text = getString(R.string.cancel_download)
fab!!.setIconResource(R.drawable.ic_cancel)
topAppBar!!.menu.forEach { it.isEnabled = false }
}
private var commandPathResultLauncher = registerForActivityResult(

@ -29,6 +29,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
override val title: Int = R.string.general
private var language: ListPreference? = null
private var language13: Preference? = null
private var theme: ListPreference? = null
private var accent: ListPreference? = null
private var highContrast: SwitchPreferenceCompat? = null
@ -50,6 +51,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
}
language = findPreference("app_language")
language13 = findPreference("app_language_13")
theme = findPreference("ytdlnis_theme")
accent = findPreference("theme_accent")
highContrast = findPreference("high_contrast")
@ -63,18 +65,32 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
entries.add(Locale(it).getDisplayName(Locale(it)))
}
language!!.entries = entries.toTypedArray()
if(language!!.value == null) language!!.value = Locale.getDefault().language
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
language!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
true
}
language13?.isVisible = false
}else{
language!!.isVisible = false
}
language?.isVisible = false
language13?.isVisible = true
language13?.summary = Locale.getDefault().displayLanguage
if(language!!.value == null) language!!.value = Locale.getDefault().language
language!!.summary = Locale(language!!.value).getDisplayLanguage(Locale(language!!.value))
language!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
language!!.summary = Locale(newValue.toString()).getDisplayLanguage(Locale(newValue.toString()))
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(newValue.toString()))
language13!!.setOnPreferenceClickListener {
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
intent.data = Uri.fromParts("package", requireActivity().packageName, null)
requireActivity().startActivity(intent)
requireActivity().finishAffinity()
true
}
}
theme!!.summary = theme!!.entry
theme!!.onPreferenceChangeListener =

@ -245,7 +245,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
historyItems.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -259,7 +259,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -273,7 +273,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -287,7 +287,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -301,7 +301,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -315,7 +315,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -330,7 +330,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
val arr = JsonArray()
items.forEach {
it.useAsExtraCommand = false
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -344,7 +344,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
items.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -358,7 +358,7 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
}
val arr = JsonArray()
historyItems.forEach {
arr.add(JsonParser().parse(Gson().toJson(it)).asJsonObject)
arr.add(JsonParser.parseString(Gson().toJson(it)).asJsonObject)
}
return arr
}
@ -380,7 +380,6 @@ class MainSettingsFragment : PreferenceFragmentCompat() {
lifecycleScope.launch {
val preferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = preferences.edit()
runCatching {
val ip = requireContext().contentResolver.openInputStream(result.data!!.data!!)

@ -290,7 +290,7 @@ object FileUtil {
}
fun convertFileSize(s: Long): String{
if (s <= 0) return "?"
if (s <= 1) return "?"
val units = arrayOf("B", "kB", "MB", "GB", "TB")
val digitGroups = (log10(s.toDouble()) / log10(1024.0)).toInt()
val symbols = DecimalFormatSymbols(Locale.US)

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.os.Looper
import android.text.Html
import android.util.Log
import android.util.Patterns
import android.widget.Toast
import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
@ -547,7 +548,6 @@ class InfoUtil(private val context: Context) {
fun getFromYTDL(query: String): ArrayList<ResultItem?> {
items = ArrayList()
val webpage_urls = mutableListOf<String>()
val searchEngine = sharedPreferences.getString("search_engine", "ytsearch")
try {
val request : YoutubeDLRequest
@ -656,7 +656,11 @@ class InfoUtil(private val context: Context) {
val url = if (jsonObject.has("url")){
jsonObject.getString("url")
}else{
jsonObject.getString("webpage_url")
if (Patterns.WEB_URL.matcher(query).matches()){
query
}else{
jsonObject.getString("webpage_url")
}
}
items.add(ResultItem(0,
@ -672,10 +676,6 @@ class InfoUtil(private val context: Context) {
chapters
)
)
webpage_urls.add(jsonObject.getString("webpage_url"))
}
if (items.size == 1){
items[0]?.url = webpage_urls.first()
}
} catch (e: Exception) {
Looper.prepare().run {
@ -970,20 +970,6 @@ class InfoUtil(private val context: Context) {
downDir.mkdirs()
}
if (downloadItem.playlistTitle.isNotBlank()){
request.addOption("--parse-metadata","${downloadItem.playlistTitle.split("[")[0]}:%(playlist)s")
runCatching {
request.addOption("--parse-metadata",
downloadItem.playlistTitle
.substring(
downloadItem.playlistTitle.indexOf("[") + 1,
downloadItem.playlistTitle.indexOf("]"),
) + ":%(playlist_index)s"
)
}
}
val aria2 = sharedPreferences.getBoolean("aria2", false)
if (aria2) {
request.addOption("--downloader", "libaria2c.so")
@ -1001,6 +987,7 @@ class InfoUtil(private val context: Context) {
val limitRate = sharedPreferences.getString("limit_rate", "")
if (limitRate != "") request.addOption("-r", limitRate!!)
request.addOption("--trim-filenames", 120)
if(downloadItem.type != DownloadViewModel.Type.command){
if (downloadItem.SaveThumb) {
request.addOption("--write-thumbnail")
@ -1034,12 +1021,12 @@ class InfoUtil(private val context: Context) {
}
if(downloadItem.title.isNotBlank()){
request.addOption("--parse-metadata", downloadItem.title.take(200) + ":%(title)s")
request.addCommands(listOf("--replace-in-metadata", "title", ".+", downloadItem.title))
}
if (downloadItem.author.isNotBlank()){
request.addOption("--parse-metadata", downloadItem.author.take(25) + ":%(artist)s")
request.addCommands(listOf("--replace-in-metadata", "uploader", ".+", downloadItem.author))
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$",""))
}
request.addCommands(listOf("--replace-in-metadata","artist"," - Topic$",""))
if (downloadItem.downloadSections.isNotBlank()){
downloadItem.downloadSections.split(";").forEach {
@ -1077,6 +1064,21 @@ class InfoUtil(private val context: Context) {
if (sharedPreferences.getBoolean("write_description", false)){
request.addOption("--write-description")
}
if (downloadItem.playlistTitle.isNotBlank()){
request.addOption("--parse-metadata","${downloadItem.playlistTitle.split("[")[0]}:%(playlist)s")
runCatching {
request.addOption("--parse-metadata",
downloadItem.playlistTitle
.substring(
downloadItem.playlistTitle.indexOf("[") + 1,
downloadItem.playlistTitle.indexOf("]"),
) + ":%(playlist_index)s"
)
}
}
}
if (sharedPreferences.getBoolean("restrict_filenames", true)) {
@ -1144,7 +1146,7 @@ class InfoUtil(private val context: Context) {
if (! request.hasOption("--convert-thumbnails")) request.addOption("--convert-thumbnails", "jpg")
if (sharedPreferences.getBoolean("crop_thumbnail", true)){
try {
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt")
val config = File(context.cacheDir.absolutePath + "/config" + downloadItem.id + "##ffmpegCrop.txt")
val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
config.writeText(configData)
request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1")
@ -1156,7 +1158,7 @@ class InfoUtil(private val context: Context) {
}
if (downloadItem.customFileNameTemplate.isNotBlank()){
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s")
request.addOption("-o", "${downloadItem.customFileNameTemplate.removeSuffix(".%(ext)s")}.%(ext)s")
}
}
@ -1318,7 +1320,7 @@ class InfoUtil(private val context: Context) {
request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
}else{
if (downloadItem.customFileNameTemplate.isNotBlank()){
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s")
request.addOption("-o", "${downloadItem.customFileNameTemplate.removeSuffix(".%(ext)s")}.%(ext)s")
}
}
@ -1351,10 +1353,16 @@ class InfoUtil(private val context: Context) {
var final = java.lang.String.join(" ", arr).replace("\"\"", "\" \"")
val ppas = "--config(-locations)? \"(.*?)\"".toRegex().findAll(final)
ppas.forEach {
val path = "\"(.*?)\"".toRegex().find(it.value)?.value?.replace("\"", "")
final = final.replace(it.value, "")
final += " ${File(path ?: "").readText()}"
ppas.forEach {res ->
val path = "\"(.*?)\"".toRegex().find(res.value)?.value?.replace("\"", "")
val newVal = runCatching {
File(path ?: "").readText()
}.onFailure {
res.value
}.getOrDefault("")
final = final.replace(res.value, newVal)
}
return final

@ -31,6 +31,7 @@ import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.view.isVisible
@ -543,14 +544,14 @@ object UiUtil {
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.format_details_sheet)
val formatIdParent = bottomSheet.findViewById<LinearLayout>(R.id.format_id_parent)
val formatURLParent = bottomSheet.findViewById<LinearLayout>(R.id.format_url_parent)
val containerParent = bottomSheet.findViewById<LinearLayout>(R.id.container_parent)
val codecParent = bottomSheet.findViewById<LinearLayout>(R.id.codec_parent)
val filesizeParent = bottomSheet.findViewById<LinearLayout>(R.id.filesize_parent)
val formatnoteParent = bottomSheet.findViewById<LinearLayout>(R.id.format_note_parent)
val fpsParent = bottomSheet.findViewById<LinearLayout>(R.id.fps_parent)
val asrParent = bottomSheet.findViewById<LinearLayout>(R.id.asr_parent)
val formatIdParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_id_parent)
val formatURLParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_url_parent)
val containerParent = bottomSheet.findViewById<ConstraintLayout>(R.id.container_parent)
val codecParent = bottomSheet.findViewById<ConstraintLayout>(R.id.codec_parent)
val filesizeParent = bottomSheet.findViewById<ConstraintLayout>(R.id.filesize_parent)
val formatnoteParent = bottomSheet.findViewById<ConstraintLayout>(R.id.format_note_parent)
val fpsParent = bottomSheet.findViewById<ConstraintLayout>(R.id.fps_parent)
val asrParent = bottomSheet.findViewById<ConstraintLayout>(R.id.asr_parent)
if (format.format_id.isBlank()) formatIdParent?.visibility = View.GONE
else {
@ -690,7 +691,7 @@ object UiUtil {
}
suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: CommandTemplate) -> Unit) {
suspend fun showCommandTemplates(activity: Activity, commandTemplateViewModel: CommandTemplateViewModel, itemSelected: (itemSelected: List<CommandTemplate>) -> Unit) {
val bottomSheet = BottomSheetDialog(activity)
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE)
bottomSheet.setContentView(R.layout.command_template_list)
@ -701,17 +702,33 @@ object UiUtil {
}
linearLayout!!.removeAllViews()
val selectedItems = mutableListOf<CommandTemplate>()
val ok = bottomSheet.findViewById<MaterialButton>(R.id.command_ok)
ok?.isEnabled = list.size == 1
list.forEach {template ->
val item = activity.layoutInflater.inflate(R.layout.command_template_item, linearLayout, false) as MaterialCardView
item.findViewById<TextView>(R.id.title).text = template.title
item.findViewById<TextView>(R.id.content).text = template.content
item.setOnClickListener {
itemSelected(template)
bottomSheet.cancel()
if (selectedItems.contains(template)){
selectedItems.remove(template)
(it as MaterialCardView).isChecked = false
}else{
selectedItems.add(template)
(it as MaterialCardView).isChecked = true
}
ok?.isEnabled = selectedItems.isNotEmpty()
}
linearLayout.addView(item)
}
ok?.setOnClickListener {
itemSelected(selectedItems.ifEmpty { listOf(list.first()) })
bottomSheet.cancel()
}
bottomSheet.show()
bottomSheet.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
@ -913,9 +930,6 @@ object UiUtil {
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
editText.doOnTextChanged { _, _, _, _ ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
}
dialog.show()
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
@ -1016,9 +1030,6 @@ object UiUtil {
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
editText.doOnTextChanged { _, _, _, _ ->
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
}
dialog.show()
val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({

@ -116,11 +116,7 @@ class DownloadWorker(
"Title: ${downloadItem.title}\n" +
"URL: ${downloadItem.url}\n" +
"Type: ${downloadItem.type}\n" +
if (downloadItem.type != DownloadViewModel.Type.command){
"Format: ${downloadItem.format}\n\n"
}
else { "" } +
"Command:\n ${infoUtil.parseYTDLRequestString(request)} ${downloadItem.extraCommands}\n\n",
"Command:\n ${infoUtil.parseYTDLRequestString(request)}\n\n",
downloadItem.format,
downloadItem.type,
System.currentTimeMillis(),

@ -100,7 +100,13 @@ class TerminalDownloadWorker(
}
YoutubeDL.getInstance().execute(request, itemId.toString()){ progress, _, line ->
setProgressAsync(workDataOf("progress" to progress.toInt(), "output" to line.chunked(5000).first().toString(), "id" to itemId, "log" to logDownloads))
runBlocking {
line.chunked(10000).forEach {
setProgress(workDataOf("progress" to progress.toInt(), "output" to it, "id" to itemId, "log" to logDownloads))
Thread.sleep(100)
}
}
val title: String = command.take(65)
notificationUtil.updateDownloadNotification(
itemId,
@ -128,32 +134,46 @@ class TerminalDownloadWorker(
}
}
outputFile.appendText("${it.out}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
logRepo.update(it.out, logItem.id)
return runBlocking {
it.out.chunked(10000).forEach {
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
Thread.sleep(100)
}
}
notificationUtil.cancelDownloadNotification(itemId)
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
logRepo.update(it.out, logItem.id)
}
}
notificationUtil.cancelDownloadNotification(itemId)
return@runBlocking Result.success()
}
}.onFailure {
if (it is YoutubeDL.CanceledException) {
return Result.failure()
}
outputFile.appendText("\n${it.message}\n")
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
if (it.message != null){
logRepo.update(it.message!!, logItem.id)
return runBlocking {
it.message?.chunked(10000)?.forEach {
setProgress(workDataOf("progress" to 100, "output" to it, "id" to itemId, "log" to logDownloads))
Thread.sleep(100)
}
if (logDownloads){
CoroutineScope(Dispatchers.IO).launch {
if (it.message != null){
logRepo.update(it.message!!, logItem.id)
}
}
}
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
Log.e(TAG, context.getString(R.string.failed_download), it)
notificationUtil.cancelDownloadNotification(itemId)
return@runBlocking Result.failure()
}
File(FileUtil.getDefaultCommandPath() + "/" + itemId).deleteRecursively()
Log.e(TAG, context.getString(R.string.failed_download), it)
notificationUtil.cancelDownloadNotification(itemId)
return Result.failure()
}
return Result.success()

@ -24,8 +24,12 @@
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="false"
android:text="@string/command_templates"
android:textSize="25sp"
android:maxLines="2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/command_ok"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -39,6 +43,18 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
<Button
android:id="@+id/command_ok"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/ok"
app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -15,37 +16,41 @@
android:layout_margin="20dp"
android:layout_height="wrap_content"/>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/format_id_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingHorizontal="10dp"
android:weightSum="100"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:maxLines="2"
android:text="@string/format_id"
android:textSize="14sp"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/format_id_value"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/format_id_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/format_url_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -56,29 +61,35 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:maxLines="2"
android:text="@string/url"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="14sp"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/format_url_value"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/format_url_value"
android:layout_width="0dp"
android:maxLines="3"
android:ellipsize="end"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:ellipsize="end"
android:gravity="end"
android:layout_height="wrap_content"/>
android:maxLines="3"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -89,26 +100,32 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:id="@+id/textView3"
android:layout_width="wrap_content"
app:layout_constraintHorizontal_bias="0.0"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:maxLines="2"
android:textSize="14sp"
android:text="@string/container"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/container_value"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/container_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/codec_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -119,25 +136,31 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:textSize="14sp"
android:text="@string/codec"
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
android:text="@string/codec"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/codec_value"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/codec_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/filesize_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -148,25 +171,31 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@string/file_size"
android:textSize="14sp"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toStartOf="@+id/filesize_value"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/filesize_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/format_note_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -177,25 +206,30 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@string/quality"
android:layout_weight="30"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="14sp"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/format_note_value"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/format_note_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/fps_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -206,25 +240,31 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="FPS"
android:textSize="14sp"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/fps_value"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/fps_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/asr_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -235,23 +275,29 @@
android:orientation="horizontal" >
<TextView
android:layout_width="0dp"
android:layout_weight="30"
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
app:layout_constraintHorizontal_bias="0.0"
android:text="@string/audio_samplerate"
android:textSize="14sp"
android:layout_margin="10dp"
android:layout_height="wrap_content"/>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/asr_value"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/asr_value"
android:layout_width="0dp"
android:layout_weight="70"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="end"
android:layout_height="wrap_content"/>
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

@ -26,6 +26,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/format"
android:maxLines="2"
android:singleLine="false"
android:textSize="25sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

@ -788,9 +788,8 @@
<item>@string/defaultValue</item>
<item tools:ignore="Typos">AV1</item>
<item>VP9</item>
<item tools:ignore="Typos">AVC</item>
<item>H265</item>
<item>H264</item>
<item tools:ignore="Typos">AVC (H264)</item>
<item>HEVC (H265)</item>
</string-array>
<string-array name="video_codec_values">
@ -799,8 +798,5 @@
<item>vp</item>
<item>avc</item>
<item>hev</item>
<item>h264</item>
</string-array>
<string name="auto">Auto</string>
</resources>

@ -302,4 +302,5 @@
<string name="use_scheduler">Download On Schedule</string>
<string name="update_app_beta">Beta Releases</string>
<string name="update_app_beta_summary">Enroll in the beta program. It may contain bugs.</string>
<string name="auto">Auto</string>
</resources>

@ -11,6 +11,12 @@
app:summary="en"
app:title="@string/language" />
<Preference
android:icon="@drawable/baseline_translate_24"
app:key="app_language_13"
app:summary="en"
app:title="@string/language" />
<ListPreference
android:entries="@array/themes"

@ -57,7 +57,7 @@
<EditTextPreference
app:key="piped_instance"
app:defaultValue="https://api.piped.projectsegfau.lt"
app:defaultValue=""
android:summary="@string/piped_instance_summary"
app:title="@string/piped_instance" />

Loading…
Cancel
Save