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 'it.xabaras.android:recyclerview-swipedecorator:1.4'
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.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.provider.Settings
import android.util.Log
import android.view.View
@ -184,6 +185,7 @@ class MainActivity : BaseActivity() {
handleIntents(intent)
}
fun hideBottomNavigation(){
if(navigationView is NavigationBarView){
if (navigationView is BottomNavigationView){
@ -299,21 +301,6 @@ class MainActivity : BaseActivity() {
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(
requestCode: Int,
permissions: Array<String>,
@ -373,8 +349,6 @@ class MainActivity : BaseActivity() {
if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) continue
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
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)
else -> {
actionButton.setIconResource(R.drawable.ic_baseline_file_open_24)
val logFile = FileUtil.getLogFile(activity, item)
if (!logFile.exists()){
val logFile = FileUtil.checkLogFileExists(activity, item)
if (logFile == null){
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)
}
if (btn.hasOnClickListeners()) btn.setOnClickListener(null)
btn.isClickable = false
btn.isClickable = filePresent
if (checkedItems.contains(item.id)) {
card.isChecked = true
card.strokeWidth = 5
@ -138,6 +139,10 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
onItemClickListener.onCardClick(item.id, finalFilePresent)
}
}
btn.setOnClickListener {
onItemClickListener.onButtonClick(item.id, finalFilePresent)
}
}
private fun checkCard(card: MaterialCardView, itemID: Long) {
@ -154,6 +159,7 @@ class HistoryAdapter(onItemClickListener: OnItemClickListener, activity: Activit
interface OnItemClickListener {
fun onCardClick(itemID: Long, isPresent: Boolean)
fun onButtonClick(itemID: Long, isPresent: 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.net.ConnectivityManager
import android.os.Environment
import android.os.Looper
import android.util.DisplayMetrics
import android.util.Log
import android.widget.Toast
@ -438,7 +439,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
d.status = DownloadRepository.Status.Queued.toString()
d.toString() == it.toString()
} != 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{
if (it.id == 0L){
it.id = lastDownloadId
@ -486,7 +489,9 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
val isCurrentNetworkMetered = (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager).isActiveNetworkMetered
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(
requestCode: Int,
permissions: Array<String>,
@ -227,8 +216,6 @@ class ShareActivity : BaseActivity() {
if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) break
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
createPermissionRequestDialog()
}else{
createDefaultFolders()
}
}
}

View file

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

View file

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

View file

@ -5,23 +5,32 @@ import android.app.Activity
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle
import android.provider.MediaStore.Video
import android.text.format.DateFormat
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.get
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
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.button.MaterialButton
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.textfield.TextInputLayout
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -60,6 +71,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var bottomAppBar: BottomAppBar
private lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
@ -67,6 +79,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
}
@SuppressLint("RestrictedApi", "NotifyDataSetChanged")
@ -143,15 +156,16 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
}
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>>()
allFormats.forEach {f ->
formatCollection.add(f.mapTo(mutableListOf()) {it.copy()})
}
items.forEachIndexed { index, it ->
it.allFormats.clear()
if (formatCollection.isNotEmpty() && formatCollection[index].isNotEmpty()) it.allFormats.addAll(formatCollection[index])
it.format = selectedFormats[index]
items.forEachIndexed { index, i ->
i.allFormats.clear()
if (formatCollection.isNotEmpty() && formatCollection[index].isNotEmpty()) i.allFormats.addAll(formatCollection[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.notifyDataSetChanged()
@ -193,7 +207,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
bottomAppBar.menu[2].icon?.alpha = 255
bottomAppBar.menu[1].icon?.alpha = 255
bottomSheet.cancel()
}
@ -202,7 +216,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
bottomAppBar.menu[2].icon?.alpha = 255
bottomAppBar.menu[1].icon?.alpha = 255
bottomSheet.cancel()
}
@ -214,7 +228,7 @@ class DownloadMultipleBottomSheetDialog(private var results: List<ResultItem?>,
listAdapter.submitList(items.toList())
listAdapter.notifyDataSetChanged()
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
bottomAppBar.menu[2].icon?.alpha = 255
bottomAppBar.menu[1].icon?.alpha = 255
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 -> {
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 })
items[i] = item
if (! items.all { it.type == items[0].type }){
bottomAppBar.menu[2].icon?.alpha = 30
bottomAppBar.menu[1].icon?.alpha = 30
}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())
}

View file

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

View file

@ -15,6 +15,7 @@ import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import com.deniscerri.ytdlnis.util.FileUtil
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 = when(items.first()?.type){
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 {
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 = when(items.first()?.type){
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()
chosenFormats.forEach {
@ -185,14 +186,28 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
}
okBtn.setOnClickListener {
val selectedFormats = mutableListOf<Format>()
if (!::selectedVideo.isInitialized) {
selectedVideo =
chosenFormats.filter { it.vcodec.isNotBlank() && it.vcodec != "none" }.maxByOrNull { it.filesize }!!
}
selectedFormats.add(selectedVideo)
selectedFormats.addAll(selectedAudios)
listener.onFormatClick(List(items.size){chosenFormats}, selectedFormats)
//simple video format selection
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()
}
@ -201,7 +216,7 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
}
}
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()
audioFormatList.removeAllViews()
@ -230,12 +245,12 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
formatItem.tag = "${format.format_id}${format.format_note}"
UiUtil.populateFormatCard(formatItem as MaterialCardView, format, null)
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){
val clickedCard = (clickedformat as MaterialCardView)
if (format.vcodec.isNotBlank() && format.vcodec != "none") {
if (clickedCard.isChecked) {
listener.onFormatClick(List(items.size){chosenFormats}, listOf(format))
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null)))
dismiss()
}
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)
}
}else{
if (items.size == 1){
listener.onFormatClick(List(items.size){chosenFormats}, listOf(format))
}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)
}
//if its a simple audio format selection
listener.onFormatClick(List(items.size){chosenFormats}, listOf(FormatTuple(format, null)))
dismiss()
}
}
@ -303,5 +306,10 @@ class FormatSelectionBottomSheetDialog(private val items: List<DownloadItem?>, p
}
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())
}
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
for (i in websites.indices) {
val w = websites[i]
@ -374,6 +377,7 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
}
websiteGroup!!.addView(tmp)
}
requireView().findViewById<View>(R.id.website_divider).visibility = VISIBLE
}
@ -458,7 +462,8 @@ class HistoryFragment : Fragment(), HistoryAdapter.OnItemClickListener{
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
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 {
private const val TAG = "historyFragment"
}

View file

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Build
import android.os.Bundle
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.ChipGroup
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.regex.Pattern
import kotlin.properties.Delegates
@ -219,6 +225,28 @@ class TerminalActivity : BaseActivity() {
}
}
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() {

View file

@ -92,21 +92,12 @@ object FileUtil {
//sending to main or SD CARD
if (currentDirectory.exists()){
val inputStream = context.contentResolver.openInputStream(it.toUri())!!
val outputStream = FileOutputStream(destFile)
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
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)
if (Build.VERSION.SDK_INT >= 26 ){
Files.move(it.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}else{
it.renameTo(destFile)
}
inputStream.close()
outputStream.closeQuietly()
fileList.add(destFile)
}else{
//sending to USB OTG
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
@ -177,6 +168,12 @@ object FileUtil {
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""")
}
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 {
val titleRegex = Regex("[^A-Za-z\\d ]")
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 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())
items.add(ResultItem(0,

View file

@ -9,8 +9,10 @@ import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import androidx.navigation.NavDeepLinkBuilder
import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.receiver.CancelDownloadNotificationReceiver
@ -235,14 +237,15 @@ class NotificationUtil(var context: Context) {
) {
val notificationBuilder = getBuilder(channel)
val intent = Intent(context, MainActivity::class.java)
intent.putExtra("logpath", logFile?.absolutePath)
val bundle = Bundle()
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
.setContentTitle("${context.getString(R.string.failed_download)}: $title")
.setContentText(error)

View file

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

View file

@ -1,5 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="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>

View file

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

View file

@ -1,5 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="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>

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android: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>

View file

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

View file

@ -86,7 +86,7 @@
</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
android:id="@+id/website_chip_group"

View file

@ -3,23 +3,31 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
<item
android:id="@+id/folder"
android:title="@string/directories"
android:icon="@drawable/ic_folders"
app:showAsAction="collapseActionView|ifRoom"/>
<item
android:id="@+id/preferred_download_type"
android:title="@string/preferred_download_type"
android:icon="@drawable/baseline_insert_drive_file_24"
app:showAsAction="collapseActionView|ifRoom"/>
app:showAsAction="ifRoom"/>
<item
android:id="@+id/format"
android:title="@string/format"
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"/>
<item
android:id="@+id/folder"
android:title="@string/directories"
android:icon="@drawable/ic_folders"
app:showAsAction="ifRoom"/>
</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>ko</item>
<item>lv</item>
<item>ms</item>
<item>nb</item>
<item>pl</item>
<item>pt</item>

View file

@ -275,4 +275,5 @@
<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_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>

View file

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

View file

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