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