even more stuff

Added malay language
Fixed app not recognising log files if it was quick downloaded
Fixed app not closing download item when using -o in CC
Added code syntax highlighter in terminal
Added filename template on playlist batch download
added ability to multi-select audio on playlist download
Removed default folder auto creation
This commit is contained in:
deniscerri 2023-05-31 23:38:17 +02:00
parent 7d5d6af5e8
commit 23c469f4bd
No known key found for this signature in database
GPG key ID: 95C43D517D830350
29 changed files with 250 additions and 137 deletions

View file

@ -179,4 +179,5 @@ dependencies {
implementation "com.google.android.exoplayer:exoplayer:2.18.5" implementation "com.google.android.exoplayer:exoplayer:2.18.5"
implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4' implementation 'it.xabaras.android:recyclerview-swipedecorator:1.4'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1"
implementation "com.github.Irineu333:Highlight-KT:1.0.4"
} }

View file

@ -12,6 +12,7 @@ import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@ -184,6 +185,7 @@ class MainActivity : BaseActivity() {
handleIntents(intent) handleIntents(intent)
} }
fun hideBottomNavigation(){ fun hideBottomNavigation(){
if(navigationView is NavigationBarView){ if(navigationView is NavigationBarView){
if (navigationView is BottomNavigationView){ if (navigationView is BottomNavigationView){
@ -299,21 +301,6 @@ class MainActivity : BaseActivity() {
e.printStackTrace() e.printStackTrace()
} }
} }
else{
//coming from errored notification
if (intent.hasExtra("logpath")){
val bundle = Bundle()
bundle.putString("logpath", intent.getStringExtra("logpath"))
navController.navigate(
R.id.downloadLogFragment,
bundle
)
//coming from active download notification
}else if (intent.hasExtra("activedownload")){
navController.popBackStack(R.id.downloadQueueMainFragment, true)
navController.navigate(R.id.downloadQueueMainFragment)
}
}
} }
@ -352,17 +339,6 @@ class MainActivity : BaseActivity() {
} }
} }
private fun createDefaultFolders(){
val audio = File(FileUtil.getDefautAudioPath())
val video = File(FileUtil.getDefautVideoPath())
val command = File(FileUtil.getDefaultCommandPath())
audio.mkdirs()
video.mkdirs()
command.mkdirs()
}
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<String>, permissions: Array<String>,
@ -373,8 +349,6 @@ class MainActivity : BaseActivity() {
if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) continue if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) continue
if (grantResults[i] == PackageManager.PERMISSION_DENIED) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
createPermissionRequestDialog() createPermissionRequestDialog()
}else{
createDefaultFolders()
} }
} }
} }

View file

@ -105,8 +105,8 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24) DownloadRepository.Status.Queued.toString() -> actionButton.setIconResource(R.drawable.ic_baseline_delete_outline_24)
else -> { else -> {
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24) actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
val logFile = FileUtil.getLogFile(activity, item) val logFile = FileUtil.checkLogFileExists(activity, item)
if (!logFile.exists()){ if (logFile == null){
actionButton.visibility = View.GONE actionButton.visibility = View.GONE
} }
} }

View file

@ -118,7 +118,8 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_terminal) btn.icon = ContextCompat.getDrawable(activity, R.drawable.ic_terminal)
} }
if (btn.hasOnClickListeners()) btn.setOnClickListener(null) if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = false btn.isClickable = filePresent
if (checkedItems.contains(item.id)) { if (checkedItems.contains(item.id)) {
card.isChecked = true card.isChecked = true
card.strokeWidth = 5 card.strokeWidth = 5
@ -138,6 +139,10 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
onItemClickListener.onCardClick(item.id, finalFilePresent) onItemClickListener.onCardClick(item.id, finalFilePresent)
} }
} }
btn.setOnClickListener {
onItemClickListener.onButtonClick(item.id, finalFilePresent)
}
} }
private fun checkCard(card: MaterialCardView, itemID: Long) { private fun checkCard(card: MaterialCardView, itemID: Long) {
@ -154,6 +159,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
interface OnItemClickListener { interface OnItemClickListener {
fun onCardClick(itemID: Long, isPresent: Boolean) fun onCardClick(itemID: Long, isPresent: Boolean)
fun onButtonClick(itemID: Long, isPresent: Boolean)
fun onCardSelect(itemID: Long, isChecked: Boolean) fun onCardSelect(itemID: Long, isChecked: Boolean)
} }

View file

@ -7,6 +7,7 @@ import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Environment import android.os.Environment
import android.os.Looper
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
@ -438,7 +439,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
d.status = DownloadRepository.Status.Queued.toString() d.status = DownloadRepository.Status.Queued.toString()
d.toString() == it.toString() d.toString() == it.toString()
} != null) { } != null) {
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show() Looper.prepare().run {
Toast.makeText(context, context.getString(R.string.download_already_exists), Toast.LENGTH_LONG).show()
}
}else{ }else{
if (it.id == 0L){ if (it.id == 0L){
it.id = lastDownloadId it.id = lastDownloadId
@ -486,7 +489,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
if (!allowMeteredNetworks && isCurrentNetworkMetered){ if (!allowMeteredNetworks && isCurrentNetworkMetered){
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show() Looper.prepare().run {
Toast.makeText(context, context.getString(R.string.metered_network_download_start_info), Toast.LENGTH_LONG).show()
}
} }
} }

View file

@ -206,17 +206,6 @@ class ShareActivity : BaseActivity() {
} }
} }
private fun createDefaultFolders(){
val audio = File(FileUtil.getDefautAudioPath())
val video = File(FileUtil.getDefautVideoPath())
val command = File(FileUtil.getDefaultCommandPath())
audio.mkdirs()
video.mkdirs()
command.mkdirs()
}
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<String>, permissions: Array<String>,
@ -227,8 +216,6 @@ class ShareActivity : BaseActivity() {
if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) break if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) break
if (grantResults[i] == PackageManager.PERMISSION_DENIED) { if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
createPermissionRequestDialog() createPermissionRequestDialog()
}else{
createDefaultFolders()
} }
} }
} }

View file

@ -186,7 +186,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val data : MutableList<String?> = withContext(Dispatchers.IO){ val data : MutableList<String?> = withContext(Dispatchers.IO){
if (urls.isNullOrEmpty() || chapters.isNullOrEmpty()) { if (urls.isNullOrEmpty()) {
infoUtil.getStreamingUrlAndChapters(item.url) infoUtil.getStreamingUrlAndChapters(item.url)
}else { }else {
urls.split("\n").toMutableList() urls.split("\n").toMutableList()

View file

@ -142,9 +142,9 @@ class DownloadAudioFragment(private var resultItem: ResultItem, private var curr
val chosenFormat = downloadItem.format val chosenFormat = downloadItem.format
UiUtil.populateFormatCard(formatCard, chosenFormat, null) UiUtil.populateFormatCard(formatCard, chosenFormat, null)
val listener = object : OnFormatClickListener { val listener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) { override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
downloadItem.format = item.first() downloadItem.format = item.first().format
UiUtil.populateFormatCard(formatCard, item.first(), null) UiUtil.populateFormatCard(formatCard, item.first().format, null)
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
resultItem.formats.removeAll(formats.toSet()) resultItem.formats.removeAll(formats.toSet())

View file

@ -5,23 +5,32 @@ import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore.Video
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.Button import android.widget.Button
import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.get import androidx.core.view.get
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -42,7 +51,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textfield.TextInputLayout
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -60,6 +71,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var bottomAppBar: BottomAppBar private lateinit var bottomAppBar: BottomAppBar
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -67,6 +79,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
} }
@SuppressLint("RestrictedApi", "NotifyDataSetChanged") @SuppressLint("RestrictedApi", "NotifyDataSetChanged")
@ -143,15 +156,16 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
} }
val formatListener = object : OnFormatClickListener { val formatListener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<List<Format>>, selectedFormats: List<Format>) { override fun onFormatClick(allFormats: List<List<Format>>, selectedFormats: List<FormatTuple>) {
val formatCollection = mutableListOf<List<Format>>() val formatCollection = mutableListOf<List<Format>>()
allFormats.forEach {f -> allFormats.forEach {f ->
formatCollection.add(f.mapTo(mutableListOf()) {it.copy()}) formatCollection.add(f.mapTo(mutableListOf()) {it.copy()})
} }
items.forEachIndexed { index, it -> items.forEachIndexed { index, i ->
it.allFormats.clear() i.allFormats.clear()
if (formatCollection.isNotEmpty() && formatCollection[index].isNotEmpty()) it.allFormats.addAll(formatCollection[index]) if (formatCollection.isNotEmpty() && formatCollection[index].isNotEmpty()) i.allFormats.addAll(formatCollection[index])
it.format = selectedFormats[index] i.format = selectedFormats[index].format
if (i.type == DownloadViewModel.Type.video) selectedFormats[index].audioFormats?.map { it.format_id }?.let { i.videoPreferences.audioFormatIDs.addAll(it) }
} }
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
@ -193,7 +207,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24) preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[2].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
bottomSheet.cancel() bottomSheet.cancel()
} }
@ -202,7 +216,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24) preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[2].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
bottomSheet.cancel() bottomSheet.cancel()
} }
@ -214,7 +228,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged() listAdapter.notifyDataSetChanged()
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24) preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[2].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
bottomSheet.cancel() bottomSheet.cancel()
} }
} }
@ -229,6 +243,52 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
) )
} }
}
R.id.filename_template -> {
if (items.count { it.type == DownloadViewModel.Type.command } != 0){
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint_2), Toast.LENGTH_SHORT).show()
}else{
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle(getString(R.string.file_name_template))
val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
val editText = inputLayout.findViewById<EditText>(R.id.url_edittext)
inputLayout.findViewById<TextInputLayout>(R.id.url_textinput).hint = getString(R.string.file_name_template)
editText.setSelection(editText.text.length)
builder.setView(inputLayout)
builder.setPositiveButton(
getString(R.string.ok)
) { dialog: DialogInterface?, which: Int ->
lifecycleScope.launch {
items.forEach {
it.customFileNameTemplate = editText.text.toString()
}
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
}
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
getString(R.string.cancel)
) { dialog: DialogInterface?, which: Int -> }
val filenameTemplateDialog = builder.create()
editText.doOnTextChanged { text, start, before, count ->
filenameTemplateDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
}
filenameTemplateDialog.show()
val imm = context?.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager
editText!!.postDelayed({
editText.requestFocus()
imm.showSoftInput(editText, 0)
}, 300)
filenameTemplateDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
filenameTemplateDialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
}
} }
R.id.format -> { R.id.format -> {
if (! items.all { it.type == items[0].type }){ if (! items.all { it.type == items[0].type }){
@ -390,10 +450,17 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
val i = items.indexOf(items.find { it.url == item.url }) val i = items.indexOf(items.find { it.url == item.url })
items[i] = item items[i] = item
if (! items.all { it.type == items[0].type }){ if (! items.all { it.type == items[0].type }){
bottomAppBar.menu[2].icon?.alpha = 30 bottomAppBar.menu[1].icon?.alpha = 30
}else{ }else{
bottomAppBar.menu[2].icon?.alpha = 255 bottomAppBar.menu[1].icon?.alpha = 255
} }
if (items.count { it.type == DownloadViewModel.Type.command } == 0){
bottomAppBar.menu[2].icon?.alpha = 255
}else{
bottomAppBar.menu[2].icon?.alpha = 30
}
listAdapter.submitList(items.toList()) listAdapter.submitList(items.toList())
} }

View file

@ -145,9 +145,10 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
val chosenFormat = downloadItem.format val chosenFormat = downloadItem.format
UiUtil.populateFormatCard(formatCard, chosenFormat, downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) }) UiUtil.populateFormatCard(formatCard, chosenFormat, downloadItem.allFormats.filter { downloadItem.videoPreferences.audioFormatIDs.contains(it.format_id) })
val listener = object : OnFormatClickListener { val listener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) { override fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>) {
downloadItem.format = item.first() downloadItem.format = item.first().format
downloadItem.videoPreferences.audioFormatIDs.addAll(item.drop(1).map { it.format_id }) item.first().audioFormats?.map { it.format_id }
?.let { downloadItem.videoPreferences.audioFormatIDs.addAll(it) }
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
resultItem.formats.removeAll(formats.toSet()) resultItem.formats.removeAll(formats.toSet())
@ -156,7 +157,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem, private var curr
} }
} }
formats = allFormats.first().toMutableList() formats = allFormats.first().toMutableList()
UiUtil.populateFormatCard(formatCard, item.first(), item.drop(1)) UiUtil.populateFormatCard(formatCard, item.first().format, item.first().audioFormats)
} }
} }
formatCard.setOnClickListener{ formatCard.setOnClickListener{

View file

@ -15,6 +15,7 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.InfoUtil import com.deniscerri.ytdlnis.util.InfoUtil
@ -90,7 +91,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
chosenFormats = commonFormats.mapTo(mutableListOf()) {it.copy()} chosenFormats = commonFormats.mapTo(mutableListOf()) {it.copy()}
chosenFormats = when(items.first()?.type){ chosenFormats = when(items.first()?.type){
Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) } Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
else -> chosenFormats.filter { !it.format_note.contains("audio", ignoreCase = true) } else -> chosenFormats
} }
chosenFormats.forEach { chosenFormats.forEach {
it.filesize = it.filesize =
@ -155,7 +156,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
chosenFormats = commonFormats.filter { it.filesize != 0L }.mapTo(mutableListOf()) {it.copy()} chosenFormats = commonFormats.filter { it.filesize != 0L }.mapTo(mutableListOf()) {it.copy()}
chosenFormats = when(items.first()?.type){ chosenFormats = when(items.first()?.type){
Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) } Type.audio -> chosenFormats.filter { it.format_note.contains("audio", ignoreCase = true) }
else -> chosenFormats.filter { !it.format_note.contains("audio", ignoreCase = true) } else -> chosenFormats
} }
if (chosenFormats.isEmpty()) throw Exception() if (chosenFormats.isEmpty()) throw Exception()
chosenFormats.forEach { chosenFormats.forEach {
@ -185,14 +186,28 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
} }
okBtn.setOnClickListener { okBtn.setOnClickListener {
val selectedFormats = mutableListOf<Format>()
if (!::selectedVideo.isInitialized) { if (!::selectedVideo.isInitialized) {
selectedVideo = selectedVideo =
chosenFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.maxByOrNull { it.filesize }!! chosenFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.maxByOrNull { it.filesize }!!
} }
selectedFormats.add(selectedVideo)
selectedFormats.addAll(selectedAudios) //simple video format selection
listener.onFormatClick(List(items.size){chosenFormats}, selectedFormats) if (items.size == 1){
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(selectedVideo, selectedAudios)))
}else{
//playlist format selection
val selectedFormats = mutableListOf<Format>()
formatCollection.forEach {
selectedFormats.add(it.first{ f -> f.format_id == selectedVideo.format_id})
}
if (selectedFormats.isEmpty()) {
items.forEach { _ ->
selectedFormats.add(selectedVideo)
}
}
listener.onFormatClick(formatCollection, selectedFormats.map { FormatTuple(it, selectedAudios) })
}
dismiss() dismiss()
} }
@ -201,7 +216,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
} }
} }
private fun addFormatsToView(){ private fun addFormatsToView(){
val canMultiSelectAudio = items.first()?.type == Type.video && items.count() == 1 && chosenFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null val canMultiSelectAudio = items.first()?.type == Type.video && chosenFormats.find { it.format_note.contains("audio", ignoreCase = true) } != null
videoFormatList.removeAllViews() videoFormatList.removeAllViews()
audioFormatList.removeAllViews() audioFormatList.removeAllViews()
@ -230,12 +245,12 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
formatItem.tag = "${format.format_id}${format.format_note}" formatItem.tag = "${format.format_id}${format.format_note}"
UiUtil.populateFormatCard(formatItem as MaterialCardView, format, null) UiUtil.populateFormatCard(formatItem as MaterialCardView, format, null)
formatItem.setOnClickListener{ clickedformat -> formatItem.setOnClickListener{ clickedformat ->
//if the context is behind a single download card and its a video, allow the ability to multiselect audio formats //if the context is behind a video or playlist, allow the ability to multiselect audio formats
if (canMultiSelectAudio){ if (canMultiSelectAudio){
val clickedCard = (clickedformat as MaterialCardView) val clickedCard = (clickedformat as MaterialCardView)
if (format.vcodec.isNotBlank() && format.vcodec != "none") { if (format.vcodec.isNotBlank() && format.vcodec != "none") {
if (clickedCard.isChecked) { if (clickedCard.isChecked) {
listener.onFormatClick(List(items.size){chosenFormats}, listOf(format)) listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null)))
dismiss() dismiss()
} }
videoFormatList.forEach { (it as MaterialCardView).isChecked = false } videoFormatList.forEach { (it as MaterialCardView).isChecked = false }
@ -253,20 +268,8 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
(it as MaterialCardView).isChecked = selectedAudios.map { a -> "${a.format_id}${a.format_note}" }.contains(it.tag) (it as MaterialCardView).isChecked = selectedAudios.map { a -> "${a.format_id}${a.format_note}" }.contains(it.tag)
} }
}else{ }else{
if (items.size == 1){ //if its a simple audio format selection
listener.onFormatClick(List(items.size){chosenFormats}, listOf(format)) listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null)))
}else{
val selectedFormats = mutableListOf<Format>()
formatCollection.forEach {
selectedFormats.add(it.first{ f -> f.format_id == format.format_id})
}
if (selectedFormats.isEmpty()) {
items.forEach {
selectedFormats.add(format)
}
}
listener.onFormatClick(formatCollection, selectedFormats)
}
dismiss() dismiss()
} }
} }
@ -303,5 +306,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
} }
interface OnFormatClickListener{ interface OnFormatClickListener{
fun onFormatClick(allFormats: List<List<Format>>, item: List<Format>) fun onFormatClick(allFormats: List<List<Format>>, item: List<FormatTuple>)
} }
class FormatTuple internal constructor(
var format: Format,
var audioFormats: List<Format>?
)

View file

@ -351,7 +351,10 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
if (!websites.contains(item.website.lowercase())) websites.add(item.website.lowercase()) if (!websites.contains(item.website.lowercase())) websites.add(item.website.lowercase())
} }
websiteGroup!!.removeAllViews() websiteGroup!!.removeAllViews()
if (websites.size <= 1) return if (websites.size <= 1) {
requireView().findViewById<View>(R.id.website_divider).visibility = GONE
return
}
//val websites = historyRecyclerViewAdapter!!.websites //val websites = historyRecyclerViewAdapter!!.websites
for (i in websites.indices) { for (i in websites.indices) {
val w = websites[i] val w = websites[i]
@ -374,6 +377,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
} }
websiteGroup!!.addView(tmp) websiteGroup!!.addView(tmp)
} }
requireView().findViewById<View>(R.id.website_divider).visibility = VISIBLE
} }
@ -458,7 +462,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
codec.text = codecText codec.text = codecText
} }
val fileSizeReadable = FileUtil.convertFileSize(item.format.filesize) val file = File(item.downloadPath)
val fileSizeReadable = FileUtil.convertFileSize(if (file.exists()) file.length() else item.format.filesize)
if (fileSizeReadable == "?") fileSize!!.visibility = GONE if (fileSizeReadable == "?") fileSize!!.visibility = GONE
else fileSize!!.text = fileSizeReadable else fileSize!!.text = fileSizeReadable
@ -686,6 +691,11 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
} }
} }
override fun onButtonClick(itemID: Long, isPresent: Boolean) {
if (isPresent){
UiUtil.shareFileIntent(requireContext(), listOf(historyList!!.first { it!!.id == itemID }!!.downloadPath))
}
}
companion object { companion object {
private const val TAG = "historyFragment" private const val TAG = "historyFragment"
} }

View file

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.FileObserver import android.os.FileObserver
@ -41,11 +42,16 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.neo.highlight.core.Highlight
import com.neo.highlight.core.Scheme
import com.neo.highlight.util.listener.HighlightTextWatcher
import com.neo.highlight.util.scheme.ColorScheme
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.regex.Pattern
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -219,6 +225,28 @@ class TerminalActivity : BaseActivity() {
} }
} }
initMenu() initMenu()
//init syntax highlighter
val highlight = Highlight()
val highlightWatcher = HighlightTextWatcher()
val schemes = listOf(
ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")),
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#00FF00")),
ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")),
ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564"))
)
highlight.addScheme(
*schemes.map { it }.toTypedArray()
)
highlightWatcher.addScheme(
*schemes.map { it }.toTypedArray()
)
highlight.setSpan(output)
highlight.setSpan(input)
input?.addTextChangedListener(highlightWatcher)
output?.addTextChangedListener(highlightWatcher)
} }
private fun initMenu() { private fun initMenu() {

View file

@ -92,21 +92,12 @@ object FileUtil {
//sending to main or SD CARD //sending to main or SD CARD
if (currentDirectory.exists()){ if (currentDirectory.exists()){
val inputStream = context.contentResolver.openInputStream(it.toUri())!! if (Build.VERSION.SDK_INT >= 26 ){
val outputStream = FileOutputStream(destFile) Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}else{
val buffer = ByteArray(DEFAULT_BUFFER_SIZE) it.renameTo(destFile)
var bytesRead: Int
val totalBytes = it.length()
// Transfer the file contents
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
progress((bytesRead / totalBytes).toInt() * 100)
outputStream.write(buffer, 0, bytesRead)
} }
fileList.add(destFile)
inputStream.close()
outputStream.closeQuietly()
}else{ }else{
//sending to USB OTG //sending to USB OTG
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*" val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
@ -177,6 +168,12 @@ object FileUtil {
val title = item.title.ifEmpty { item.url } val title = item.title.ifEmpty { item.url }
return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(title, "").take(150)}##${item.type}##${item.format.format_id}.log""") return File(context.filesDir.absolutePath + """/logs/${item.id} - ${titleRegex.replace(title, "").take(150)}##${item.type}##${item.format.format_id}.log""")
} }
fun checkLogFileExists(context: Context, item: DownloadItem) : File? {
val dir = File(context.filesDir.absolutePath + "/logs/")
return dir.listFiles()?.toList()?.first { it.name.startsWith(item.id.toString()) }
}
fun getLogFileForTerminal(context: Context, command: String) : File { fun getLogFileForTerminal(context: Context, command: String) : File {
val titleRegex = Regex("[^A-Za-z\\d ]") val titleRegex = Regex("[^A-Za-z\\d ]")
return File(context.filesDir.absolutePath + """/logs/Terminal - ${titleRegex.replace(command.take(30), "")}##terminal.log""") return File(context.filesDir.absolutePath + """/logs/Terminal - ${titleRegex.replace(command.take(30), "")}##terminal.log""")

View file

@ -816,7 +816,11 @@ class InfoUtil(private val context: Context) {
val chaptersInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null val chaptersInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
val chapters : ArrayList<ChapterItem> = Gson().fromJson(chaptersInJSON?.toString(), listType) var chapters : ArrayList<ChapterItem> = arrayListOf()
if (chaptersInJSON != null){
chapters = Gson().fromJson(chaptersInJSON.toString(), listType)
}
Log.e("aa", formats.toString()) Log.e("aa", formats.toString())
items.add(ResultItem(0, items.add(ResultItem(0,

View file

@ -9,8 +9,10 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.os.Build import android.os.Build
import android.os.Bundle
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.navigation.NavDeepLinkBuilder
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
@ -235,14 +237,15 @@ class NotificationUtil(var context: Context) {
) { ) {
val notificationBuilder = getBuilder(channel) val notificationBuilder = getBuilder(channel)
val intent = Intent(context, MainActivity::class.java) val bundle = Bundle()
intent.putExtra("logpath", logFile?.absolutePath) bundle.putString("logpath", logFile?.absolutePath)
val errorPendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.downloadLogFragment)
.setArguments(bundle)
.createPendingIntent()
val errorPendingIntent: PendingIntent = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(intent)
getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
notificationBuilder notificationBuilder
.setContentTitle("${context.getString(R.string.failed_download)}: $title") .setContentTitle("${context.getString(R.string.failed_download)}: $title")
.setContentText(error) .setContentText(error)

View file

@ -7,6 +7,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.navigation.NavDeepLinkBuilder
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.work.Data import androidx.work.Data
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
@ -72,9 +73,11 @@ class DownloadWorker(
updateDownloadItem(downloadItem, infoUtil, dao, resultDao, false, notificationUtil) updateDownloadItem(downloadItem, infoUtil, dao, resultDao, false, notificationUtil)
} }
val intent = Intent(context, MainActivity::class.java) val pendingIntent = NavDeepLinkBuilder(context)
intent.putExtra("activedownload", "") .setGraph(R.navigation.nav_graph)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) .setDestination(R.id.downloadQueueMainFragment)
.createPendingIntent()
val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID) val notification = notificationUtil.createDownloadServiceNotification(pendingIntent, downloadItem.title, downloadItem.id.toInt(), NotificationUtil.DOWNLOAD_SERVICE_CHANNEL_ID)
val foregroundInfo = ForegroundInfo(downloadItem.id.toInt(), notification) val foregroundInfo = ForegroundInfo(downloadItem.id.toInt(), notification)
setForegroundAsync(foregroundInfo) setForegroundAsync(foregroundInfo)
@ -129,6 +132,7 @@ class DownloadWorker(
if (downloadItem.author.isNotBlank()){ if (downloadItem.author.isNotBlank()){
request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25))) request.addCommands(listOf("--replace-in-metadata","uploader",".*.",downloadItem.author.take(25)))
} }
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic",""))
if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader)s - %(title)s" if (downloadItem.customFileNameTemplate.isBlank()) downloadItem.customFileNameTemplate = "%(uploader)s - %(title)s"
if (downloadItem.downloadSections.isNotBlank()){ if (downloadItem.downloadSections.isNotBlank()){
@ -281,7 +285,6 @@ class DownloadWorker(
writeText(downloadItem.format.format_note) writeText(downloadItem.format.format_note)
}.absolutePath }.absolutePath
) )
request.addOption("-P", tempFileDir.absolutePath) request.addOption("-P", tempFileDir.absolutePath)
} }
@ -322,7 +325,11 @@ class DownloadWorker(
finalPaths = FileUtil.moveFile(tempFileDir.absoluteFile,context, downloadLocation, keepCache){ p -> 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)) setProgressAsync(workDataOf("progress" to p, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads))
} }
setProgressAsync(workDataOf("progress" to 100, "output" to "Moved file to $downloadLocation", "id" to downloadItem.id, "log" to logDownloads)) 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){ }catch (e: Exception){
finalPaths = listOf(context.getString(R.string.unfound_file)) finalPaths = listOf(context.getString(R.string.unfound_file))
e.printStackTrace() e.printStackTrace()
@ -338,8 +345,8 @@ class DownloadWorker(
if (!incognito) { if (!incognito) {
val unixtime = System.currentTimeMillis() / 1000 val unixtime = System.currentTimeMillis() / 1000
val file = File(finalPaths?.first()!!) val file = File(finalPaths?.first()!!)
downloadItem.format.filesize = if (file.exists()) file.length() else 0L downloadItem.format.filesize = file.length()
val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first(), downloadItem.website, downloadItem.format, downloadItem.id) val historyItem = HistoryItem(0, downloadItem.url, downloadItem.title, downloadItem.author, downloadItem.duration, downloadItem.thumb, downloadItem.type, unixtime, finalPaths.first() , downloadItem.website, downloadItem.format, downloadItem.id)
runBlocking { runBlocking {
historyDao.insert(historyItem) historyDao.insert(historyItem)
} }

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" <vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> <path android:fillColor="?android:textColorPrimary" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector> </vector>

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" <vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/> <path android:fillColor="?android:textColorPrimary" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
</vector> </vector>

View file

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

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF" <vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/> <path android:fillColor="?android:colorAccent" android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z"/>
</vector> </vector>

View file

@ -9,15 +9,12 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp"
android:padding="10dp"
android:layout_marginVertical="10dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:text="@string/preferred_download_type" android:text="@string/preferred_download_type"
android:paddingBottom="20dp" android:layout_margin="20dp"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<TextView <TextView
@ -25,6 +22,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:drawablePadding="30dp" android:drawablePadding="30dp"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -39,6 +37,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:drawablePadding="30dp" android:drawablePadding="30dp"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -53,6 +52,7 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:drawablePadding="30dp" android:drawablePadding="30dp"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -86,7 +86,7 @@
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
<View style="@style/Divider.Vertical"/> <View android:id="@+id/website_divider" style="@style/Divider.Vertical"/>
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/website_chip_group" android:id="@+id/website_chip_group"

View file

@ -3,23 +3,31 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" > tools:context=".MainActivity" >
<item
android:id="@+id/folder"
android:title="@string/directories"
android:icon="@drawable/ic_folders"
app:showAsAction="collapseActionView|ifRoom"/>
<item <item
android:id="@+id/preferred_download_type" android:id="@+id/preferred_download_type"
android:title="@string/preferred_download_type" android:title="@string/preferred_download_type"
android:icon="@drawable/baseline_insert_drive_file_24" android:icon="@drawable/baseline_insert_drive_file_24"
app:showAsAction="collapseActionView|ifRoom"/> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/format" android:id="@+id/format"
android:title="@string/format" android:title="@string/format"
android:icon="@drawable/baseline_high_quality_24" android:icon="@drawable/baseline_high_quality_24"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/filename_template"
android:title="@string/file_name_template"
android:icon="@drawable/if_file_rename"
app:showAsAction="collapseActionView|ifRoom"/> app:showAsAction="collapseActionView|ifRoom"/>
<item
android:id="@+id/folder"
android:title="@string/directories"
android:icon="@drawable/ic_folders"
app:showAsAction="ifRoom"/>
</menu> </menu>

View file

@ -0,0 +1,4 @@
<resources>
<integer name="grid_size">2</integer>
<bool name="isTablet">false</bool>
</resources>

View file

@ -102,6 +102,7 @@
<item>ja</item> <item>ja</item>
<item>ko</item> <item>ko</item>
<item>lv</item> <item>lv</item>
<item>ms</item>
<item>nb</item> <item>nb</item>
<item>pl</item> <item>pl</item>
<item>pt</item> <item>pt</item>

View file

@ -275,4 +275,5 @@
<string name="audio_only_item">This item only contains audio formats</string> <string name="audio_only_item">This item only contains audio formats</string>
<string name="move_temporary_files">Move Temporary Files</string> <string name="move_temporary_files">Move Temporary Files</string>
<string name="move_temporary_files_summary">Transfer cached files to the downloads folder</string> <string name="move_temporary_files_summary">Transfer cached files to the downloads folder</string>
<string name="format_filtering_hint_2">All items must either be audio or video to use this option</string>
</resources> </resources>

View file

@ -13,6 +13,7 @@
<locale android:name="ja"/> <locale android:name="ja"/>
<locale android:name="ko"/> <locale android:name="ko"/>
<locale android:name="lv"/> <locale android:name="lv"/>
<locale android:name="ms"/>
<locale android:name="nb-NO"/> <locale android:name="nb-NO"/>
<locale android:name="pl"/> <locale android:name="pl"/>
<locale android:name="pt-BR" /> <locale android:name="pt-BR" />

View file

@ -13,7 +13,7 @@
<Preference <Preference
android:key="downloading" android:key="downloading"
app:icon="@drawable/baseline_download_24" app:icon="@drawable/ic_downloads"
android:title="@string/downloading"/> android:title="@string/downloading"/>
<Preference <Preference