diff --git a/app/build.gradle b/app/build.gradle index b0f1d567..6ef7ec47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index ad28bce9..2a31ea72 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -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, @@ -373,8 +349,6 @@ class MainActivity : BaseActivity() { if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) continue if (grantResults[i] == PackageManager.PERMISSION_DENIED) { createPermissionRequestDialog() - }else{ - createDefaultFolders() } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt index 4c7d19ad..6ac673c8 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/GenericDownloadAdapter.kt @@ -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 } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt index d47d8f98..dfb2baf7 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HistoryAdapter.kt @@ -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) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt index dfebe321..4d22d4e3 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/DownloadViewModel.kt @@ -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() + } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt index c4d847e1..31eb33f9 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt @@ -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, @@ -227,8 +216,6 @@ class ShareActivity : BaseActivity() { if (permissions.contains(Manifest.permission.POST_NOTIFICATIONS)) break if (grantResults[i] == PackageManager.PERMISSION_DENIED) { createPermissionRequestDialog() - }else{ - createDefaultFolders() } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt index bbf43350..ff001f45 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/CutVideoBottomSheetDialog.kt @@ -186,7 +186,7 @@ class CutVideoBottomSheetDialog(private val item: DownloadItem, private val urls lifecycleScope.launch { try { val data : MutableList = withContext(Dispatchers.IO){ - if (urls.isNullOrEmpty() || chapters.isNullOrEmpty()) { + if (urls.isNullOrEmpty()) { infoUtil.getStreamingUrlAndChapters(item.url) }else { urls.split("\n").toMutableList() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt index 3cfc4481..ea9eca87 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadAudioFragment.kt @@ -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>, item: List) { - downloadItem.format = item.first() - UiUtil.populateFormatCard(formatCard, item.first(), null) + override fun onFormatClick(allFormats: List>, item: List) { + downloadItem.format = item.first().format + UiUtil.populateFormatCard(formatCard, item.first().format, null) lifecycleScope.launch { withContext(Dispatchers.IO){ resultItem.formats.removeAll(formats.toSet()) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt index d08164cc..ae46103c 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadMultipleBottomSheetDialog.kt @@ -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, private lateinit var recyclerView: RecyclerView private lateinit var behavior: BottomSheetBehavior 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, 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, } val formatListener = object : OnFormatClickListener { - override fun onFormatClick(allFormats: List>, selectedFormats: List) { + override fun onFormatClick(allFormats: List>, selectedFormats: List) { val formatCollection = mutableListOf>() 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, 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, 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, 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, ) } + } + 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(R.id.url_edittext) + inputLayout.findViewById(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, 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()) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt index 5c8be596..c5d56198 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/DownloadVideoFragment.kt @@ -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>, item: List) { - downloadItem.format = item.first() - downloadItem.videoPreferences.audioFormatIDs.addAll(item.drop(1).map { it.format_id }) + override fun onFormatClick(allFormats: List>, item: List) { + 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{ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt index 6d90dc19..84f14b10 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/FormatSelectionBottomSheetDialog.kt @@ -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, 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, 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, p } okBtn.setOnClickListener { - val selectedFormats = mutableListOf() 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() + 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, 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, 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, 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() - 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, p } interface OnFormatClickListener{ - fun onFormatClick(allFormats: List>, item: List) -} \ No newline at end of file + fun onFormatClick(allFormats: List>, item: List) +} + +class FormatTuple internal constructor( + var format: Format, + var audioFormats: List? +) \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt index 56790ba9..73cb11a9 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/HistoryFragment.kt @@ -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(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(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" } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt index 307a9a35..43c793e0 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/TerminalActivity.kt @@ -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() { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt index 512eb85c..a2e6247b 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt @@ -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""") diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt index 99eb01dc..dbebf68e 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -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>() {}.type - val chapters : ArrayList = Gson().fromJson(chaptersInJSON?.toString(), listType) + var chapters : ArrayList = arrayListOf() + + if (chaptersInJSON != null){ + chapters = Gson().fromJson(chaptersInJSON.toString(), listType) + } Log.e("aa", formats.toString()) items.add(ResultItem(0, diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt index 70d03392..6ce8ce97 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/NotificationUtil.kt @@ -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) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt index f1c5e7af..4be7c929 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -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) } diff --git a/app/src/main/res/drawable/baseline_delete_24.xml b/app/src/main/res/drawable/baseline_delete_24.xml index 1a93522f..5f9f6afc 100644 --- a/app/src/main/res/drawable/baseline_delete_24.xml +++ b/app/src/main/res/drawable/baseline_delete_24.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/baseline_download_24.xml b/app/src/main/res/drawable/baseline_download_24.xml index e8d7c46d..60e91236 100644 --- a/app/src/main/res/drawable/baseline_download_24.xml +++ b/app/src/main/res/drawable/baseline_download_24.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/baseline_share_24.xml b/app/src/main/res/drawable/baseline_share_24.xml index 66aa3204..f159a3ac 100644 --- a/app/src/main/res/drawable/baseline_share_24.xml +++ b/app/src/main/res/drawable/baseline_share_24.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_downloads.xml b/app/src/main/res/drawable/ic_downloads.xml index e69407e0..e8d7c46d 100644 --- a/app/src/main/res/drawable/ic_downloads.xml +++ b/app/src/main/res/drawable/ic_downloads.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/layout/download_type_sheet.xml b/app/src/main/res/layout/download_type_sheet.xml index d5fc51a5..6f05a147 100644 --- a/app/src/main/res/layout/download_type_sheet.xml +++ b/app/src/main/res/layout/download_type_sheet.xml @@ -9,15 +9,12 @@ - + - + app:showAsAction="ifRoom"/> + + + + + diff --git a/app/src/main/res/values-land/attrs.xml b/app/src/main/res/values-land/attrs.xml new file mode 100644 index 00000000..e5ad9720 --- /dev/null +++ b/app/src/main/res/values-land/attrs.xml @@ -0,0 +1,4 @@ + + 2 + false + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 707ef1a7..50419980 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -102,6 +102,7 @@ ja ko lv + ms nb pl pt diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0372722f..0f3ca004 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -275,4 +275,5 @@ This item only contains audio formats Move Temporary Files Transfer cached files to the downloads folder + All items must either be audio or video to use this option \ No newline at end of file diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index 3d02b9dc..5f528366 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -13,6 +13,7 @@ + diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 571d6f9d..72984b83 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -13,7 +13,7 @@