From 6dc25bab9a4d624dd0401d3ecfc4e3bc72bc1001 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sat, 22 Jul 2023 20:11:41 +0200 Subject: [PATCH] CHANGELOG made from and to textboxes wider in the cut video screen added search engines in the search view removed filename template override if the user leaves it empty. Now it follows the yt-dlp default fixed app crashing when its tablet added drag handle to scrolling content added pause functionality made video player faster to load for youtube videos --- app/build.gradle | 2 +- .../com/deniscerri/ytdlnis/MainActivity.kt | 2 + .../ytdlnis/adapter/ActiveDownloadAdapter.kt | 66 +++-- .../adapter/ActiveDownloadMinifiedAdapter.kt | 48 +++- .../deniscerri/ytdlnis/adapter/HomeAdapter.kt | 5 +- .../ytdlnis/database/dao/DownloadDao.kt | 8 +- .../database/repository/DownloadRepository.kt | 2 +- .../database/viewmodel/CookieViewModel.kt | 7 +- .../database/viewmodel/DownloadViewModel.kt | 13 +- .../CancelDownloadNotificationReceiver.kt | 14 + .../com/deniscerri/ytdlnis/ui/HomeFragment.kt | 29 +- .../downloadcard/ResultCardDetailsDialog.kt | 32 ++- .../ui/downloads/ActiveDownloadsFragment.kt | 84 +++++- .../ui/downloads/DownloadQueueMainFragment.kt | 1 - .../more/settings/GeneralSettingsFragment.kt | 10 +- .../com/deniscerri/ytdlnis/util/FileUtil.kt | 86 +++++- .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 264 +++++++++--------- .../com/deniscerri/ytdlnis/util/ThemeUtil.kt | 4 +- .../com/deniscerri/ytdlnis/util/UiUtil.kt | 3 +- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 28 +- .../main/res/drawable/ic_refresh_white.xml | 5 + app/src/main/res/drawable/line.xml | 12 + app/src/main/res/drawable/line_drawable.xml | 9 + app/src/main/res/drawable/thumb.xml | 18 ++ app/src/main/res/drawable/thumb_drawable.xml | 8 + .../res/layout-sw600dp-land/fragment_home.xml | 31 +- .../main/res/layout-sw600dp/fragment_home.xml | 32 ++- .../res/layout-sw950dp-land/fragment_home.xml | 31 +- .../main/res/layout/active_download_card.xml | 24 +- .../layout/active_download_card_minified.xml | 105 +++---- app/src/main/res/layout/cut_video_sheet.xml | 4 +- .../layout/download_multiple_bottom_sheet.xml | 5 + app/src/main/res/layout/fragment_active.xml | 41 +++ .../fragment_generic_download_queue.xml | 5 + app/src/main/res/layout/fragment_history.xml | 7 +- app/src/main/res/layout/fragment_home.xml | 33 ++- app/src/main/res/layout/fragment_inqueue.xml | 6 + .../res/layout/header_navigation_drawer.xml | 1 + app/src/main/res/layout/history_card.xml | 2 + app/src/main/res/layout/result_card.xml | 67 ++--- .../main/res/layout/result_card_details.xml | 54 ++-- .../main/res/layout/result_card_shimmer.xml | 2 + .../main/res/layout/select_playlist_items.xml | 12 +- .../menu/cancelled_downloads_menu_context.xml | 2 +- app/src/main/res/values-ar/strings.xml | 3 +- app/src/main/res/values-az/strings.xml | 5 +- app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values-el/strings.xml | 3 +- app/src/main/res/values-es/strings.xml | 5 +- app/src/main/res/values-hi/strings.xml | 3 +- app/src/main/res/values-in/strings.xml | 8 +- app/src/main/res/values-ja/strings.xml | 3 +- app/src/main/res/values-ko/strings.xml | 3 +- app/src/main/res/values-lv/strings.xml | 3 +- app/src/main/res/values-nn/strings.xml | 3 +- app/src/main/res/values-pl/strings.xml | 3 +- app/src/main/res/values-pt-rBR/strings.xml | 8 +- app/src/main/res/values-pt/strings.xml | 3 +- app/src/main/res/values-ru/strings.xml | 5 +- app/src/main/res/values-sq-rAL/strings.xml | 5 +- app/src/main/res/values-tr-rTR/strings.xml | 3 +- app/src/main/res/values-uk/strings.xml | 5 +- app/src/main/res/values-zh-rTW/strings.xml | 3 +- app/src/main/res/values/colors.xml | 6 +- app/src/main/res/values/strings.xml | 4 +- .../main/res/xml/downloading_preferences.xml | 1 - app/src/main/res/xml/general_preferences.xml | 6 +- 67 files changed, 883 insertions(+), 435 deletions(-) create mode 100644 app/src/main/res/drawable/ic_refresh_white.xml create mode 100644 app/src/main/res/drawable/line.xml create mode 100644 app/src/main/res/drawable/line_drawable.xml create mode 100644 app/src/main/res/drawable/thumb.xml create mode 100644 app/src/main/res/drawable/thumb_drawable.xml create mode 100644 app/src/main/res/layout/fragment_active.xml diff --git a/app/build.gradle b/app/build.gradle index 67f82c37..ffa0a801 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ def properties = new Properties() def versionMajor = 1 def versionMinor = 6 def versionPatch = 3 -def versionBuild = 9 // bump for dogfood builds, public betas, etc. +def versionBuild = 10 // bump for dogfood builds, public betas, etc. def versionExt = "" if (versionBuild > 0){ diff --git a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt index 44a15157..7feb2a45 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/MainActivity.kt @@ -185,6 +185,8 @@ class MainActivity : BaseActivity() { } true } + + (navigationView as NavigationView).getHeaderView(0).findViewById(R.id.title).text = ThemeUtil.getStyledAppName(this) } cookieViewModel.updateCookiesFile() val intent = intent diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt index 8d79d471..69fde5c5 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadAdapter.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil @@ -17,6 +18,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton @@ -56,12 +58,13 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = getItem(position) val card = holder.cardView + card.tag = "${item!!.id}##card" val uiHandler = Handler(Looper.getMainLooper()) val thumbnail = card.findViewById(R.id.image_view) // THUMBNAIL ---------------------------------- if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ - val imageURL = item!!.thumb + val imageURL = item.thumb if (imageURL.isNotEmpty()) { uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } } else { @@ -74,7 +77,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: // PROGRESS BAR ---------------------------------------------------- val progressBar = card.findViewById(R.id.progress) - progressBar.tag = "${item!!.id}##progress" + progressBar.tag = "${item.id}##progress" progressBar.progress = 0 progressBar.isIndeterminate = true @@ -105,17 +108,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: val formatDetailsChip = card.findViewById(R.id.format_note) val formatDetailsText = StringBuilder(item.format.format_note.uppercase()) - val codecText = - if (item.format.encoding != "") { - item.format.encoding.uppercase() - }else if (item.format.vcodec != "none" && item.format.vcodec != ""){ - item.format.vcodec.uppercase() - } else { - item.format.acodec.uppercase() - } - if (codecText != "" && codecText != "none"){ - formatDetailsText.append(" \t •\t $codecText") - } + formatDetailsText.append(" \t •\t ${item.container.uppercase().ifEmpty { item.format.container.uppercase() }}") val fileSize = FileUtil.convertFileSize(item.format.filesize) if (fileSize != "?") formatDetailsText.append(" \t •\t $fileSize") @@ -131,19 +124,54 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: } - // CANCEL BUTTON ---------------------------------- - val cancelButton = card.findViewById(R.id.active_download_cancel) - if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) + // PAUSE BUTTON ---------------------------------- + val pauseButton = card.findViewById(R.id.active_download_pause) + if (pauseButton.hasOnClickListeners()) pauseButton.setOnClickListener(null) - cancelButton.setOnClickListener { - onItemClickListener.onCancelClick(item.id) + // CANCEL BUTTON ---------------------------------- + val cancelButton = card.findViewById(R.id.active_download_delete) + if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) + cancelButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)} + + if (item.status == DownloadRepository.Status.Paused.toString()){ + progressBar.isIndeterminate = false + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) + pauseButton.tag = ActiveDownloadAction.Resume + cancelButton.visibility = View.VISIBLE + }else{ + progressBar.isIndeterminate = true + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white) + cancelButton.visibility = View.GONE + pauseButton.tag = ActiveDownloadAction.Pause } + + pauseButton.setOnClickListener { + if (pauseButton.tag == ActiveDownloadAction.Pause){ + onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Pause) + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) + if (progressBar.progress == 0) progressBar.isIndeterminate = false + cancelButton.visibility = View.VISIBLE + pauseButton.tag = ActiveDownloadAction.Resume + }else{ + onItemClickListener.onPauseClick(item.id, ActiveDownloadAction.Resume) + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white) + progressBar.isIndeterminate = true + cancelButton.visibility = View.GONE + pauseButton.tag = ActiveDownloadAction.Pause + } + } + } interface OnItemClickListener { fun onCancelClick(itemID: Long) + fun onPauseClick(itemID: Long, action: ActiveDownloadAction) fun onOutputClick(item: DownloadItem) } + enum class ActiveDownloadAction { + Resume, Pause + } + companion object { private val DIFF_CALLBACK: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { @@ -152,7 +180,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity: } override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { - return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb + return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb && oldItem.status == newItem.status } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt index 842ebba6..86b8b5b0 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/ActiveDownloadMinifiedAdapter.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.DiffUtil @@ -17,6 +18,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.database.models.DownloadItem +import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.util.FileUtil import com.google.android.material.button.MaterialButton @@ -86,9 +88,6 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac } itemTitle.text = title.ifEmpty { item.url } - val duration = card.findViewById(R.id.duration) - duration.text = item.duration - val formatDetailsChip = card.findViewById(R.id.format_note) val formatDetailsText = StringBuilder(item.format.format_note.uppercase()) @@ -110,12 +109,41 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac formatDetailsChip.text = formatDetailsText - // CANCEL BUTTON ---------------------------------- - val cancelButton = card.findViewById(R.id.active_download_cancel) - if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) + // PAUSE BUTTON ---------------------------------- + val pauseButton = card.findViewById(R.id.active_download_pause) + if (pauseButton.hasOnClickListeners()) pauseButton.setOnClickListener(null) - cancelButton.setOnClickListener { - onItemClickListener.onCancelClick(item.id) + // CANCEL BUTTON ---------------------------------- + val cancelButton = card.findViewById(R.id.active_download_delete) + if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) + cancelButton.setOnClickListener {onItemClickListener.onCancelClick(item.id)} + + if (item.status == DownloadRepository.Status.Paused.toString()){ + progressBar.isIndeterminate = false + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) + pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume + cancelButton.visibility = View.VISIBLE + }else{ + progressBar.isIndeterminate = true + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white) + cancelButton.visibility = View.GONE + pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause + } + + pauseButton.setOnClickListener { + if (pauseButton.tag == ActiveDownloadAdapter.ActiveDownloadAction.Pause){ + onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Pause) + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_play_arrow_white) + if (progressBar.progress == 0) progressBar.isIndeterminate = false + cancelButton.visibility = View.VISIBLE + pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume + }else{ + onItemClickListener.onPauseClick(item.id, ActiveDownloadAdapter.ActiveDownloadAction.Resume) + pauseButton.icon = ContextCompat.getDrawable(activity, R.drawable.exomedia_ic_pause_white) + progressBar.isIndeterminate = true + cancelButton.visibility = View.GONE + pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause + } } card.setOnClickListener { @@ -124,9 +152,11 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac } interface OnItemClickListener { fun onCancelClick(itemID: Long) + fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction) fun onCardClick() } + companion object { private val DIFF_CALLBACK: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { @@ -135,7 +165,7 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac } override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { - return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb + return oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.thumb == newItem.thumb && oldItem.status == newItem.status } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt index 9abe7c85..02378614 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/adapter/HomeAdapter.kt @@ -90,13 +90,12 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity) // BUTTONS ---------------------------------- val videoURL = video.url - val buttonLayout = card.findViewById(R.id.download_button_layout) - val musicBtn = buttonLayout.findViewById(R.id.download_music) + val musicBtn = card.findViewById(R.id.download_music) musicBtn.tag = "$videoURL##audio" musicBtn.setTag(R.id.cancelDownload, "false") musicBtn.setOnClickListener { onItemClickListener.onButtonClick(videoURL, DownloadViewModel.Type.audio) } musicBtn.setOnLongClickListener{ onItemClickListener.onLongButtonClick(videoURL, DownloadViewModel.Type.audio); true} - val videoBtn = buttonLayout.findViewById(R.id.download_video) + val videoBtn = card.findViewById(R.id.download_video) videoBtn.tag = "$videoURL##video" videoBtn.setTag(R.id.cancelDownload, "false") videoBtn.setOnClickListener { onItemClickListener.onButtonClick(videoURL, DownloadViewModel.Type.video) } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt index ab7f765b..0dd77f13 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/DownloadDao.kt @@ -10,16 +10,16 @@ interface DownloadDao { @Query("SELECT * FROM downloads ORDER BY status") fun getAllDownloads() : Flow> - @Query("SELECT * FROM downloads WHERE status='Active'") + @Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'") fun getActiveDownloads() : Flow> - @Query("SELECT COUNT(*) FROM downloads WHERE status='Active'") + @Query("SELECT COUNT(*) FROM downloads WHERE status='Active' or status='Paused'") fun getActiveDownloadsCount() : Flow - @Query("SELECT * FROM downloads WHERE status='Active'") + @Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'") fun getActiveDownloadsList() : List - @Query("SELECT * FROM downloads WHERE status='Active' or status='Queued'") + @Query("SELECT * FROM downloads WHERE status='Active' or status='Queued' or status='Paused'") fun getActiveAndQueuedDownloadsList() : List @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt index 84e0ed80..17620bec 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/DownloadRepository.kt @@ -16,7 +16,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) { val savedDownloads : Flow> = downloadDao.getSavedDownloads() enum class Status { - Active, Queued, Error, Processing, Cancelled, Saved + Active, Paused, Queued, Error, Cancelled, Saved } suspend fun insert(item: DownloadItem) : Long { diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt index b4a07c5d..4d83af70 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope +import com.anggrayudi.storage.file.dataDirectory import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.models.CookieItem import com.deniscerri.ytdlnis.database.repository.CookieRepository @@ -72,11 +73,11 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a flush() } - var dbPath = "/data/data/com.deniscerri.ytdl/app_webview/Cookies" - if (!File(dbPath).exists()) dbPath = "/data/data/com.deniscerri.ytdl/app_webview/Default/Cookies" + val dbPath = File("/data/data/com.deniscerri.ytdl/").walkTopDown().find { it.name == "Cookies" } + ?: throw Exception("Cookies File not found!") SQLiteDatabase.openDatabase( - dbPath, null, OPEN_READONLY + dbPath.absolutePath, null, OPEN_READONLY ).run { val projection = arrayOf( CookieObject.HOST, 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 b1ac71e9..00499082 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 @@ -166,6 +166,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application else -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath()) } + val container = when(type){ + Type.audio -> sharedPreferences.getString("audio_format", "") + else -> sharedPreferences.getString("video_format", "") + } + val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet()) @@ -182,10 +187,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application resultItem.duration, type, getFormat(resultItem.formats, type), - sharedPreferences.getString("video_format", "Default")!!, + container!!, "", resultItem.formats, - downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null + downloadPath!!, resultItem.website, "", resultItem.playlistTitle, audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null ) } @@ -307,7 +312,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application container, "", ArrayList(), - path, historyItem.website, "", "", audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Processing.toString(), 0, null + path, historyItem.website, "", "", audioPreferences, videoPreferences, "", customFileNameTemplate!!, saveThumb, DownloadRepository.Status.Queued.toString(), 0, null ) } @@ -459,7 +464,7 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application var exists = false items.forEach { - it.status = DownloadRepository.Status.Queued.toString() + if (it.status != DownloadRepository.Status.Paused.toString()) it.status = DownloadRepository.Status.Queued.toString() if (activeAndQueuedDownloads.firstOrNull{d -> d.id = 0 d.status = DownloadRepository.Status.Queued.toString() diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt index 23ab5bed..2dc7b796 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/CancelDownloadNotificationReceiver.kt @@ -3,8 +3,14 @@ package com.deniscerri.ytdlnis.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import com.deniscerri.ytdlnis.database.DBManager +import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.util.NotificationUtil import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking class CancelDownloadNotificationReceiver : BroadcastReceiver() { override fun onReceive(c: Context, intent: Intent) { @@ -14,6 +20,14 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() { val notificationUtil = NotificationUtil(c) notificationUtil.cancelDownloadNotification(id) YoutubeDL.getInstance().destroyProcessById(id.toString()) + + val dbManager = DBManager.getInstance(c) + CoroutineScope(Dispatchers.IO).launch{ + val item = dbManager.downloadDao.getDownloadById(id.toLong()) + item.status = DownloadRepository.Status.Cancelled.toString() + dbManager.downloadDao.update(item) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt index 4cef7f45..d407f6ad 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/HomeFragment.kt @@ -5,14 +5,12 @@ import android.app.Activity import android.content.* import android.content.Context.CLIPBOARD_SERVICE import android.content.res.ColorStateList -import android.content.res.Resources.Theme import android.graphics.Color import android.os.Bundle import android.os.Handler import android.os.Looper import android.text.Editable import android.util.Log -import android.util.TypedValue import android.view.* import android.view.View.* import android.widget.* @@ -36,7 +34,6 @@ import com.deniscerri.ytdlnis.adapter.HomeAdapter import com.deniscerri.ytdlnis.database.models.ResultItem import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel -import com.deniscerri.ytdlnis.databinding.FragmentHomeBinding import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog @@ -137,7 +134,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene searchHistory = view.findViewById(R.id.search_history_scroll_view) searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout) - materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) + runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) } homeAdapter = HomeAdapter( @@ -257,6 +254,30 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene val queriesInitStartBtn = queriesConstraint.findViewById(R.id.init_search_query) val isRightToLeft = resources.getBoolean(R.bool.is_right_to_left) + val providersChipGroup = searchView!!.findViewById(R.id.providers) + val providers = resources.getStringArray(R.array.search_engines) + val providersValues = resources.getStringArray(R.array.search_engines_values).toMutableList() + val currentProvider = sharedPreferences?.getString("search_engine", "ytsearch") + + for(i in providersValues.indices){ + val provider = providers[i] + val providerValue = providersValues[i] + val tmp = layoutinflater!!.inflate(R.layout.filter_chip, providersChipGroup, false) as Chip + tmp.text = provider + tmp.id = i + + if (currentProvider == providerValue) tmp.isChecked = true + + tmp.setOnClickListener { + val editor = sharedPreferences?.edit() + editor?.putString("search_engine", providerValue) + editor?.apply() + + } + + providersChipGroup!!.addView(tmp) + } + infoUtil = InfoUtil(requireContext()) searchView!!.addTransitionListener { _, _, newState -> diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ResultCardDetailsDialog.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ResultCardDetailsDialog.kt index 9ee57143..cabf37f7 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ResultCardDetailsDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloadcard/ResultCardDetailsDialog.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.map import androidx.media3.common.MediaItem -import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.MediaSource import androidx.media3.exoplayer.source.MergingMediaSource @@ -35,6 +34,7 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.work.WorkManager import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.database.models.DownloadItem @@ -68,7 +68,6 @@ import java.util.* class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogFragment(), GenericDownloadAdapter.OnItemClickListener, ActiveDownloadMinifiedAdapter.OnItemClickListener { private lateinit var infoUtil: InfoUtil private lateinit var notificationUtil: NotificationUtil - private lateinit var player: ExoPlayer private lateinit var videoView: PlayerView private lateinit var downloadViewModel: DownloadViewModel private lateinit var resultViewModel: ResultViewModel @@ -232,9 +231,8 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF } - videoView = view.findViewById(R.id.video_view) + videoView = view.findViewById(R.id.video_view) val pause = view.findViewById(R.id.pause) - val frameLayout = view.findViewById(R.id.frame_layout) val player = VideoPlayerUtil.buildPlayer(requireContext()) videoView.player = player @@ -261,7 +259,7 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF } } - data.removeFirst() + if (data.size > 1) data.removeFirst() if (data.isEmpty()) throw Exception("No Streaming URL found!") if (data.size == 2){ @@ -280,7 +278,6 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF player.prepare() player.play() }catch (e: Exception){ - frameLayout.visibility = View.GONE e.printStackTrace() } } @@ -536,7 +533,24 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF override fun onCancelClick(itemID: Long) { cancelActiveDownload(itemID) } + override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction) { + val item = activeItems.find { it.id == itemID } ?: return + when(action){ + ActiveDownloadAdapter.ActiveDownloadAction.Pause -> { + cancelItem(itemID.toInt()) + item.status = DownloadRepository.Status.Paused.toString() + downloadViewModel.updateDownload(item) + } + ActiveDownloadAdapter.ActiveDownloadAction.Resume -> { + item.status = DownloadRepository.Status.Active.toString() + downloadViewModel.updateDownload(item) + runBlocking { + downloadViewModel.queueDownloads(listOf(item)) + } + } + } + } override fun onCardClick() { this.dismiss() findNavController().navigate( @@ -544,6 +558,12 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF ) } + private fun cancelItem(id: Int){ + YoutubeDL.getInstance().destroyProcessById(id.toString()) + WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) + notificationUtil.cancelDownloadNotification(id) + } + private fun cancelActiveDownload(itemID: Long){ val id = itemID.toInt() YoutubeDL.getInstance().destroyProcessById(id.toString()) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt index 7b346c67..b0509533 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/ActiveDownloadsFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.View.OnClickListener import android.view.ViewGroup import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController @@ -19,8 +20,11 @@ import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.repository.DownloadRepository import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.util.NotificationUtil +import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView import com.google.android.material.progressindicator.LinearProgressIndicator import com.yausername.youtubedl_android.YoutubeDL +import kotlinx.coroutines.runBlocking class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, OnClickListener { @@ -32,6 +36,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis lateinit var downloadItem: DownloadItem private lateinit var notificationUtil: NotificationUtil private lateinit var list: List + private lateinit var pauseResume: MaterialButton override fun onCreateView( @@ -39,7 +44,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis container: ViewGroup?, savedInstanceState: Bundle? ): View? { - fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) + fragmentView = inflater.inflate(R.layout.fragment_active, container, false) activity = getActivity() notificationUtil = NotificationUtil(requireContext()) downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] @@ -59,10 +64,62 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis activeRecyclerView = view.findViewById(R.id.download_recyclerview) activeRecyclerView.adapter = activeDownloads activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size)) + pauseResume = view.findViewById(R.id.pause_resume) + + pauseResume.setOnClickListener { + if (pauseResume.text == requireContext().getString(R.string.pause)){ + list.forEach { + cancelItem(it.id.toInt()) + it.status = DownloadRepository.Status.Paused.toString() + downloadViewModel.updateDownload(it) + + val card = view.findViewWithTag("${it.id}##card") + val progressBar = card.findViewById(R.id.progress) + val pauseButton = card.findViewById(R.id.active_download_pause) + val cancelButton = card.findViewById(R.id.active_download_delete) + + progressBar.isIndeterminate = false + pauseButton.icon = ContextCompat.getDrawable(requireContext(), R.drawable.exomedia_ic_play_arrow_white) + pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Resume + cancelButton.visibility = View.VISIBLE + } + }else{ + val toQueue = list.filter { it.status == DownloadRepository.Status.Paused.toString() } + toQueue.forEach { + it.status = DownloadRepository.Status.Active.toString() + downloadViewModel.updateDownload(it) + + val card = view.findViewWithTag("${it.id}##card") + val progressBar = card.findViewById(R.id.progress) + val pauseButton = card.findViewById(R.id.active_download_pause) + val cancelButton = card.findViewById(R.id.active_download_delete) + + pauseButton.icon = ContextCompat.getDrawable(requireContext(), R.drawable.exomedia_ic_pause_white) + progressBar.isIndeterminate = true + cancelButton.visibility = View.GONE + pauseButton.tag = ActiveDownloadAdapter.ActiveDownloadAction.Pause + } + runBlocking { + downloadViewModel.queueDownloads(toQueue) + } + } + } downloadViewModel.activeDownloads.observe(viewLifecycleOwner) { list = it activeDownloads.submitList(it) + + if (list.isNotEmpty()){ + pauseResume.visibility = View.VISIBLE + if (list.all { l -> l.status == DownloadRepository.Status.Paused.toString() }){ + pauseResume.text = requireContext().getString(R.string.resume) + }else{ + pauseResume.text = requireContext().getString(R.string.pause) + } + }else{ + pauseResume.visibility = View.GONE + } + it.forEach{item -> WorkManager.getInstance(requireContext()) .getWorkInfosForUniqueWorkLiveData(item.id.toString()) @@ -95,6 +152,25 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis cancelDownload(itemID) } + override fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction) { + val item = list.find { it.id == itemID } ?: return + when(action){ + ActiveDownloadAdapter.ActiveDownloadAction.Pause -> { + cancelItem(itemID.toInt()) + item.status = DownloadRepository.Status.Paused.toString() + downloadViewModel.updateDownload(item) + } + ActiveDownloadAdapter.ActiveDownloadAction.Resume -> { + item.status = DownloadRepository.Status.Active.toString() + downloadViewModel.updateDownload(item) + runBlocking { + downloadViewModel.queueDownloads(listOf(item)) + } + } + } + + } + override fun onOutputClick(item: DownloadItem) { if (item.logID != null) { val bundle = Bundle() @@ -106,12 +182,14 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis } } - private fun cancelDownload(itemID: Long){ - val id = itemID.toInt() + private fun cancelItem(id: Int){ YoutubeDL.getInstance().destroyProcessById(id.toString()) WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) notificationUtil.cancelDownloadNotification(id) + } + private fun cancelDownload(itemID: Long){ + cancelItem(itemID.toInt()) list.find { it.id == itemID }?.let { it.status = DownloadRepository.Status.Cancelled.toString() downloadViewModel.updateDownload(it) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt index 5c57f6ce..1b4eda45 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/downloads/DownloadQueueMainFragment.kt @@ -115,7 +115,6 @@ class DownloadQueueMainFragment : Fragment(){ when(m.itemId){ R.id.clear_queue -> { showDeleteDialog { - cancelAllDownloads() downloadViewModel.cancelQueued() } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt index bff7c213..33dd9c49 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/settings/GeneralSettingsFragment.kt @@ -2,8 +2,6 @@ package com.deniscerri.ytdlnis.ui.more.settings import android.content.ComponentName import android.content.Intent -import android.content.IntentFilter -import android.content.pm.ActivityInfo import android.content.pm.PackageManager import android.os.Build.VERSION import android.os.Build.VERSION_CODES @@ -18,10 +16,8 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.R -import com.deniscerri.ytdlnis.ui.more.TerminalActivity import com.deniscerri.ytdlnis.util.ThemeUtil import com.deniscerri.ytdlnis.util.UpdateUtil -import com.google.android.gms.common.wrappers.Wrappers.packageManager import java.util.Locale @@ -33,7 +29,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { private var accent: ListPreference? = null private var highContrast: SwitchPreferenceCompat? = null private var locale: ListPreference? = null - private var hideTerminal: SwitchPreferenceCompat? = null + private var showTerminalShareIcon: SwitchPreferenceCompat? = null private var updateUtil: UpdateUtil? = null private var activeDownloadCount = 0 @@ -54,7 +50,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { accent = findPreference("theme_accent") highContrast = findPreference("high_contrast") locale = findPreference("locale") - hideTerminal = findPreference("hide_terminal") + showTerminalShareIcon = findPreference("show_terminal") if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){ val values = resources.getStringArray(R.array.language_values) @@ -115,7 +111,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { true } - hideTerminal!!.onPreferenceChangeListener = + showTerminalShareIcon!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { pref: Preference?, _: Any -> val packageManager = requireContext().packageManager val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdlnis.terminalShareAlias") 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 97e4cb25..1437b169 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/FileUtil.kt @@ -2,9 +2,15 @@ package com.deniscerri.ytdlnis.util import android.content.Context import android.media.MediaScannerConnection +import android.net.Uri import android.os.Build import android.os.Environment +import android.os.storage.StorageManager +import android.os.storage.StorageVolume +import android.provider.DocumentsContract import android.util.Log +import android.webkit.MimeTypeMap +import androidx.core.net.toFile import androidx.core.net.toUri import androidx.documentfile.provider.DocumentFile import com.anggrayudi.storage.callback.FileCallback @@ -12,11 +18,12 @@ import com.anggrayudi.storage.callback.FolderCallback import com.anggrayudi.storage.file.copyFileTo import com.anggrayudi.storage.file.copyFolderTo import com.anggrayudi.storage.file.getAbsolutePath -import com.anggrayudi.storage.file.moveFileTo -import com.anggrayudi.storage.file.moveFolderTo +import com.anggrayudi.storage.file.getBasePath +import com.deniscerri.ytdlnis.App import com.deniscerri.ytdlnis.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.internal.closeQuietly import java.io.File import java.net.URLDecoder import java.nio.charset.StandardCharsets @@ -32,15 +39,10 @@ import kotlin.math.pow object FileUtil { fun deleteFile(path: String){ - val file = File(path) - if (file.exists()) { - file.delete() - } - } - - internal object Compare { - fun max(a: File, b: File): File { - return if (a.length() > b.length()) a else b + runCatching { + if (!File(path).delete()){ + DocumentFile.fromSingleUri(App.instance, Uri.parse(path))?.delete() + } } } @@ -100,7 +102,7 @@ object FileUtil { withContext(Dispatchers.IO){ curr.copyFolderTo(context, dst!!, skipEmptyFiles = false, callback = object : FolderCallback() { override fun onStart(folder: DocumentFile, totalFilesToCopy: Int, workerThread: Thread): Long { - return 1000 // update progress every 1 second + return 500 // update progress every half second } override fun onParentConflict(destinationFolder: DocumentFile, action: ParentFolderConflictAction, canMerge: Boolean) { @@ -120,11 +122,66 @@ object FileUtil { it.deleteRecursively() } + override fun onFailed(errorCode: ErrorCode) { + //if its usb? + runCatching { + it.walkTopDown().forEach { f -> + if (f.isDirectory) return@forEach + + val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(f.extension) ?: "*/*" + + val destUri = DocumentsContract.createDocument( + context.contentResolver, + dst.uri, + mimeType, + f.name + ) ?: return@runCatching + + val inputStream = f.inputStream() + val outputStream = + context.contentResolver.openOutputStream(destUri) ?: return@runCatching + inputStream.copyTo(outputStream) + inputStream.closeQuietly() + outputStream.closeQuietly() + + fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context)) + } + + it.deleteRecursively() + } + super.onFailed(errorCode) + } + }) } }else{ withContext(Dispatchers.IO){ curr.copyFileTo(context, dst!!, callback = object : FileCallback() { + override fun onFailed(errorCode: ErrorCode) { + //if its usb? + runCatching { + val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*" + + val destUri = DocumentsContract.createDocument( + context.contentResolver, + dst.uri, + mimeType, + it.name + ) ?: return@runCatching + + val inputStream = it.inputStream() + val outputStream = + context.contentResolver.openOutputStream(destUri) ?: return@runCatching + inputStream.copyTo(outputStream) + inputStream.closeQuietly() + outputStream.closeQuietly() + + fileList.add(DocumentFile.fromSingleUri(context, destUri)!!.getAbsolutePath(context)) + it.delete() + } + super.onFailed(errorCode) + } + override fun onStart(file: Any, workerThread: Thread): Long { return 1000 // update progress every 1 second } @@ -136,7 +193,7 @@ object FileUtil { override fun onCompleted(result: Any) { destFile = (result as DocumentFile) fileList.add(destFile.getAbsolutePath(context)) - it.deleteRecursively() + it.delete() super.onCompleted(result) } }) @@ -170,10 +227,9 @@ object FileUtil { } } private fun scanMedia(files: List, context: Context) : List { - try { val paths = files.sortedByDescending { File(it).length() } - MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) + runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) } return paths }catch (e: Exception){ e.printStackTrace() 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 e42b7b9f..b0922c91 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt @@ -8,6 +8,7 @@ import android.util.Log import android.widget.Toast import androidx.preference.PreferenceManager import com.deniscerri.ytdlnis.R +import com.deniscerri.ytdlnis.database.Converters import com.deniscerri.ytdlnis.database.models.ChapterItem import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.Format @@ -452,12 +453,12 @@ class InfoUtil(private val context: Context) { request.addOption("-R", "1") request.addOption("--socket-timeout", "5") val cookiesFile = File(context.cacheDir, "cookies.txt") - if (cookiesFile.exists()){ + if (cookiesFile.exists()) { request.addOption("--cookies", cookiesFile.absolutePath) } val proxy = sharedPreferences.getString("proxy", "") - if (proxy!!.isNotBlank()){ + if (proxy!!.isNotBlank()) { request.addOption("--proxy", proxy) } @@ -471,34 +472,7 @@ class InfoUtil(private val context: Context) { val json = results[0] val jsonArray = JSONArray(json) - val formats : ArrayList = ArrayList() - for (f in 0 until jsonArray.length()){ - val format = jsonArray.getJSONObject(f) - if (format.has("filesize")){ - if (format.get("filesize") == "None"){ - format.remove("filesize") - format.put("filesize", 0) - } - try{ - val size = format.get("filesize").toString().toFloat() - format.remove("filesize") - format.put("filesize", size) - }catch (ignored: Exception){} - } - val formatProper = Gson().fromJson(format.toString(), Format::class.java) - if (format.has("format_note")){ - if (!formatProper!!.format_note.contains("audio only", true)) { - formatProper.format_note = format.getString("format_note") - }else{ - formatProper.format_note = "${format.getString("format_note")} audio" - } - } - if (formatProper!!.format_note == "storyboard") continue - formatProper.container = format.getString("ext") - formats.add(formatProper) - } - - return formats + return parseYTDLFormats(jsonArray) } catch (e: Exception) { Looper.prepare().run { Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() @@ -540,35 +514,8 @@ class InfoUtil(private val context: Context) { YoutubeDL.getInstance().execute(request){ progress, _, line -> try{ - val formats = mutableListOf() val listOfStrings = JSONArray(line) - for (f in 0 until listOfStrings.length()){ - val format = listOfStrings.get(f) as JSONObject - try{ - if (format.getString("filesize") == "None") continue - }catch (e: Exception) { continue } - if (format.has("filesize")){ - if (format.get("filesize") == "None"){ - format.remove("filesize") - format.put("filesize", 0) - } - try{ - val size = format.get("filesize").toString().toFloat() - format.remove("filesize") - format.put("filesize", size) - }catch (ignored: Exception){} - } - val formatProper = Gson().fromJson(format.toString(), Format::class.java) - if ( !formatProper!!.format_note.contains("audio only", true)) { - formatProper.format_note = format.getString("format_note") - }else{ - formatProper.format_note = "${format.getString("format_note")} audio" - } - if (formatProper.format_note == "storyboard") continue - formatProper.container = format.getString("ext") - formats.add(formatProper) - } - progress(formats) + progress(parseYTDLFormats(listOfStrings)) }catch (e: Exception){ progress(emptyList()) } @@ -668,37 +615,7 @@ class InfoUtil(private val context: Context) { if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title") if(playlistTitle.equals(query)) playlistTitle = "" val formatsInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null - val formats : ArrayList = ArrayList() - if (formatsInJSON != null) { - for (f in 0 until formatsInJSON.length()){ - val format = formatsInJSON.getJSONObject(f) - if (format.has("filesize")){ - if (format.get("filesize") == "None"){ - format.remove("filesize") - format.put("filesize", 0) - } - try{ - val size = format.get("filesize").toString().toFloat() - format.remove("filesize") - format.put("filesize", size) - }catch (ignored: Exception){} - } - val formatProper = Gson().fromJson(format.toString(), Format::class.java) - if (formatProper.format_note == null) continue - if (format.has("format_note")){ - if (!formatProper!!.format_note.contains("audio only", true)) { - formatProper.format_note = format.getString("format_note") - }else{ - if (!formatProper.format_note.endsWith("audio", true)){ - formatProper.format_note = "${format.getString("format_note")} audio" - } - } - } - if (formatProper.format_note == "storyboard") continue - formatProper.container = format.getString("ext") - formats.add(formatProper) - } - } + val formats : ArrayList = parseYTDLFormats(formatsInJSON) val chaptersInJSON = if (jsonObject.has("chapters") && jsonObject.get("chapters") is JSONArray) jsonObject.getJSONArray("chapters") else null val listType: Type = object : TypeToken>() {}.type @@ -734,6 +651,47 @@ class InfoUtil(private val context: Context) { return items } + private fun parseYTDLFormats(formatsInJSON: JSONArray?) : ArrayList { + val formats = arrayListOf() + + if (formatsInJSON != null) { + for (f in 0 until formatsInJSON.length()){ + val format = formatsInJSON.getJSONObject(f) + if (format.has("filesize")){ + if (format.get("filesize") == "None"){ + format.remove("filesize") + if (format.has("filesize_approx") && format.get("filesize_approx") != "None"){ + format.put("filesize", format.getInt("filesize_approx")) + }else{ + format.put("filesize", 0) + } + } + try{ + val size = format.get("filesize").toString().toFloat() + format.remove("filesize") + format.put("filesize", size) + }catch (ignored: Exception){} + } + val formatProper = Gson().fromJson(format.toString(), Format::class.java) + if (formatProper.format_note == null) continue + if (format.has("format_note")){ + if (!formatProper!!.format_note.contains("audio only", true)) { + formatProper.format_note = format.getString("format_note") + }else{ + if (!formatProper.format_note.endsWith("audio", true)){ + formatProper.format_note = "${format.getString("format_note")} audio" + } + } + } + if (formatProper.format_note == "storyboard") continue + formatProper.container = format.getString("ext") + formats.add(formatProper) + } + } + + return formats + } + fun getMissingInfo(url: String): ResultItem? { try { val request = YoutubeDLRequest(url) @@ -885,33 +843,57 @@ class InfoUtil(private val context: Context) { fun getStreamingUrlAndChapters(url: String) : MutableList { try { - val request = YoutubeDLRequest(url) - request.addOption("--get-url") - request.addOption("--print", "%(chapters)s") - request.addOption("--skip-download") - request.addOption("-R", "1") - request.addOption("--socket-timeout", "5") + val p = Pattern.compile("(^(https?)://(www.)?(music.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)") + val m = p.matcher(url) - val cookiesFile = File(context.cacheDir, "cookies.txt") - if (cookiesFile.exists()){ - request.addOption("--cookies", cookiesFile.absolutePath) + if (m.find()){ + return getStreamingUrlAndChaptersFromPIPED(getIDFromYoutubeURL(url)) + }else{ + throw Exception() } + }catch (e: Exception) { + try { + val request = YoutubeDLRequest(url) + request.addOption("--get-url") + request.addOption("--print", "%(chapters)s") + request.addOption("--skip-download") + request.addOption("-R", "1") + request.addOption("--socket-timeout", "5") - val proxy = sharedPreferences.getString("proxy", "") - if (proxy!!.isNotBlank()){ - request.addOption("--proxy", proxy) - } + val cookiesFile = File(context.cacheDir, "cookies.txt") + if (cookiesFile.exists()){ + request.addOption("--cookies", cookiesFile.absolutePath) + } - val youtubeDLResponse = YoutubeDL.getInstance().execute(request) - val results: Array = try { - val lineSeparator = System.getProperty("line.separator") - youtubeDLResponse.out.split(lineSeparator!!).toTypedArray() + val proxy = sharedPreferences.getString("proxy", "") + if (proxy!!.isNotBlank()){ + request.addOption("--proxy", proxy) + } + + val youtubeDLResponse = YoutubeDL.getInstance().execute(request) + val results: Array = try { + val lineSeparator = System.getProperty("line.separator") + youtubeDLResponse.out.split(lineSeparator!!).toTypedArray() + } catch (e: Exception) { + arrayOf(youtubeDLResponse.out) + } + return results.filter { it!!.isNotEmpty() }.toMutableList() } catch (e: Exception) { - arrayOf(youtubeDLResponse.out) + return mutableListOf() } - return results.filter { it!!.isNotEmpty() }.toMutableList() - } catch (e: Exception) { - return mutableListOf() + } + } + + private fun getStreamingUrlAndChaptersFromPIPED(id: String) : MutableList { + val res = genericRequest("$pipedURL/streams/$id") + if (res.length() == 0) { + throw Exception() + }else{ + val item = createVideoFromPipedJSON(res, id) + if (item!!.urls.isBlank()) throw Exception() + val list = mutableListOf(Gson().toJson(item.chapters)) + list.addAll(item.urls.split(",")) + return list } } @@ -978,7 +960,6 @@ class InfoUtil(private val context: Context) { 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|${downloadItem.author})s - %(title)s" downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s") if (downloadItem.downloadSections.isNotBlank()){ @@ -1050,38 +1031,41 @@ class InfoUtil(private val context: Context) { } } - request.addOption("--embed-metadata") - - if (downloadItem.audioPreferences.embedThumb) { - request.addOption("--embed-thumbnail") - request.addOption("--convert-thumbnails", "jpg") - if (sharedPreferences.getBoolean("crop_thumbnail", true)){ - try { - val config = File(context.cacheDir.absolutePath + "/downloads/${downloadItem.id}/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt") - val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"" - config.writeText(configData) - request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1") - request.addOption("--config", config.absolutePath) - } catch (e: Exception) { - e.printStackTrace() - } - } - } - request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s") - - if (downloadItem.playlistTitle.isNotEmpty()) { - request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s") - request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s") - } else { - request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") - } - request.addOption("-P", tempFileDir.absolutePath) if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ request.addOption("--split-chapters") + request.addOption("-o", "chapter:%(section_title)s.%(ext)s") }else{ - request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s") + request.addOption("--embed-metadata") + + if (downloadItem.audioPreferences.embedThumb) { + request.addOption("--embed-thumbnail") + request.addOption("--convert-thumbnails", "jpg") + if (sharedPreferences.getBoolean("crop_thumbnail", true)){ + try { + val config = File(context.cacheDir.absolutePath + "/downloads/${downloadItem.id}/config" + downloadItem.title + "##" + downloadItem.format.format_id + ".txt") + val configData = "--ppa \"ffmpeg: -c:v mjpeg -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"" + config.writeText(configData) + request.addOption("--ppa", "ThumbnailsConvertor:-qmin 1 -q:v 1") + request.addOption("--config", config.absolutePath) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + request.addOption("--parse-metadata", "%(release_year,upload_date)s:%(meta_date)s") + + if (downloadItem.playlistTitle.isNotEmpty()) { + request.addOption("--parse-metadata", "%(album,playlist,title)s:%(meta_album)s") + request.addOption("--parse-metadata", "%(track_number,playlist_index)d:%(meta_track)s") + } else { + request.addOption("--parse-metadata", "%(album,title)s:%(meta_album)s") + } + + if (downloadItem.customFileNameTemplate.isNotBlank()){ + request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s") + } } } @@ -1149,7 +1133,9 @@ class InfoUtil(private val context: Context) { if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ request.addOption("--split-chapters") }else{ - request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s") + if (downloadItem.customFileNameTemplate.isNotBlank()){ + request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s") + } } } diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/ThemeUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/ThemeUtil.kt index 0b9fb06e..98764569 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/ThemeUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/ThemeUtil.kt @@ -117,8 +117,8 @@ object ThemeUtil { private fun getThemeColor(context: Context, colorCode: Int): Int { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val accent = sharedPreferences.getString("theme_accent", "blue") - return if (accent == "Default" || accent == "blue"){ - "c22727".toInt(16) + return if (accent == "blue"){ + "d43c3b".toInt(16) }else{ val value = TypedValue() context.theme.resolveAttribute(colorCode, value, true) diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt index 1013b565..36807ef5 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/util/UiUtil.kt @@ -17,6 +17,7 @@ import android.view.View import android.view.ViewGroup import android.view.Window import android.view.inputmethod.InputMethodManager +import android.webkit.MimeTypeMap import android.widget.Button import android.widget.EditText import android.widget.LinearLayout @@ -251,7 +252,7 @@ object UiUtil { fragmentContext.packageName + ".fileprovider", file ) - val mime = fragmentContext.contentResolver.getType(uri) + val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension) val i = Intent(Intent.ACTION_VIEW) i.setDataAndType(uri, mime) i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 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 3ea33b73..73cf4865 100644 --- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt +++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt @@ -60,7 +60,7 @@ class DownloadWorker( return Result.failure() } - if (downloadItem.status != DownloadRepository.Status.Queued.toString()) return Result.failure() + if (downloadItem.status != DownloadRepository.Status.Queued.toString() && downloadItem.status != DownloadRepository.Status.Paused.toString()) return Result.failure() val pendingIntent = NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_graph) @@ -134,7 +134,7 @@ class DownloadWorker( }.onSuccess { val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao) - CoroutineScope(Dispatchers.IO).launch { + runBlocking { var finalPaths : List? //move file from internal to set download directory setProgressAsync(workDataOf("progress" to 100, "output" to "Moving file to ${FileUtil.formatPath(downloadLocation)}", "id" to downloadItem.id, "log" to logDownloads)) @@ -174,30 +174,26 @@ class DownloadWorker( downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths, NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID ) - } - if (wasQuickDownloaded){ - runCatching { - setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false)) - runBlocking { - infoUtil.getFromYTDL(downloadItem.url).forEach { res -> - if (res != null) { - resultDao.insert(res) + if (wasQuickDownloaded){ + runCatching { + setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false)) + runBlocking { + infoUtil.getFromYTDL(downloadItem.url).forEach { res -> + if (res != null) { + resultDao.insert(res) + } } } } } - } - runBlocking { dao.delete(downloadItem.id) } + + }.onFailure { if (it is YoutubeDL.CanceledException) { - downloadItem.status = DownloadRepository.Status.Cancelled.toString() - runBlocking { - dao.update(downloadItem) - } return Result.failure( Data.Builder().putString("output", "Download has been cancelled!").build() ) diff --git a/app/src/main/res/drawable/ic_refresh_white.xml b/app/src/main/res/drawable/ic_refresh_white.xml new file mode 100644 index 00000000..b008a4e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/line.xml b/app/src/main/res/drawable/line.xml new file mode 100644 index 00000000..56ae20af --- /dev/null +++ b/app/src/main/res/drawable/line.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/line_drawable.xml b/app/src/main/res/drawable/line_drawable.xml new file mode 100644 index 00000000..4f79f470 --- /dev/null +++ b/app/src/main/res/drawable/line_drawable.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/thumb.xml b/app/src/main/res/drawable/thumb.xml new file mode 100644 index 00000000..80fd14fd --- /dev/null +++ b/app/src/main/res/drawable/thumb.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/thumb_drawable.xml b/app/src/main/res/drawable/thumb_drawable.xml new file mode 100644 index 00000000..162eddc4 --- /dev/null +++ b/app/src/main/res/drawable/thumb_drawable.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp-land/fragment_home.xml b/app/src/main/res/layout-sw600dp-land/fragment_home.xml index 3da6cc0c..b153d663 100644 --- a/app/src/main/res/layout-sw600dp-land/fragment_home.xml +++ b/app/src/main/res/layout-sw600dp-land/fragment_home.xml @@ -27,6 +27,28 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + @@ -152,14 +152,30 @@ + + - - - - + app:layout_constraintDimensionRatio="H,16:9" + app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" + android:adjustViewBounds="true" + app:layout_constraintEnd_toStartOf="@+id/download_item_data" + app:layout_constraintBottom_toBottomOf="parent" + > + + - + + + + + + + app:constraint_referenced_ids="download_item_data,active_download_delete" /> diff --git a/app/src/main/res/layout/cut_video_sheet.xml b/app/src/main/res/layout/cut_video_sheet.xml index bdbb7079..0bc59f1f 100644 --- a/app/src/main/res/layout/cut_video_sheet.xml +++ b/app/src/main/res/layout/cut_video_sheet.xml @@ -129,7 +129,7 @@ @@ -154,7 +154,7 @@ diff --git a/app/src/main/res/layout/fragment_active.xml b/app/src/main/res/layout/fragment_active.xml new file mode 100644 index 00000000..847bf322 --- /dev/null +++ b/app/src/main/res/layout/fragment_active.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_generic_download_queue.xml b/app/src/main/res/layout/fragment_generic_download_queue.xml index 4f074f04..7289cbad 100644 --- a/app/src/main/res/layout/fragment_generic_download_queue.xml +++ b/app/src/main/res/layout/fragment_generic_download_queue.xml @@ -4,6 +4,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="200dp" + app:fastScrollEnabled="true" + app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable" + app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollVerticalTrackDrawable="@drawable/line_drawable" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index 16039c3c..42eef4fc 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -112,8 +112,11 @@ android:orientation="vertical" android:layout_height="wrap_content" android:scrollbars="vertical" - android:layout_marginLeft="10dp" - android:layout_marginRight="10dp" + app:fastScrollEnabled="true" + app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable" + app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollVerticalTrackDrawable="@drawable/line_drawable" android:clipToPadding="false" android:paddingBottom="100dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index b4ce6e26..6f362b88 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -27,6 +27,28 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + diff --git a/app/src/main/res/layout/fragment_inqueue.xml b/app/src/main/res/layout/fragment_inqueue.xml index 7f5954f2..031bddf8 100644 --- a/app/src/main/res/layout/fragment_inqueue.xml +++ b/app/src/main/res/layout/fragment_inqueue.xml @@ -2,6 +2,7 @@ @@ -22,6 +23,11 @@ android:id="@+id/download_recyclerview" android:layout_width="match_parent" android:layout_height="wrap_content" + app:fastScrollEnabled="true" + app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable" + app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollVerticalTrackDrawable="@drawable/line_drawable" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" diff --git a/app/src/main/res/layout/header_navigation_drawer.xml b/app/src/main/res/layout/header_navigation_drawer.xml index d8cd1656..44b95273 100644 --- a/app/src/main/res/layout/header_navigation_drawer.xml +++ b/app/src/main/res/layout/header_navigation_drawer.xml @@ -24,6 +24,7 @@ app:layout_constraintTop_toTopOf="parent" /> @@ -53,7 +55,7 @@ android:layout_height="0dp" android:dividerPadding="5dp" android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@+id/download_button_layout" + app:layout_constraintBottom_toTopOf="@+id/download_music" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -62,15 +64,15 @@ android:id="@+id/result_title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="10dp" - android:maxLines="2" android:ellipsize="end" + android:maxLines="2" + android:paddingStart="10dp" android:paddingTop="10dp" android:paddingEnd="10dp" + android:scrollbars="none" android:shadowColor="@color/black" android:shadowDx="4" android:shadowDy="4" - android:scrollbars="none" android:shadowRadius="2" android:textColor="#FFF" android:textSize="17sp" @@ -80,15 +82,15 @@ android:id="@+id/author" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="bottom" - android:paddingStart="10dp" - android:maxLines="1" android:ellipsize="end" + android:gravity="bottom" + android:maxLines="1" + android:paddingStart="10dp" android:paddingEnd="10dp" + android:scrollbars="none" android:shadowColor="@color/black" android:shadowDx="4" android:shadowDy="4" - android:scrollbars="none" android:shadowRadius="1.5" android:textColor="#FFF" android:textSize="12sp" @@ -115,33 +117,32 @@ app:layout_constraintStart_toStartOf="parent" /> - + app:layout_constraintEnd_toStartOf="@+id/download_video" /> - - - - - + diff --git a/app/src/main/res/layout/result_card_details.xml b/app/src/main/res/layout/result_card_details.xml index 76950cb4..8c3ab04d 100644 --- a/app/src/main/res/layout/result_card_details.xml +++ b/app/src/main/res/layout/result_card_details.xml @@ -78,6 +78,7 @@ android:id="@+id/title" android:layout_width="0dp" android:layout_marginStart="20dp" + android:layout_marginEnd="10dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:ellipsize="end" @@ -85,7 +86,7 @@ android:singleLine="false" android:text="@string/app_name" android:textSize="17sp" - app:layout_constraintEnd_toStartOf="@+id/download_button_layout" + app:layout_constraintEnd_toStartOf="@+id/download_music" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -94,53 +95,46 @@ android:id="@+id/bottom_info" android:layout_width="0dp" android:layout_marginStart="20dp" + android:layout_marginEnd="10dp" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:ellipsize="end" android:singleLine="false" android:text="@string/app_name" android:textSize="12sp" - app:layout_constraintEnd_toStartOf="@+id/download_button_layout" + app:layout_constraintEnd_toStartOf="@+id/download_music" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> - + + - - - - - - + app:layout_constraintTop_toTopOf="parent" /> + app:constraint_referenced_ids="bottom_info,download_music" /> diff --git a/app/src/main/res/layout/select_playlist_items.xml b/app/src/main/res/layout/select_playlist_items.xml index 7ae673d1..4f7aca28 100644 --- a/app/src/main/res/layout/select_playlist_items.xml +++ b/app/src/main/res/layout/select_playlist_items.xml @@ -85,7 +85,6 @@ @@ -139,6 +136,11 @@ android:focusableInTouchMode="true" android:orientation="vertical" android:paddingBottom="80dp" + app:fastScrollEnabled="true" + app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable" + app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable" + app:fastScrollVerticalTrackDrawable="@drawable/line_drawable" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> diff --git a/app/src/main/res/menu/cancelled_downloads_menu_context.xml b/app/src/main/res/menu/cancelled_downloads_menu_context.xml index 4457b1ef..afbf3af9 100644 --- a/app/src/main/res/menu/cancelled_downloads_menu_context.xml +++ b/app/src/main/res/menu/cancelled_downloads_menu_context.xml @@ -12,7 +12,7 @@ حاجب شرائح الإعلانات الاحتفاظ بالملفات المؤقتة عمل نسخة احتياطية - إيقاف -\n + إيقاف استئناف يتم جلب بيانات التحميل عدم مسح ملفات الكاش بعد اكتمال التحميل diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index b4bca58f..a6919d89 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -236,8 +236,7 @@ Üstünlük Verilən Format ID Yükləmə artıq var Üstünlük Verilən Yer - Dayandır -\n + Dayandır Təkrar başlat Yükləmə Element Məlumatı Yenilənir Qalığı Saxla @@ -282,7 +281,7 @@ Titr Formatı \n Miniatürləri Gizlət - Sıxacı paylaşma menyusundan gizlət + Sıxacı paylaşma menyusundan gizlət Saxlanıldı Saxlanılanları Təmizlə Əlavə Əmrlər Əlavə Et diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7dbd9963..2ddca08c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -232,8 +232,7 @@ Schneller Download Daten nicht vorab abrufen Download bereits vorhanden - Pause -\n + Pause Sicherung Wiederherstellen Netzwerkfehler diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 08f1dc05..a2d6437f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -221,8 +221,7 @@ Λήψη Τώρα Η λήψη υπάρχει ήδη Προτιμώμενες Τοπικές Ρυθμίσεις - Παύση -\n + Παύση Συνέχεια Ανώνυμη Λειτουργία SponsorBlock diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3f451fe0..df5ae4eb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -236,8 +236,7 @@ Descargas terminadas Notificación de descargas finalizadas o defectuosas Ubicación preferida - Pausa -\n + Pausa Resumen Actualizando los datos de los elementos de la descarga Conservar la caché @@ -285,7 +284,7 @@ \n Ocultar las miniaturas Guardado - Ocultar Terminal del menú compartir + Ocultar Terminal del menú compartir ¿Guardar la descarga para más tarde\? Borrar lo Guardado \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index d61681d4..a2a13d39 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -153,8 +153,7 @@ पसंदीदा प्रारूप आईडी तुरंत डाउनलोड करें पसंदीदा लोकेल - रोको -\n + रोको चालू करो कैचे रखें डाउनलोड समाप्त होने के बाद अस्थायी फ़ाइलें न हटाएं diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index e97ed1ca..f54fa300 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -235,8 +235,7 @@ Preferensi ID Format Unduh Sekarang Hilangkan Audio - Jeda -\n + Jeda Memutakhirkan Data Butir Unduhan Preferensi Wilayah Lanjutkan @@ -278,9 +277,8 @@ Urutan Format Paksakan Keyframe saat Pemotongan Proses yang lebih lambat, tetapi pemotongannya lebih akurat - Sembunyikan Terminal dari menu berbagi - Format Takarir (Subtitle) -\n + Sembunyikan Terminal dari menu berbagi + Format Takarir (Subtitle) Sembunyikan Keluku (Thumbnail) Bersihkan yang Disimpan Disimpan diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index df19b266..a3abb1f7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -238,8 +238,7 @@ 事前にデータを取得しません 音声を削除 ダウンロードに既に存在します - 一時停止 -\n + 一時停止 再開 キャッシュを保持 ダウンロードの完了後に一時ファイルを削除しないようにします diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2be3995c..3c963425 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -241,8 +241,7 @@ 시스템 빨간색 퓨어 블랙 / 화이트 테마 - 일시정지 -\n + 일시정지 머터리얼 녹색 파란색 diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 0ed814c1..67461ea0 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -228,8 +228,7 @@ Vēlamā Formāta ID Lejupielādēt Tagad Lejupielāde jau pastāv - Pauze -\n + Pauze Notiek Lejupielādes Vienuma Datu Atjaunināšana Paturēt Kešatmiņu Neizdzēst pagaidu failus pēc lejupielādes pabeigšanas diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index ab910131..f62448c4 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -1,7 +1,6 @@ - Stans -\n + Stans Behald mellomgøymet Ikkje slett mellombelse filer etter at hentinga er ferdig Nettverksfeil diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 93d4ffe7..0c1b5f90 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -204,8 +204,7 @@ Pobierz teraz Pobieranie już istnieje Preferowane ustawienia regionalne - Pauza -\n + Pauza Aktualizowanie danych elementu do pobrania Nie usuwaj plików tymczasowych po zakończeniu pobierania Przywróć diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 400e15cb..bc1cf03b 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -215,8 +215,7 @@ O download já existe Manter Cache Atualizando os dados do item de download - Pausar -\n + Pausar Local preferido Continuar Erro de rede @@ -278,10 +277,9 @@ Bloqueio de Patrocinadores Forçar quadros-chave nos cortes Processo lento, mas cortes mais precisos - Formato da Legenda -\n + Formato da Legenda Ocultar Miniaturas - Ocultar Terminal do menu de compartilhamento + Ocultar Terminal do menu de compartilhamento Salvo Limpar Salvo Salvar download para mais tarde\? diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d9bd9c26..7f0cb661 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -168,8 +168,7 @@ Descarregar agora Descarga já existente Idioma preferido - Pausa -\n + Pausa Retomar Manter em cache Backup diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b20fc90e..29711452 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -239,8 +239,7 @@ Чтобы использовать эту опцию, все элементы должны быть одного типа Восстановление приложения завершено. Были восстановлены следующие объекты: Загрузка уже существует - Пауза -\n + Пауза Предпочтительный регион Видеорекомендации Не удалось разобрать файл @@ -284,7 +283,7 @@ Формат субтиров \n Скрыть миниатюры видео - Скрыть терминал из меню \"Поделиться\" + Скрыть терминал из меню \"Поделиться\" Сохраненные Очистить сохраненные Сохранить загрузку для возврата к ней позднее\? diff --git a/app/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml index dfda8653..ee403b15 100644 --- a/app/src/main/res/values-sq-rAL/strings.xml +++ b/app/src/main/res/values-sq-rAL/strings.xml @@ -241,8 +241,7 @@ Mbaj Memoriken Kashe Mos fshi skedarët e përkohshëm pasi shkarkimi ka mbaruar Rikthe - Pauzë -\n + Pauzë Duke Përditësuar Informacionet e Objektit Ruaj Zgjidh kategoritë për ruajtje @@ -281,7 +280,7 @@ Proçes më i ngadaltë, por prerje më të sakta Formati i Titrave \n - Fshihe Terminalin nga fleta e shpërndarjes + Fshihe Terminalin nga fleta e shpërndarjes Të ruajtura Pastro të ruajturat Ruaj Shkarkimin për më vonë\? diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index cf85565b..15ca9c99 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -240,8 +240,7 @@ Şimdi İndir Sesi Kaldır Tercih Edilen Konum - Duraklat -\n + Duraklat Devam et İndirilen Öğe Verilerini Güncelleme Önbelleği Sakla diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5f9adbc8..360a8d15 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -236,8 +236,7 @@ Завантажити зараз Завантаження вже існує Бажане розташування - Пауза -\n + Пауза Продовжити Оновлення даних завантажуваного елемента Зберегти кеш @@ -282,7 +281,7 @@ Поточна команда Додайте свої додаткові команди перед завантаженням аудіо/відео Приховати мініатюри відео - Приховати термінал з меню \"Поділитися\" + Приховати термінал з меню \"Поділитися\" Збережені Очистити збережені Формат субтитрів diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6172b8e8..a9c7eee6 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -122,8 +122,7 @@ 立即下載 已存在下載 首選地區設定 - 暫停 -\n + 暫停 繼續 正在更新下載項目資料 保留快取 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f92701b1..3c960c2f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,7 +5,7 @@ #323232 #A6A6A6 #FFFFFF - #d43c3c + #db2e2e #0058CB @@ -320,9 +320,9 @@ #93000A #690005 #FFDAD6 - #1F1B16 + #1b1b1b #EBE1D9 - #1F1B16 + #1b1b1b #EBE1D9 #504539 #D4C4B5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1185b7cf..4ac0668e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -244,7 +244,7 @@ Download Now Download already exists Preferred Locale - Pause\n + Pause Resume Updating Download Item Data Keep Cache @@ -290,7 +290,7 @@ Current Command Subtitle Format\n Hide Thumbnails - Hide Terminal from share menu + Show Terminal in share menu Saved Clear Saved Save Download for Later? diff --git a/app/src/main/res/xml/downloading_preferences.xml b/app/src/main/res/xml/downloading_preferences.xml index 3386ca0c..87d3d64a 100644 --- a/app/src/main/res/xml/downloading_preferences.xml +++ b/app/src/main/res/xml/downloading_preferences.xml @@ -44,7 +44,6 @@ app:title="@string/socks5_proxy" /> + android:key="show_terminal" + app:title="@string/show_terminal" /> \ No newline at end of file