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
This commit is contained in:
deniscerri 2023-07-22 20:11:41 +02:00
parent 0cacff6e9f
commit 6dc25bab9a
No known key found for this signature in database
GPG key ID: 95C43D517D830350
67 changed files with 883 additions and 435 deletions

View file

@ -10,7 +10,7 @@ def properties = new Properties()
def versionMajor = 1 def versionMajor = 1
def versionMinor = 6 def versionMinor = 6
def versionPatch = 3 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 = "" def versionExt = ""
if (versionBuild > 0){ if (versionBuild > 0){

View file

@ -185,6 +185,8 @@ class MainActivity : BaseActivity() {
} }
true true
} }
(navigationView as NavigationView).getHeaderView(0).findViewById<TextView>(R.id.title).text = ThemeUtil.getStyledAppName(this)
} }
cookieViewModel.updateCookiesFile() cookieViewModel.updateCookiesFile()
val intent = intent val intent = intent

View file

@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -17,6 +18,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -56,12 +58,13 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
val card = holder.cardView val card = holder.cardView
card.tag = "${item!!.id}##card"
val uiHandler = Handler(Looper.getMainLooper()) val uiHandler = Handler(Looper.getMainLooper())
val thumbnail = card.findViewById<ImageView>(R.id.image_view) val thumbnail = card.findViewById<ImageView>(R.id.image_view)
// THUMBNAIL ---------------------------------- // THUMBNAIL ----------------------------------
if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){ if (!sharedPreferences.getStringSet("hide_thumbnails", emptySet())!!.contains("queue")){
val imageURL = item!!.thumb val imageURL = item.thumb
if (imageURL.isNotEmpty()) { if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) } uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else { } else {
@ -74,7 +77,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
// PROGRESS BAR ---------------------------------------------------- // PROGRESS BAR ----------------------------------------------------
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress) val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
progressBar.tag = "${item!!.id}##progress" progressBar.tag = "${item.id}##progress"
progressBar.progress = 0 progressBar.progress = 0
progressBar.isIndeterminate = true progressBar.isIndeterminate = true
@ -105,17 +108,7 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val formatDetailsChip = card.findViewById<Chip>(R.id.format_note) val formatDetailsChip = card.findViewById<Chip>(R.id.format_note)
val formatDetailsText = StringBuilder(item.format.format_note.uppercase()) val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
val codecText = formatDetailsText.append(" \t\t ${item.container.uppercase().ifEmpty { item.format.container.uppercase() }}")
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")
}
val fileSize = FileUtil.convertFileSize(item.format.filesize) val fileSize = FileUtil.convertFileSize(item.format.filesize)
if (fileSize != "?") formatDetailsText.append(" \t\t $fileSize") if (fileSize != "?") formatDetailsText.append(" \t\t $fileSize")
@ -131,19 +124,54 @@ class ActiveDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
} }
// CANCEL BUTTON ---------------------------------- // PAUSE BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_cancel) val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) if (pauseButton.hasOnClickListeners()) pauseButton.setOnClickListener(null)
cancelButton.setOnClickListener { // CANCEL BUTTON ----------------------------------
onItemClickListener.onCancelClick(item.id) val cancelButton = card.findViewById<MaterialButton>(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 { interface OnItemClickListener {
fun onCancelClick(itemID: Long) fun onCancelClick(itemID: Long)
fun onPauseClick(itemID: Long, action: ActiveDownloadAction)
fun onOutputClick(item: DownloadItem) fun onOutputClick(item: DownloadItem)
} }
enum class ActiveDownloadAction {
Resume, Pause
}
companion object { companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() { private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { 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 { 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
} }
} }
} }

View file

@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -17,6 +18,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.FileUtil import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -86,9 +88,6 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
} }
itemTitle.text = title.ifEmpty { item.url } itemTitle.text = title.ifEmpty { item.url }
val duration = card.findViewById<TextView>(R.id.duration)
duration.text = item.duration
val formatDetailsChip = card.findViewById<TextView>(R.id.format_note) val formatDetailsChip = card.findViewById<TextView>(R.id.format_note)
val formatDetailsText = StringBuilder(item.format.format_note.uppercase()) val formatDetailsText = StringBuilder(item.format.format_note.uppercase())
@ -110,12 +109,41 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
formatDetailsChip.text = formatDetailsText formatDetailsChip.text = formatDetailsText
// CANCEL BUTTON ---------------------------------- // PAUSE BUTTON ----------------------------------
val cancelButton = card.findViewById<MaterialButton>(R.id.active_download_cancel) val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
if (cancelButton.hasOnClickListeners()) cancelButton.setOnClickListener(null) if (pauseButton.hasOnClickListeners()) pauseButton.setOnClickListener(null)
cancelButton.setOnClickListener { // CANCEL BUTTON ----------------------------------
onItemClickListener.onCancelClick(item.id) val cancelButton = card.findViewById<MaterialButton>(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 { card.setOnClickListener {
@ -124,9 +152,11 @@ class ActiveDownloadMinifiedAdapter(onItemClickListener: OnItemClickListener, ac
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onCancelClick(itemID: Long) fun onCancelClick(itemID: Long)
fun onPauseClick(itemID: Long, action: ActiveDownloadAdapter.ActiveDownloadAction)
fun onCardClick() fun onCardClick()
} }
companion object { companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() { private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
override fun areItemsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { 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 { 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
} }
} }
} }

View file

@ -90,13 +90,12 @@ class HomeAdapter(onItemClickListener: OnItemClickListener, activity: Activity)
// BUTTONS ---------------------------------- // BUTTONS ----------------------------------
val videoURL = video.url val videoURL = video.url
val buttonLayout = card.findViewById<LinearLayout>(R.id.download_button_layout) val musicBtn = card.findViewById<MaterialButton>(R.id.download_music)
val musicBtn = buttonLayout.findViewById<MaterialButton>(R.id.download_music)
musicBtn.tag = "$videoURL##audio" musicBtn.tag = "$videoURL##audio"
musicBtn.setTag(R.id.cancelDownload, "false") musicBtn.setTag(R.id.cancelDownload, "false")
musicBtn.setOnClickListener { onItemClickListener.onButtonClick(videoURL, DownloadViewModel.Type.audio) } musicBtn.setOnClickListener { onItemClickListener.onButtonClick(videoURL, DownloadViewModel.Type.audio) }
musicBtn.setOnLongClickListener{ onItemClickListener.onLongButtonClick(videoURL, DownloadViewModel.Type.audio); true} musicBtn.setOnLongClickListener{ onItemClickListener.onLongButtonClick(videoURL, DownloadViewModel.Type.audio); true}
val videoBtn = buttonLayout.findViewById<MaterialButton>(R.id.download_video) val videoBtn = card.findViewById<MaterialButton>(R.id.download_video)
videoBtn.tag = "$videoURL##video" videoBtn.tag = "$videoURL##video"
videoBtn.setTag(R.id.cancelDownload, "false") videoBtn.setTag(R.id.cancelDownload, "false")
videoBtn.setOnClickListener { onItemClickListener.onButtonClick(videoURL, DownloadViewModel.Type.video) } videoBtn.setOnClickListener { onItemClickListener.onButtonClick(videoURL, DownloadViewModel.Type.video) }

View file

@ -10,16 +10,16 @@ interface DownloadDao {
@Query("SELECT * FROM downloads ORDER BY status") @Query("SELECT * FROM downloads ORDER BY status")
fun getAllDownloads() : Flow<List<DownloadItem>> fun getAllDownloads() : Flow<List<DownloadItem>>
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
fun getActiveDownloads() : Flow<List<DownloadItem>> fun getActiveDownloads() : Flow<List<DownloadItem>>
@Query("SELECT COUNT(*) FROM downloads WHERE status='Active'") @Query("SELECT COUNT(*) FROM downloads WHERE status='Active' or status='Paused'")
fun getActiveDownloadsCount() : Flow<Int> fun getActiveDownloadsCount() : Flow<Int>
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active' or status='Paused'")
fun getActiveDownloadsList() : List<DownloadItem> fun getActiveDownloadsList() : List<DownloadItem>
@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<DownloadItem> fun getActiveAndQueuedDownloadsList() : List<DownloadItem>
@Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id") @Query("SELECT * FROM downloads WHERE status='Queued' ORDER BY downloadStartTime, id")

View file

@ -16,7 +16,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
val savedDownloads : Flow<List<DownloadItem>> = downloadDao.getSavedDownloads() val savedDownloads : Flow<List<DownloadItem>> = downloadDao.getSavedDownloads()
enum class Status { enum class Status {
Active, Queued, Error, Processing, Cancelled, Saved Active, Paused, Queued, Error, Cancelled, Saved
} }
suspend fun insert(item: DownloadItem) : Long { suspend fun insert(item: DownloadItem) : Long {

View file

@ -12,6 +12,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.anggrayudi.storage.file.dataDirectory
import com.deniscerri.ytdlnis.database.DBManager import com.deniscerri.ytdlnis.database.DBManager
import com.deniscerri.ytdlnis.database.models.CookieItem import com.deniscerri.ytdlnis.database.models.CookieItem
import com.deniscerri.ytdlnis.database.repository.CookieRepository import com.deniscerri.ytdlnis.database.repository.CookieRepository
@ -72,11 +73,11 @@ class CookieViewModel(private val application: Application) : AndroidViewModel(a
flush() flush()
} }
var dbPath = "/data/data/com.deniscerri.ytdl/app_webview/Cookies" val dbPath = File("/data/data/com.deniscerri.ytdl/").walkTopDown().find { it.name == "Cookies" }
if (!File(dbPath).exists()) dbPath = "/data/data/com.deniscerri.ytdl/app_webview/Default/Cookies" ?: throw Exception("Cookies File not found!")
SQLiteDatabase.openDatabase( SQLiteDatabase.openDatabase(
dbPath, null, OPEN_READONLY dbPath.absolutePath, null, OPEN_READONLY
).run { ).run {
val projection = arrayOf( val projection = arrayOf(
CookieObject.HOST, CookieObject.HOST,

View file

@ -166,6 +166,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
else -> sharedPreferences.getString("command_path", FileUtil.getDefaultCommandPath()) 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()) val sponsorblock = sharedPreferences.getStringSet("sponsorblock_filters", emptySet())
@ -182,10 +187,10 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
resultItem.duration, resultItem.duration,
type, type,
getFormat(resultItem.formats, type), getFormat(resultItem.formats, type),
sharedPreferences.getString("video_format", "Default")!!, container!!,
"", "",
resultItem.formats, 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, container,
"", "",
ArrayList(), 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 var exists = false
items.forEach { 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 -> if (activeAndQueuedDownloads.firstOrNull{d ->
d.id = 0 d.id = 0
d.status = DownloadRepository.Status.Queued.toString() d.status = DownloadRepository.Status.Queued.toString()

View file

@ -3,8 +3,14 @@ package com.deniscerri.ytdlnis.receiver
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent 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.deniscerri.ytdlnis.util.NotificationUtil
import com.yausername.youtubedl_android.YoutubeDL 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() { class CancelDownloadNotificationReceiver : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) { override fun onReceive(c: Context, intent: Intent) {
@ -14,6 +20,14 @@ class CancelDownloadNotificationReceiver : BroadcastReceiver() {
val notificationUtil = NotificationUtil(c) val notificationUtil = NotificationUtil(c)
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
YoutubeDL.getInstance().destroyProcessById(id.toString()) 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)
}
} }
} }
} }

View file

@ -5,14 +5,12 @@ import android.app.Activity
import android.content.* import android.content.*
import android.content.Context.CLIPBOARD_SERVICE import android.content.Context.CLIPBOARD_SERVICE
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.Resources.Theme
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.text.Editable import android.text.Editable
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.* import android.view.*
import android.view.View.* import android.view.View.*
import android.widget.* 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.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel 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.DownloadBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog import com.deniscerri.ytdlnis.ui.downloadcard.DownloadMultipleBottomSheetDialog
import com.deniscerri.ytdlnis.ui.downloadcard.ResultCardDetailsDialog 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) searchHistory = view.findViewById(R.id.search_history_scroll_view)
searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout) searchHistoryLinearLayout = view.findViewById(R.id.search_history_linear_layout)
materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) runCatching { materialToolbar!!.title = ThemeUtil.getStyledAppName(requireContext()) }
homeAdapter = homeAdapter =
HomeAdapter( HomeAdapter(
@ -257,6 +254,30 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, OnClickListene
val queriesInitStartBtn = queriesConstraint.findViewById<MaterialButton>(R.id.init_search_query) val queriesInitStartBtn = queriesConstraint.findViewById<MaterialButton>(R.id.init_search_query)
val isRightToLeft = resources.getBoolean(R.bool.is_right_to_left) val isRightToLeft = resources.getBoolean(R.bool.is_right_to_left)
val providersChipGroup = searchView!!.findViewById<ChipGroup>(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()) infoUtil = InfoUtil(requireContext())
searchView!!.addTransitionListener { _, _, newState -> searchView!!.addTransitionListener { _, _, newState ->

View file

@ -23,7 +23,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.source.MediaSource import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.MergingMediaSource import androidx.media3.exoplayer.source.MergingMediaSource
@ -35,6 +34,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager import androidx.work.WorkManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ActiveDownloadAdapter
import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter import com.deniscerri.ytdlnis.adapter.ActiveDownloadMinifiedAdapter
import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter import com.deniscerri.ytdlnis.adapter.GenericDownloadAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem 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 { class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogFragment(), GenericDownloadAdapter.OnItemClickListener, ActiveDownloadMinifiedAdapter.OnItemClickListener {
private lateinit var infoUtil: InfoUtil private lateinit var infoUtil: InfoUtil
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var player: ExoPlayer
private lateinit var videoView: PlayerView private lateinit var videoView: PlayerView
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
@ -232,9 +231,8 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
} }
videoView = view.findViewById<PlayerView>(R.id.video_view) videoView = view.findViewById(R.id.video_view)
val pause = view.findViewById<MaterialButton>(R.id.pause) val pause = view.findViewById<MaterialButton>(R.id.pause)
val frameLayout = view.findViewById<FrameLayout>(R.id.frame_layout)
val player = VideoPlayerUtil.buildPlayer(requireContext()) val player = VideoPlayerUtil.buildPlayer(requireContext())
videoView.player = player 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.isEmpty()) throw Exception("No Streaming URL found!")
if (data.size == 2){ if (data.size == 2){
@ -280,7 +278,6 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
player.prepare() player.prepare()
player.play() player.play()
}catch (e: Exception){ }catch (e: Exception){
frameLayout.visibility = View.GONE
e.printStackTrace() e.printStackTrace()
} }
} }
@ -536,7 +533,24 @@ class ResultCardDetailsDialog(private val item: ResultItem) : BottomSheetDialogF
override fun onCancelClick(itemID: Long) { override fun onCancelClick(itemID: Long) {
cancelActiveDownload(itemID) 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() { override fun onCardClick() {
this.dismiss() this.dismiss()
findNavController().navigate( 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){ private fun cancelActiveDownload(itemID: Long){
val id = itemID.toInt() val id = itemID.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())

View file

@ -7,6 +7,7 @@ import android.view.View
import android.view.View.OnClickListener import android.view.View.OnClickListener
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController 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.repository.DownloadRepository
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.util.NotificationUtil 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.google.android.material.progressindicator.LinearProgressIndicator
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
import kotlinx.coroutines.runBlocking
class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, OnClickListener { class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickListener, OnClickListener {
@ -32,6 +36,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
lateinit var downloadItem: DownloadItem lateinit var downloadItem: DownloadItem
private lateinit var notificationUtil: NotificationUtil private lateinit var notificationUtil: NotificationUtil
private lateinit var list: List<DownloadItem> private lateinit var list: List<DownloadItem>
private lateinit var pauseResume: MaterialButton
override fun onCreateView( override fun onCreateView(
@ -39,7 +44,7 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
fragmentView = inflater.inflate(R.layout.fragment_generic_download_queue, container, false) fragmentView = inflater.inflate(R.layout.fragment_active, container, false)
activity = getActivity() activity = getActivity()
notificationUtil = NotificationUtil(requireContext()) notificationUtil = NotificationUtil(requireContext())
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
@ -59,10 +64,62 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
activeRecyclerView = view.findViewById(R.id.download_recyclerview) activeRecyclerView = view.findViewById(R.id.download_recyclerview)
activeRecyclerView.adapter = activeDownloads activeRecyclerView.adapter = activeDownloads
activeRecyclerView.layoutManager = GridLayoutManager(context, resources.getInteger(R.integer.grid_size)) 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<MaterialCardView>("${it.id}##card")
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
val cancelButton = card.findViewById<MaterialButton>(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<MaterialCardView>("${it.id}##card")
val progressBar = card.findViewById<LinearProgressIndicator>(R.id.progress)
val pauseButton = card.findViewById<MaterialButton>(R.id.active_download_pause)
val cancelButton = card.findViewById<MaterialButton>(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) { downloadViewModel.activeDownloads.observe(viewLifecycleOwner) {
list = it list = it
activeDownloads.submitList(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 -> it.forEach{item ->
WorkManager.getInstance(requireContext()) WorkManager.getInstance(requireContext())
.getWorkInfosForUniqueWorkLiveData(item.id.toString()) .getWorkInfosForUniqueWorkLiveData(item.id.toString())
@ -95,6 +152,25 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
cancelDownload(itemID) 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) { override fun onOutputClick(item: DownloadItem) {
if (item.logID != null) { if (item.logID != null) {
val bundle = Bundle() val bundle = Bundle()
@ -106,12 +182,14 @@ class ActiveDownloadsFragment : Fragment(), ActiveDownloadAdapter.OnItemClickLis
} }
} }
private fun cancelDownload(itemID: Long){ private fun cancelItem(id: Int){
val id = itemID.toInt()
YoutubeDL.getInstance().destroyProcessById(id.toString()) YoutubeDL.getInstance().destroyProcessById(id.toString())
WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString()) WorkManager.getInstance(requireContext()).cancelUniqueWork(id.toString())
notificationUtil.cancelDownloadNotification(id) notificationUtil.cancelDownloadNotification(id)
}
private fun cancelDownload(itemID: Long){
cancelItem(itemID.toInt())
list.find { it.id == itemID }?.let { list.find { it.id == itemID }?.let {
it.status = DownloadRepository.Status.Cancelled.toString() it.status = DownloadRepository.Status.Cancelled.toString()
downloadViewModel.updateDownload(it) downloadViewModel.updateDownload(it)

View file

@ -115,7 +115,6 @@ class DownloadQueueMainFragment : Fragment(){
when(m.itemId){ when(m.itemId){
R.id.clear_queue -> { R.id.clear_queue -> {
showDeleteDialog { showDeleteDialog {
cancelAllDownloads()
downloadViewModel.cancelQueued() downloadViewModel.cancelQueued()
} }
} }

View file

@ -2,8 +2,6 @@ package com.deniscerri.ytdlnis.ui.more.settings
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
@ -18,10 +16,8 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.ui.more.TerminalActivity
import com.deniscerri.ytdlnis.util.ThemeUtil import com.deniscerri.ytdlnis.util.ThemeUtil
import com.deniscerri.ytdlnis.util.UpdateUtil import com.deniscerri.ytdlnis.util.UpdateUtil
import com.google.android.gms.common.wrappers.Wrappers.packageManager
import java.util.Locale import java.util.Locale
@ -33,7 +29,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
private var accent: ListPreference? = null private var accent: ListPreference? = null
private var highContrast: SwitchPreferenceCompat? = null private var highContrast: SwitchPreferenceCompat? = null
private var locale: ListPreference? = null private var locale: ListPreference? = null
private var hideTerminal: SwitchPreferenceCompat? = null private var showTerminalShareIcon: SwitchPreferenceCompat? = null
private var updateUtil: UpdateUtil? = null private var updateUtil: UpdateUtil? = null
private var activeDownloadCount = 0 private var activeDownloadCount = 0
@ -54,7 +50,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
accent = findPreference("theme_accent") accent = findPreference("theme_accent")
highContrast = findPreference("high_contrast") highContrast = findPreference("high_contrast")
locale = findPreference("locale") locale = findPreference("locale")
hideTerminal = findPreference("hide_terminal") showTerminalShareIcon = findPreference("show_terminal")
if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){ if(VERSION.SDK_INT < VERSION_CODES.TIRAMISU){
val values = resources.getStringArray(R.array.language_values) val values = resources.getStringArray(R.array.language_values)
@ -115,7 +111,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() {
true true
} }
hideTerminal!!.onPreferenceChangeListener = showTerminalShareIcon!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { pref: Preference?, _: Any -> Preference.OnPreferenceChangeListener { pref: Preference?, _: Any ->
val packageManager = requireContext().packageManager val packageManager = requireContext().packageManager
val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdlnis.terminalShareAlias") val aliasComponentName = ComponentName(requireContext(), "com.deniscerri.ytdlnis.terminalShareAlias")

View file

@ -2,9 +2,15 @@ package com.deniscerri.ytdlnis.util
import android.content.Context import android.content.Context
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.os.storage.StorageManager
import android.os.storage.StorageVolume
import android.provider.DocumentsContract
import android.util.Log import android.util.Log
import android.webkit.MimeTypeMap
import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.anggrayudi.storage.callback.FileCallback 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.copyFileTo
import com.anggrayudi.storage.file.copyFolderTo import com.anggrayudi.storage.file.copyFolderTo
import com.anggrayudi.storage.file.getAbsolutePath import com.anggrayudi.storage.file.getAbsolutePath
import com.anggrayudi.storage.file.moveFileTo import com.anggrayudi.storage.file.getBasePath
import com.anggrayudi.storage.file.moveFolderTo import com.deniscerri.ytdlnis.App
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.internal.closeQuietly
import java.io.File import java.io.File
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -32,15 +39,10 @@ import kotlin.math.pow
object FileUtil { object FileUtil {
fun deleteFile(path: String){ fun deleteFile(path: String){
val file = File(path) runCatching {
if (file.exists()) { if (!File(path).delete()){
file.delete() DocumentFile.fromSingleUri(App.instance, Uri.parse(path))?.delete()
} }
}
internal object Compare {
fun max(a: File, b: File): File {
return if (a.length() > b.length()) a else b
} }
} }
@ -100,7 +102,7 @@ object FileUtil {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
curr.copyFolderTo(context, dst!!, skipEmptyFiles = false, callback = object : FolderCallback() { curr.copyFolderTo(context, dst!!, skipEmptyFiles = false, callback = object : FolderCallback() {
override fun onStart(folder: DocumentFile, totalFilesToCopy: Int, workerThread: Thread): Long { 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) { override fun onParentConflict(destinationFolder: DocumentFile, action: ParentFolderConflictAction, canMerge: Boolean) {
@ -120,11 +122,66 @@ object FileUtil {
it.deleteRecursively() 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{ }else{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
curr.copyFileTo(context, dst!!, callback = object : FileCallback() { 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 { override fun onStart(file: Any, workerThread: Thread): Long {
return 1000 // update progress every 1 second return 1000 // update progress every 1 second
} }
@ -136,7 +193,7 @@ object FileUtil {
override fun onCompleted(result: Any) { override fun onCompleted(result: Any) {
destFile = (result as DocumentFile) destFile = (result as DocumentFile)
fileList.add(destFile.getAbsolutePath(context)) fileList.add(destFile.getAbsolutePath(context))
it.deleteRecursively() it.delete()
super.onCompleted(result) super.onCompleted(result)
} }
}) })
@ -170,10 +227,9 @@ object FileUtil {
} }
} }
private fun scanMedia(files: List<String>, context: Context) : List<String> { private fun scanMedia(files: List<String>, context: Context) : List<String> {
try { try {
val paths = files.sortedByDescending { File(it).length() } val paths = files.sortedByDescending { File(it).length() }
MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) runCatching {MediaScannerConnection.scanFile(context, paths.toTypedArray(), null, null) }
return paths return paths
}catch (e: Exception){ }catch (e: Exception){
e.printStackTrace() e.printStackTrace()

View file

@ -8,6 +8,7 @@ import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.Converters
import com.deniscerri.ytdlnis.database.models.ChapterItem import com.deniscerri.ytdlnis.database.models.ChapterItem
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.Format import com.deniscerri.ytdlnis.database.models.Format
@ -452,12 +453,12 @@ class InfoUtil(private val context: Context) {
request.addOption("-R", "1") request.addOption("-R", "1")
request.addOption("--socket-timeout", "5") request.addOption("--socket-timeout", "5")
val cookiesFile = File(context.cacheDir, "cookies.txt") val cookiesFile = File(context.cacheDir, "cookies.txt")
if (cookiesFile.exists()){ if (cookiesFile.exists()) {
request.addOption("--cookies", cookiesFile.absolutePath) request.addOption("--cookies", cookiesFile.absolutePath)
} }
val proxy = sharedPreferences.getString("proxy", "") val proxy = sharedPreferences.getString("proxy", "")
if (proxy!!.isNotBlank()){ if (proxy!!.isNotBlank()) {
request.addOption("--proxy", proxy) request.addOption("--proxy", proxy)
} }
@ -471,34 +472,7 @@ class InfoUtil(private val context: Context) {
val json = results[0] val json = results[0]
val jsonArray = JSONArray(json) val jsonArray = JSONArray(json)
val formats : ArrayList<Format> = ArrayList() return parseYTDLFormats(jsonArray)
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
} catch (e: Exception) { } catch (e: Exception) {
Looper.prepare().run { Looper.prepare().run {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() 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 -> YoutubeDL.getInstance().execute(request){ progress, _, line ->
try{ try{
val formats = mutableListOf<Format>()
val listOfStrings = JSONArray(line) val listOfStrings = JSONArray(line)
for (f in 0 until listOfStrings.length()){ progress(parseYTDLFormats(listOfStrings))
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)
}catch (e: Exception){ }catch (e: Exception){
progress(emptyList()) progress(emptyList())
} }
@ -668,37 +615,7 @@ class InfoUtil(private val context: Context) {
if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title") if (jsonObject.has("playlist_title")) playlistTitle = jsonObject.getString("playlist_title")
if(playlistTitle.equals(query)) playlistTitle = "" if(playlistTitle.equals(query)) playlistTitle = ""
val formatsInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null val formatsInJSON = if (jsonObject.has("formats") && jsonObject.get("formats") is JSONArray) jsonObject.getJSONArray("formats") else null
val formats : ArrayList<Format> = ArrayList() val formats : ArrayList<Format> = parseYTDLFormats(formatsInJSON)
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 chaptersInJSON = if (jsonObject.has("chapters") && jsonObject.get("chapters") is JSONArray) jsonObject.getJSONArray("chapters") else null val chaptersInJSON = if (jsonObject.has("chapters") && jsonObject.get("chapters") is JSONArray) jsonObject.getJSONArray("chapters") else null
val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type val listType: Type = object : TypeToken<List<ChapterItem>>() {}.type
@ -734,6 +651,47 @@ class InfoUtil(private val context: Context) {
return items return items
} }
private fun parseYTDLFormats(formatsInJSON: JSONArray?) : ArrayList<Format> {
val formats = arrayListOf<Format>()
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? { fun getMissingInfo(url: String): ResultItem? {
try { try {
val request = YoutubeDLRequest(url) val request = YoutubeDLRequest(url)
@ -885,33 +843,57 @@ class InfoUtil(private val context: Context) {
fun getStreamingUrlAndChapters(url: String) : MutableList<String?> { fun getStreamingUrlAndChapters(url: String) : MutableList<String?> {
try { try {
val request = YoutubeDLRequest(url) val p = Pattern.compile("(^(https?)://(www.)?(music.)?youtu(.be)?)|(^(https?)://(www.)?piped.video)")
request.addOption("--get-url") val m = p.matcher(url)
request.addOption("--print", "%(chapters)s")
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
val cookiesFile = File(context.cacheDir, "cookies.txt") if (m.find()){
if (cookiesFile.exists()){ return getStreamingUrlAndChaptersFromPIPED(getIDFromYoutubeURL(url))
request.addOption("--cookies", cookiesFile.absolutePath) }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", "") val cookiesFile = File(context.cacheDir, "cookies.txt")
if (proxy!!.isNotBlank()){ if (cookiesFile.exists()){
request.addOption("--proxy", proxy) request.addOption("--cookies", cookiesFile.absolutePath)
} }
val youtubeDLResponse = YoutubeDL.getInstance().execute(request) val proxy = sharedPreferences.getString("proxy", "")
val results: Array<String?> = try { if (proxy!!.isNotBlank()){
val lineSeparator = System.getProperty("line.separator") request.addOption("--proxy", proxy)
youtubeDLResponse.out.split(lineSeparator!!).toTypedArray() }
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: Array<String?> = 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) { } 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<String?> {
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<String?>(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",".*.",downloadItem.author.take(25)))
} }
request.addCommands(listOf("--replace-in-metadata","uploader"," - Topic$","")) 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") downloadItem.customFileNameTemplate = downloadItem.customFileNameTemplate.replace("%(uploader)s", "%(uploader|${downloadItem.author})s")
if (downloadItem.downloadSections.isNotBlank()){ 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) request.addOption("-P", tempFileDir.absolutePath)
if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){ if (downloadItem.audioPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters") request.addOption("--split-chapters")
request.addOption("-o", "chapter:%(section_title)s.%(ext)s")
}else{ }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()){ if (downloadItem.videoPreferences.splitByChapters && downloadItem.downloadSections.isBlank()){
request.addOption("--split-chapters") request.addOption("--split-chapters")
}else{ }else{
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s") if (downloadItem.customFileNameTemplate.isNotBlank()){
request.addOption("-o", "${downloadItem.customFileNameTemplate}.%(ext)s")
}
} }
} }

View file

@ -117,8 +117,8 @@ object ThemeUtil {
private fun getThemeColor(context: Context, colorCode: Int): Int { private fun getThemeColor(context: Context, colorCode: Int): Int {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val accent = sharedPreferences.getString("theme_accent", "blue") val accent = sharedPreferences.getString("theme_accent", "blue")
return if (accent == "Default" || accent == "blue"){ return if (accent == "blue"){
"c22727".toInt(16) "d43c3b".toInt(16)
}else{ }else{
val value = TypedValue() val value = TypedValue()
context.theme.resolveAttribute(colorCode, value, true) context.theme.resolveAttribute(colorCode, value, true)

View file

@ -17,6 +17,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.webkit.MimeTypeMap
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
@ -251,7 +252,7 @@ object UiUtil {
fragmentContext.packageName + ".fileprovider", fragmentContext.packageName + ".fileprovider",
file file
) )
val mime = fragmentContext.contentResolver.getType(uri) val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension)
val i = Intent(Intent.ACTION_VIEW) val i = Intent(Intent.ACTION_VIEW)
i.setDataAndType(uri, mime) i.setDataAndType(uri, mime)
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

View file

@ -60,7 +60,7 @@ class DownloadWorker(
return Result.failure() 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) val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_graph) .setGraph(R.navigation.nav_graph)
@ -134,7 +134,7 @@ class DownloadWorker(
}.onSuccess { }.onSuccess {
val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao) val wasQuickDownloaded = updateDownloadItem(downloadItem, infoUtil, dao, resultDao)
CoroutineScope(Dispatchers.IO).launch { runBlocking {
var finalPaths : List<String>? var finalPaths : List<String>?
//move file from internal to set download directory //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)) 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, downloadItem.title, if (finalPaths?.first().equals(context.getString(R.string.unfound_file))) null else finalPaths,
NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID NotificationUtil.DOWNLOAD_FINISHED_CHANNEL_ID
) )
}
if (wasQuickDownloaded){ if (wasQuickDownloaded){
runCatching { runCatching {
setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false)) setProgressAsync(workDataOf("progress" to 100, "output" to "Creating Result Items", "id" to downloadItem.id, "log" to false))
runBlocking { runBlocking {
infoUtil.getFromYTDL(downloadItem.url).forEach { res -> infoUtil.getFromYTDL(downloadItem.url).forEach { res ->
if (res != null) { if (res != null) {
resultDao.insert(res) resultDao.insert(res)
}
} }
} }
} }
} }
}
runBlocking {
dao.delete(downloadItem.id) dao.delete(downloadItem.id)
} }
}.onFailure { }.onFailure {
if (it is YoutubeDL.CanceledException) { if (it is YoutubeDL.CanceledException) {
downloadItem.status = DownloadRepository.Status.Cancelled.toString()
runBlocking {
dao.update(downloadItem)
}
return Result.failure( return Result.failure(
Data.Builder().putString("output", "Download has been cancelled!").build() Data.Builder().putString("output", "Download has been cancelled!").build()
) )

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:textColorPrimary" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<padding
android:top="10dp"
android:left="10dp"
android:right="10dp"
android:bottom="10dp"/>
</shape>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/line"/>
<item
android:drawable="@drawable/line"/>
</selector>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="65%"
android:pivotY="20%"
android:toDegrees="250"
android:fromDegrees="270">
<shape android:shape="rectangle">
<solid android:color="?android:colorAccent"/>
<size
android:width="40dp"
android:height="20dp"/>
<corners
android:topLeftRadius="20dp"
android:topRightRadius="20dp"/>
</shape>
</rotate>

View file

@ -0,0 +1,8 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:drawable="@drawable/thumb"/>
<item
android:drawable="@drawable/thumb"/>
</selector>

View file

@ -27,6 +27,28 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:singleSelection="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true" />
</HorizontalScrollView>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint" android:id="@+id/queries_constraint"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -132,9 +154,12 @@
android:id="@+id/recyclerViewHome" android:id="@+id/recyclerViewHome"
android:orientation="vertical" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="vertical" android:scrollbars="none"
android:layout_marginLeft="10dp" app:fastScrollEnabled="true"
android:layout_marginRight="10dp" app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp" android:paddingBottom="100dp"
app:spanCount="3" app:spanCount="3"

View file

@ -27,6 +27,28 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
app:singleSelection="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true" />
</HorizontalScrollView>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint" android:id="@+id/queries_constraint"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -147,9 +169,13 @@
android:id="@+id/recyclerViewHome" android:id="@+id/recyclerViewHome"
android:orientation="vertical" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="vertical" android:scrollbars="none"
android:layout_marginLeft="10dp" app:fastScrollEnabled="true"
android:layout_marginRight="10dp" app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp" android:paddingBottom="100dp"
app:spanCount="2" app:spanCount="2"

View file

@ -27,6 +27,28 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:singleSelection="true"
app:chipSpacingVertical="-10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true" />
</HorizontalScrollView>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint" android:id="@+id/queries_constraint"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -132,9 +154,12 @@
android:id="@+id/recyclerViewHome" android:id="@+id/recyclerViewHome"
android:orientation="vertical" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="vertical" android:scrollbars="none"
android:layout_marginLeft="10dp" app:fastScrollEnabled="true"
android:layout_marginRight="10dp" app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp" android:paddingBottom="100dp"
app:spanCount="4" app:spanCount="4"

View file

@ -108,7 +108,7 @@
android:dividerPadding="5dp" android:dividerPadding="5dp"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/output" app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toStartOf="@+id/active_download_cancel" app:layout_constraintEnd_toStartOf="@+id/active_download_pause"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"> app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview">
@ -152,14 +152,30 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/active_download_cancel" android:id="@+id/active_download_pause"
style="?attr/materialIconButtonFilledStyle" style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:backgroundTint="?attr/colorSurface" app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:icon="@drawable/ic_cancel" android:layout_marginEnd="5dp"
app:icon="@drawable/exomedia_ic_play_arrow_white"
app:iconSize="30dp"
app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/output"
app:layout_constraintEnd_toStartOf="@id/active_download_delete"
app:layout_constraintTop_toBottomOf="@+id/linearlayout2_horizontalscrollview"
app:layout_constraintVertical_bias="0.51" />
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_delete"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
app:backgroundTint="?attr/colorSurface"
app:cornerRadius="15dp"
app:icon="@drawable/baseline_delete_24"
app:iconSize="30dp" app:iconSize="30dp"
app:iconTint="?android:textColorPrimary" app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/output" app:layout_constraintBottom_toTopOf="@+id/output"

View file

@ -15,63 +15,53 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp" app:strokeWidth="0dp"
app:cardElevation="10dp"
app:cardMaxElevation="12dp"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:alpha="0.3"
android:scaleY="200"
app:trackColor="#000" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingHorizontal="20dp" android:paddingHorizontal="10dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView <FrameLayout
android:id="@+id/image_view" android:id="@+id/image_frame"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintVertical_bias="0.0"
android:adjustViewBounds="true"
android:background="?attr/colorSurfaceVariant"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar" /> 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"
>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress"
android:layout_width="match_parent"
android:elevation="10dp"
app:layout_constraintStart_toStartOf="@id/image_view"
app:layout_constraintEnd_toEndOf="@id/image_view"
app:layout_constraintBottom_toBottomOf="@id/image_view"
app:layout_constraintTop_toTopOf="@id/image_view"
android:layout_height="0dp"
android:layout_gravity="bottom"
android:alpha="0.6"
android:scaleY="200"
app:trackColor="#000" />
<TextView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/duration" android:id="@+id/image_view"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_margin="5dp" app:layout_constraintVertical_bias="0.0"
android:background="#80000000" android:background="?attr/colorSurfaceVariant"
android:clickable="false" android:scaleType="centerCrop"/>
android:ellipsize="end"
android:gravity="center" </FrameLayout>
android:maxLength="17"
android:maxLines="1"
android:minWidth="30dp"
android:paddingHorizontal="5dp"
android:textColor="@color/white"
android:textSize="12sp"
android:textStyle="bold"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="@+id/image_view"
app:layout_constraintEnd_toEndOf="@+id/image_view" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/download_item_data" android:id="@+id/download_item_data"
@ -79,9 +69,9 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/active_download_cancel" app:layout_constraintEnd_toStartOf="@id/active_download_pause"
app:layout_constraintHorizontal_weight="0.7" app:layout_constraintHorizontal_weight="0.7"
app:layout_constraintStart_toEndOf="@+id/image_view" app:layout_constraintStart_toEndOf="@+id/image_frame"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
@ -139,14 +129,29 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/active_download_cancel" android:id="@+id/active_download_pause"
style="?attr/materialIconButtonFilledStyle" style="@style/Widget.Material3.Button.IconButton"
app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_bias="0.0"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:backgroundTint="?attr/colorSurface" android:background="?android:attr/selectableItemBackground"
app:cornerRadius="15dp" app:cornerRadius="15dp"
app:icon="@drawable/ic_cancel" app:icon="@drawable/exomedia_ic_pause_white"
app:iconSize="30dp"
app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/active_download_delete"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/active_download_delete"
style="@style/Widget.Material3.Button.IconButton"
app:layout_constraintVertical_bias="0.0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
app:cornerRadius="15dp"
app:icon="@drawable/baseline_delete_24"
app:iconSize="30dp" app:iconSize="30dp"
app:iconTint="?android:textColorPrimary" app:iconTint="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -159,7 +164,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="download_item_data,active_download_cancel" /> app:constraint_referenced_ids="download_item_data,active_download_delete" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -129,7 +129,7 @@
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput" android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content" android:layout_width="80dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/start"> android:hint="@string/start">
@ -154,7 +154,7 @@
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/to_textinput" android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="wrap_content" android:layout_width="80dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/end" android:hint="@string/end"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View file

@ -92,6 +92,11 @@
android:paddingTop="10dp" android:paddingTop="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
android:paddingBottom="70dp" android:paddingBottom="70dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="100dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.button.MaterialButton
android:id="@+id/pause_resume"
android:visibility="gone"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:paddingVertical="10dp"
android:text="@string/resume"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/download_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pause_resume">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -4,6 +4,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="200dp" 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:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">

View file

@ -112,8 +112,11 @@
android:orientation="vertical" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="vertical" android:scrollbars="vertical"
android:layout_marginLeft="10dp" app:fastScrollEnabled="true"
android:layout_marginRight="10dp" app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp" android:paddingBottom="100dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">

View file

@ -27,6 +27,28 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<HorizontalScrollView
android:layout_width="wrap_content"
android:scrollbars="none"
android:layout_height="wrap_content">
<com.google.android.material.chip.ChipGroup
android:id="@+id/providers"
android:layout_margin="10dp"
app:chipSpacingVertical="-10dp"
android:layout_width="wrap_content"
app:singleSelection="true"
android:layout_height="wrap_content"
app:singleLine="true" />
</HorizontalScrollView>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
style="@style/Divider.Vertical"/>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/queries_constraint" android:id="@+id/queries_constraint"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -135,11 +157,16 @@
android:id="@+id/recyclerViewHome" android:id="@+id/recyclerViewHome"
android:orientation="vertical" android:orientation="vertical"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scrollbars="vertical" android:scrollbars="none"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="100dp" android:paddingBottom="100dp"
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:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/> />

View file

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools" <androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="100dp"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
@ -22,6 +23,11 @@
android:id="@+id/download_recyclerview" android:id="@+id/download_recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"

View file

@ -24,6 +24,7 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"

View file

@ -2,6 +2,8 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" <androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout <RelativeLayout

View file

@ -2,6 +2,8 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" <androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
@ -53,7 +55,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:dividerPadding="5dp" android:dividerPadding="5dp"
android:orientation="vertical" 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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -62,15 +64,15 @@
android:id="@+id/result_title" android:id="@+id/result_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="10dp"
android:maxLines="2"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2"
android:paddingStart="10dp"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:scrollbars="none"
android:shadowColor="@color/black" android:shadowColor="@color/black"
android:shadowDx="4" android:shadowDx="4"
android:shadowDy="4" android:shadowDy="4"
android:scrollbars="none"
android:shadowRadius="2" android:shadowRadius="2"
android:textColor="#FFF" android:textColor="#FFF"
android:textSize="17sp" android:textSize="17sp"
@ -80,15 +82,15 @@
android:id="@+id/author" android:id="@+id/author"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingStart="10dp"
android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:gravity="bottom"
android:maxLines="1"
android:paddingStart="10dp"
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:scrollbars="none"
android:shadowColor="@color/black" android:shadowColor="@color/black"
android:shadowDx="4" android:shadowDx="4"
android:shadowDy="4" android:shadowDy="4"
android:scrollbars="none"
android:shadowRadius="1.5" android:shadowRadius="1.5"
android:textColor="#FFF" android:textColor="#FFF"
android:textSize="12sp" android:textSize="12sp"
@ -115,33 +117,32 @@
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<LinearLayout <com.google.android.material.button.MaterialButton
android:id="@+id/download_button_layout" android:id="@+id/download_music"
android:layout_width="wrap_content" style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_height="wrap_content" android:layout_width="55dp"
android:orientation="horizontal" app:elevation="0dp"
android:padding="5dp" android:elevation="0dp"
app:borderWidth="0dp"
android:layout_height="55dp"
android:layout_marginVertical="10dp"
android:layout_marginHorizontal="5dp"
app:icon="@drawable/ic_music"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toStartOf="@+id/download_video" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/download_music" android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp" android:layout_width="55dp"
android:layout_height="55dp" android:layout_height="55dp"
android:layout_marginVertical="5dp" android:layout_margin="10dp"
android:layout_marginStart="5dp" app:elevation="0dp"
app:icon="@drawable/ic_music" /> android:elevation="0dp"
app:borderWidth="0dp"
<com.google.android.material.button.MaterialButton app:icon="@drawable/ic_video"
android:id="@+id/download_video" app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary" app:layout_constraintEnd_toEndOf="parent" />
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="5dp"
app:icon="@drawable/ic_video" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -78,6 +78,7 @@
android:id="@+id/title" android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:ellipsize="end" android:ellipsize="end"
@ -85,7 +86,7 @@
android:singleLine="false" android:singleLine="false"
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="17sp" 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_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -94,53 +95,46 @@
android:id="@+id/bottom_info" android:id="@+id/bottom_info"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:layout_marginEnd="10dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="false" android:singleLine="false"
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="12sp" 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_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" /> app:layout_constraintTop_toBottomOf="@id/title" />
<LinearLayout <com.google.android.material.button.MaterialButton
android:id="@+id/download_button_layout" android:id="@+id/download_music"
android:layout_width="wrap_content" style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_height="wrap_content" android:layout_width="55dp"
android:layout_marginTop="10dp" android:layout_height="55dp"
android:layout_marginEnd="20dp" android:layout_marginVertical="10dp"
android:orientation="horizontal" android:layout_marginEnd="5dp"
app:icon="@drawable/ic_music"
app:layout_constraintEnd_toStartOf="@+id/download_video"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_music"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginVertical="5dp"
android:layout_marginStart="5dp"
app:icon="@drawable/ic_music" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download_video"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="5dp"
app:icon="@drawable/ic_video" />
</LinearLayout>
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/title_barrier" android:id="@+id/title_barrier"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="bottom_info,download_button_layout" /> app:constraint_referenced_ids="bottom_info,download_music" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView

View file

@ -5,6 +5,8 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">

View file

@ -85,7 +85,6 @@
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -96,10 +95,9 @@
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput" android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="70dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
app:errorEnabled="true"
android:hint="@string/start"> android:hint="@string/start">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
@ -112,10 +110,9 @@
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/to_textinput" android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox" style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="70dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/end" android:hint="@string/end"
app:errorEnabled="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon"> app:layout_constraintStart_toEndOf="@+id/colon">
@ -139,6 +136,11 @@
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="80dp" 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" /> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

View file

@ -12,7 +12,7 @@
<item <item
android:id="@+id/redownload" android:id="@+id/redownload"
android:title="@string/redownload" android:title="@string/redownload"
android:icon="@drawable/ic_refresh" android:icon="@drawable/ic_refresh_white"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item <item

View file

@ -241,8 +241,7 @@
<string name="sponsorblock">حاجب شرائح الإعلانات</string> <string name="sponsorblock">حاجب شرائح الإعلانات</string>
<string name="keep_cache">الاحتفاظ بالملفات المؤقتة</string> <string name="keep_cache">الاحتفاظ بالملفات المؤقتة</string>
<string name="backup">عمل نسخة احتياطية</string> <string name="backup">عمل نسخة احتياطية</string>
<string name="pause">إيقاف <string name="pause">إيقاف</string>
\n</string>
<string name="resume">استئناف</string> <string name="resume">استئناف</string>
<string name="updating_download_data">يتم جلب بيانات التحميل</string> <string name="updating_download_data">يتم جلب بيانات التحميل</string>
<string name="keep_cache_summary">عدم مسح ملفات الكاش بعد اكتمال التحميل</string> <string name="keep_cache_summary">عدم مسح ملفات الكاش بعد اكتمال التحميل</string>

View file

@ -236,8 +236,7 @@
<string name="preferred_format_id">Üstünlük Verilən Format ID</string> <string name="preferred_format_id">Üstünlük Verilən Format ID</string>
<string name="download_already_exists">Yükləmə artıq var</string> <string name="download_already_exists">Yükləmə artıq var</string>
<string name="preferred_locale">Üstünlük Verilən Yer</string> <string name="preferred_locale">Üstünlük Verilən Yer</string>
<string name="pause">Dayandır <string name="pause">Dayandır</string>
\n</string>
<string name="resume">Təkrar başlat</string> <string name="resume">Təkrar başlat</string>
<string name="updating_download_data">Yükləmə Element Məlumatı Yenilənir</string> <string name="updating_download_data">Yükləmə Element Məlumatı Yenilənir</string>
<string name="keep_cache">Qalığı Saxla</string> <string name="keep_cache">Qalığı Saxla</string>
@ -282,7 +281,7 @@
<string name="subtitle_format">Titr Formatı <string name="subtitle_format">Titr Formatı
\n</string> \n</string>
<string name="hide_thumbnails">Miniatürləri Gizlət</string> <string name="hide_thumbnails">Miniatürləri Gizlət</string>
<string name="hide_terminal">Sıxacı paylaşma menyusundan gizlət</string> <string name="show_terminal">Sıxacı paylaşma menyusundan gizlət</string>
<string name="saved">Saxlanıldı</string> <string name="saved">Saxlanıldı</string>
<string name="clear_saved">Saxlanılanları Təmizlə</string> <string name="clear_saved">Saxlanılanları Təmizlə</string>
<string name="use_extra_commands">Əlavə Əmrlər Əlavə Et</string> <string name="use_extra_commands">Əlavə Əmrlər Əlavə Et</string>

View file

@ -232,8 +232,7 @@
<string name="quick_download">Schneller Download</string> <string name="quick_download">Schneller Download</string>
<string name="quick_download_summary">Daten nicht vorab abrufen</string> <string name="quick_download_summary">Daten nicht vorab abrufen</string>
<string name="download_already_exists">Download bereits vorhanden</string> <string name="download_already_exists">Download bereits vorhanden</string>
<string name="pause">Pause <string name="pause">Pause</string>
\n</string>
<string name="backup">Sicherung</string> <string name="backup">Sicherung</string>
<string name="restore">Wiederherstellen</string> <string name="restore">Wiederherstellen</string>
<string name="network_error">Netzwerkfehler</string> <string name="network_error">Netzwerkfehler</string>

View file

@ -221,8 +221,7 @@
<string name="download_now">Λήψη Τώρα</string> <string name="download_now">Λήψη Τώρα</string>
<string name="download_already_exists">Η λήψη υπάρχει ήδη</string> <string name="download_already_exists">Η λήψη υπάρχει ήδη</string>
<string name="preferred_locale">Προτιμώμενες Τοπικές Ρυθμίσεις</string> <string name="preferred_locale">Προτιμώμενες Τοπικές Ρυθμίσεις</string>
<string name="pause">Παύση <string name="pause">Παύση</string>
\n</string>
<string name="resume">Συνέχεια</string> <string name="resume">Συνέχεια</string>
<string name="incognito">Ανώνυμη Λειτουργία</string> <string name="incognito">Ανώνυμη Λειτουργία</string>
<string name="sponsorblock">SponsorBlock</string> <string name="sponsorblock">SponsorBlock</string>

View file

@ -236,8 +236,7 @@
<string name="finished_download_notification_channel_name">Descargas terminadas</string> <string name="finished_download_notification_channel_name">Descargas terminadas</string>
<string name="finished_download_notification_channel_description">Notificación de descargas finalizadas o defectuosas</string> <string name="finished_download_notification_channel_description">Notificación de descargas finalizadas o defectuosas</string>
<string name="preferred_locale">Ubicación preferida</string> <string name="preferred_locale">Ubicación preferida</string>
<string name="pause">Pausa <string name="pause">Pausa</string>
\n</string>
<string name="resume">Resumen</string> <string name="resume">Resumen</string>
<string name="updating_download_data">Actualizando los datos de los elementos de la descarga</string> <string name="updating_download_data">Actualizando los datos de los elementos de la descarga</string>
<string name="keep_cache">Conservar la caché</string> <string name="keep_cache">Conservar la caché</string>
@ -285,7 +284,7 @@
\n</string> \n</string>
<string name="hide_thumbnails">Ocultar las miniaturas</string> <string name="hide_thumbnails">Ocultar las miniaturas</string>
<string name="saved">Guardado</string> <string name="saved">Guardado</string>
<string name="hide_terminal">Ocultar Terminal del menú compartir</string> <string name="show_terminal">Ocultar Terminal del menú compartir</string>
<string name="save_for_later">¿Guardar la descarga para más tarde\?</string> <string name="save_for_later">¿Guardar la descarga para más tarde\?</string>
<string name="clear_saved">Borrar lo Guardado</string> <string name="clear_saved">Borrar lo Guardado</string>
</resources> </resources>

View file

@ -153,8 +153,7 @@
<string name="preferred_format_id">पसंदीदा प्रारूप आईडी</string> <string name="preferred_format_id">पसंदीदा प्रारूप आईडी</string>
<string name="download_now">तुरंत डाउनलोड करें</string> <string name="download_now">तुरंत डाउनलोड करें</string>
<string name="preferred_locale">पसंदीदा लोकेल</string> <string name="preferred_locale">पसंदीदा लोकेल</string>
<string name="pause">रोको <string name="pause">रोको</string>
\n</string>
<string name="resume">चालू करो</string> <string name="resume">चालू करो</string>
<string name="keep_cache">कैचे रखें</string> <string name="keep_cache">कैचे रखें</string>
<string name="keep_cache_summary">डाउनलोड समाप्त होने के बाद अस्थायी फ़ाइलें न हटाएं</string> <string name="keep_cache_summary">डाउनलोड समाप्त होने के बाद अस्थायी फ़ाइलें न हटाएं</string>

View file

@ -235,8 +235,7 @@
<string name="preferred_format_id">Preferensi ID Format</string> <string name="preferred_format_id">Preferensi ID Format</string>
<string name="download_now">Unduh Sekarang</string> <string name="download_now">Unduh Sekarang</string>
<string name="remove_audio">Hilangkan Audio</string> <string name="remove_audio">Hilangkan Audio</string>
<string name="pause">Jeda <string name="pause">Jeda</string>
\n</string>
<string name="updating_download_data">Memutakhirkan Data Butir Unduhan</string> <string name="updating_download_data">Memutakhirkan Data Butir Unduhan</string>
<string name="preferred_locale">Preferensi Wilayah</string> <string name="preferred_locale">Preferensi Wilayah</string>
<string name="resume">Lanjutkan</string> <string name="resume">Lanjutkan</string>
@ -278,9 +277,8 @@
<string name="format_order">Urutan Format</string> <string name="format_order">Urutan Format</string>
<string name="force_keyframes">Paksakan Keyframe saat Pemotongan</string> <string name="force_keyframes">Paksakan Keyframe saat Pemotongan</string>
<string name="force_keyframes_summary">Proses yang lebih lambat, tetapi pemotongannya lebih akurat</string> <string name="force_keyframes_summary">Proses yang lebih lambat, tetapi pemotongannya lebih akurat</string>
<string name="hide_terminal">Sembunyikan Terminal dari menu berbagi</string> <string name="show_terminal">Sembunyikan Terminal dari menu berbagi</string>
<string name="subtitle_format">Format Takarir (Subtitle) <string name="subtitle_format">Format Takarir (Subtitle)</string>
\n</string>
<string name="hide_thumbnails">Sembunyikan Keluku (Thumbnail)</string> <string name="hide_thumbnails">Sembunyikan Keluku (Thumbnail)</string>
<string name="clear_saved">Bersihkan yang Disimpan</string> <string name="clear_saved">Bersihkan yang Disimpan</string>
<string name="saved">Disimpan</string> <string name="saved">Disimpan</string>

View file

@ -238,8 +238,7 @@
<string name="quick_download_summary">事前にデータを取得しません</string> <string name="quick_download_summary">事前にデータを取得しません</string>
<string name="remove_audio">音声を削除</string> <string name="remove_audio">音声を削除</string>
<string name="download_already_exists">ダウンロードに既に存在します</string> <string name="download_already_exists">ダウンロードに既に存在します</string>
<string name="pause">一時停止 <string name="pause">一時停止</string>
\n</string>
<string name="resume">再開</string> <string name="resume">再開</string>
<string name="keep_cache">キャッシュを保持</string> <string name="keep_cache">キャッシュを保持</string>
<string name="keep_cache_summary">ダウンロードの完了後に一時ファイルを削除しないようにします</string> <string name="keep_cache_summary">ダウンロードの完了後に一時ファイルを削除しないようにします</string>

View file

@ -241,8 +241,7 @@
<string name="system">시스템</string> <string name="system">시스템</string>
<string name="red">빨간색</string> <string name="red">빨간색</string>
<string name="pure_theme_summary">퓨어 블랙 / 화이트 테마</string> <string name="pure_theme_summary">퓨어 블랙 / 화이트 테마</string>
<string name="pause">일시정지 <string name="pause">일시정지</string>
\n</string>
<string name="material_you">머터리얼</string> <string name="material_you">머터리얼</string>
<string name="green">녹색</string> <string name="green">녹색</string>
<string name="blue">파란색</string> <string name="blue">파란색</string>

View file

@ -228,8 +228,7 @@
<string name="preferred_format_id">Vēlamā Formāta ID</string> <string name="preferred_format_id">Vēlamā Formāta ID</string>
<string name="download_now">Lejupielādēt Tagad</string> <string name="download_now">Lejupielādēt Tagad</string>
<string name="download_already_exists">Lejupielāde jau pastāv</string> <string name="download_already_exists">Lejupielāde jau pastāv</string>
<string name="pause">Pauze <string name="pause">Pauze</string>
\n</string>
<string name="updating_download_data">Notiek Lejupielādes Vienuma Datu Atjaunināšana</string> <string name="updating_download_data">Notiek Lejupielādes Vienuma Datu Atjaunināšana</string>
<string name="keep_cache">Paturēt Kešatmiņu</string> <string name="keep_cache">Paturēt Kešatmiņu</string>
<string name="keep_cache_summary">Neizdzēst pagaidu failus pēc lejupielādes pabeigšanas</string> <string name="keep_cache_summary">Neizdzēst pagaidu failus pēc lejupielādes pabeigšanas</string>

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pause">Stans <string name="pause">Stans</string>
\n</string>
<string name="keep_cache">Behald mellomgøymet</string> <string name="keep_cache">Behald mellomgøymet</string>
<string name="keep_cache_summary">Ikkje slett mellombelse filer etter at hentinga er ferdig</string> <string name="keep_cache_summary">Ikkje slett mellombelse filer etter at hentinga er ferdig</string>
<string name="network_error">Nettverksfeil</string> <string name="network_error">Nettverksfeil</string>

View file

@ -204,8 +204,7 @@
<string name="download_now">Pobierz teraz</string> <string name="download_now">Pobierz teraz</string>
<string name="download_already_exists">Pobieranie już istnieje</string> <string name="download_already_exists">Pobieranie już istnieje</string>
<string name="preferred_locale">Preferowane ustawienia regionalne</string> <string name="preferred_locale">Preferowane ustawienia regionalne</string>
<string name="pause">Pauza <string name="pause">Pauza</string>
\n</string>
<string name="updating_download_data">Aktualizowanie danych elementu do pobrania</string> <string name="updating_download_data">Aktualizowanie danych elementu do pobrania</string>
<string name="keep_cache_summary">Nie usuwaj plików tymczasowych po zakończeniu pobierania</string> <string name="keep_cache_summary">Nie usuwaj plików tymczasowych po zakończeniu pobierania</string>
<string name="restore">Przywróć</string> <string name="restore">Przywróć</string>

View file

@ -215,8 +215,7 @@
<string name="download_already_exists">O download já existe</string> <string name="download_already_exists">O download já existe</string>
<string name="keep_cache">Manter Cache</string> <string name="keep_cache">Manter Cache</string>
<string name="updating_download_data">Atualizando os dados do item de download</string> <string name="updating_download_data">Atualizando os dados do item de download</string>
<string name="pause">Pausar <string name="pause">Pausar</string>
\n</string>
<string name="preferred_locale">Local preferido</string> <string name="preferred_locale">Local preferido</string>
<string name="resume">Continuar</string> <string name="resume">Continuar</string>
<string name="network_error">Erro de rede</string> <string name="network_error">Erro de rede</string>
@ -278,10 +277,9 @@
<string name="sponsorblock">Bloqueio de Patrocinadores</string> <string name="sponsorblock">Bloqueio de Patrocinadores</string>
<string name="force_keyframes">Forçar quadros-chave nos cortes</string> <string name="force_keyframes">Forçar quadros-chave nos cortes</string>
<string name="force_keyframes_summary">Processo lento, mas cortes mais precisos</string> <string name="force_keyframes_summary">Processo lento, mas cortes mais precisos</string>
<string name="subtitle_format">Formato da Legenda <string name="subtitle_format">Formato da Legenda</string>
\n</string>
<string name="hide_thumbnails">Ocultar Miniaturas</string> <string name="hide_thumbnails">Ocultar Miniaturas</string>
<string name="hide_terminal">Ocultar Terminal do menu de compartilhamento</string> <string name="show_terminal">Ocultar Terminal do menu de compartilhamento</string>
<string name="saved">Salvo</string> <string name="saved">Salvo</string>
<string name="clear_saved">Limpar Salvo</string> <string name="clear_saved">Limpar Salvo</string>
<string name="save_for_later">Salvar download para mais tarde\?</string> <string name="save_for_later">Salvar download para mais tarde\?</string>

View file

@ -168,8 +168,7 @@
<string name="download_now">Descarregar agora</string> <string name="download_now">Descarregar agora</string>
<string name="download_already_exists">Descarga já existente</string> <string name="download_already_exists">Descarga já existente</string>
<string name="preferred_locale">Idioma preferido</string> <string name="preferred_locale">Idioma preferido</string>
<string name="pause">Pausa <string name="pause">Pausa</string>
\n</string>
<string name="resume">Retomar</string> <string name="resume">Retomar</string>
<string name="keep_cache">Manter em cache</string> <string name="keep_cache">Manter em cache</string>
<string name="backup">Backup</string> <string name="backup">Backup</string>

View file

@ -239,8 +239,7 @@
<string name="format_filtering_hint">Чтобы использовать эту опцию, все элементы должны быть одного типа</string> <string name="format_filtering_hint">Чтобы использовать эту опцию, все элементы должны быть одного типа</string>
<string name="restore_complete">Восстановление приложения завершено. Были восстановлены следующие объекты:</string> <string name="restore_complete">Восстановление приложения завершено. Были восстановлены следующие объекты:</string>
<string name="download_already_exists">Загрузка уже существует</string> <string name="download_already_exists">Загрузка уже существует</string>
<string name="pause">Пауза <string name="pause">Пауза</string>
\n</string>
<string name="preferred_locale">Предпочтительный регион</string> <string name="preferred_locale">Предпочтительный регион</string>
<string name="video_recommendations">Видеорекомендации</string> <string name="video_recommendations">Видеорекомендации</string>
<string name="couldnt_parse_file">Не удалось разобрать файл</string> <string name="couldnt_parse_file">Не удалось разобрать файл</string>
@ -284,7 +283,7 @@
<string name="subtitle_format">Формат субтиров <string name="subtitle_format">Формат субтиров
\n</string> \n</string>
<string name="hide_thumbnails">Скрыть миниатюры видео</string> <string name="hide_thumbnails">Скрыть миниатюры видео</string>
<string name="hide_terminal">Скрыть терминал из меню \"Поделиться\"</string> <string name="show_terminal">Скрыть терминал из меню \"Поделиться\"</string>
<string name="saved">Сохраненные</string> <string name="saved">Сохраненные</string>
<string name="clear_saved">Очистить сохраненные</string> <string name="clear_saved">Очистить сохраненные</string>
<string name="save_for_later">Сохранить загрузку для возврата к ней позднее\?</string> <string name="save_for_later">Сохранить загрузку для возврата к ней позднее\?</string>

View file

@ -241,8 +241,7 @@
<string name="keep_cache">Mbaj Memoriken Kashe</string> <string name="keep_cache">Mbaj Memoriken Kashe</string>
<string name="keep_cache_summary">Mos fshi skedarët e përkohshëm pasi shkarkimi ka mbaruar</string> <string name="keep_cache_summary">Mos fshi skedarët e përkohshëm pasi shkarkimi ka mbaruar</string>
<string name="restore">Rikthe</string> <string name="restore">Rikthe</string>
<string name="pause">Pauzë <string name="pause">Pauzë</string>
\n</string>
<string name="updating_download_data">Duke Përditësuar Informacionet e Objektit</string> <string name="updating_download_data">Duke Përditësuar Informacionet e Objektit</string>
<string name="backup">Ruaj</string> <string name="backup">Ruaj</string>
<string name="select_backup_categories">Zgjidh kategoritë për ruajtje</string> <string name="select_backup_categories">Zgjidh kategoritë për ruajtje</string>
@ -281,7 +280,7 @@
<string name="force_keyframes_summary">Proçes më i ngadaltë, por prerje më të sakta</string> <string name="force_keyframes_summary">Proçes më i ngadaltë, por prerje më të sakta</string>
<string name="subtitle_format">Formati i Titrave <string name="subtitle_format">Formati i Titrave
\n</string> \n</string>
<string name="hide_terminal">Fshihe Terminalin nga fleta e shpërndarjes</string> <string name="show_terminal">Fshihe Terminalin nga fleta e shpërndarjes</string>
<string name="saved">Të ruajtura</string> <string name="saved">Të ruajtura</string>
<string name="clear_saved">Pastro të ruajturat</string> <string name="clear_saved">Pastro të ruajturat</string>
<string name="save_for_later">Ruaj Shkarkimin për më vonë\?</string> <string name="save_for_later">Ruaj Shkarkimin për më vonë\?</string>

View file

@ -240,8 +240,7 @@
<string name="download_now">Şimdi İndir</string> <string name="download_now">Şimdi İndir</string>
<string name="remove_audio">Sesi Kaldır</string> <string name="remove_audio">Sesi Kaldır</string>
<string name="preferred_locale">Tercih Edilen Konum</string> <string name="preferred_locale">Tercih Edilen Konum</string>
<string name="pause">Duraklat <string name="pause">Duraklat</string>
\n</string>
<string name="resume">Devam et</string> <string name="resume">Devam et</string>
<string name="updating_download_data">İndirilen Öğe Verilerini Güncelleme</string> <string name="updating_download_data">İndirilen Öğe Verilerini Güncelleme</string>
<string name="keep_cache">Önbelleği Sakla</string> <string name="keep_cache">Önbelleği Sakla</string>

View file

@ -236,8 +236,7 @@
<string name="download_now">Завантажити зараз</string> <string name="download_now">Завантажити зараз</string>
<string name="download_already_exists">Завантаження вже існує</string> <string name="download_already_exists">Завантаження вже існує</string>
<string name="preferred_locale">Бажане розташування</string> <string name="preferred_locale">Бажане розташування</string>
<string name="pause">Пауза <string name="pause">Пауза</string>
\n</string>
<string name="resume">Продовжити</string> <string name="resume">Продовжити</string>
<string name="updating_download_data">Оновлення даних завантажуваного елемента</string> <string name="updating_download_data">Оновлення даних завантажуваного елемента</string>
<string name="keep_cache">Зберегти кеш</string> <string name="keep_cache">Зберегти кеш</string>
@ -282,7 +281,7 @@
<string name="current">Поточна команда</string> <string name="current">Поточна команда</string>
<string name="use_extra_commands_summary">Додайте свої додаткові команди перед завантаженням аудіо/відео</string> <string name="use_extra_commands_summary">Додайте свої додаткові команди перед завантаженням аудіо/відео</string>
<string name="hide_thumbnails">Приховати мініатюри відео</string> <string name="hide_thumbnails">Приховати мініатюри відео</string>
<string name="hide_terminal">Приховати термінал з меню \"Поділитися\"</string> <string name="show_terminal">Приховати термінал з меню \"Поділитися\"</string>
<string name="saved">Збережені</string> <string name="saved">Збережені</string>
<string name="clear_saved">Очистити збережені</string> <string name="clear_saved">Очистити збережені</string>
<string name="subtitle_format">Формат субтитрів <string name="subtitle_format">Формат субтитрів

View file

@ -122,8 +122,7 @@
<string name="download_now">立即下載</string> <string name="download_now">立即下載</string>
<string name="download_already_exists">已存在下載</string> <string name="download_already_exists">已存在下載</string>
<string name="preferred_locale">首選地區設定</string> <string name="preferred_locale">首選地區設定</string>
<string name="pause">暫停 <string name="pause">暫停</string>
\n</string>
<string name="resume">繼續</string> <string name="resume">繼續</string>
<string name="updating_download_data">正在更新下載項目資料</string> <string name="updating_download_data">正在更新下載項目資料</string>
<string name="keep_cache">保留快取</string> <string name="keep_cache">保留快取</string>

View file

@ -5,7 +5,7 @@
<color name="grey">#323232</color> <color name="grey">#323232</color>
<color name="light_grey">#A6A6A6</color> <color name="light_grey">#A6A6A6</color>
<color name="icon_bg">#FFFFFF</color> <color name="icon_bg">#FFFFFF</color>
<color name="icon_fg">#d43c3c</color> <color name="icon_fg">#db2e2e</color>
<color name="blue_md_theme_light_primary">#0058CB</color> <color name="blue_md_theme_light_primary">#0058CB</color>
@ -320,9 +320,9 @@
<color name="orange_md_theme_dark_errorContainer">#93000A</color> <color name="orange_md_theme_dark_errorContainer">#93000A</color>
<color name="orange_md_theme_dark_onError">#690005</color> <color name="orange_md_theme_dark_onError">#690005</color>
<color name="orange_md_theme_dark_onErrorContainer">#FFDAD6</color> <color name="orange_md_theme_dark_onErrorContainer">#FFDAD6</color>
<color name="orange_md_theme_dark_background">#1F1B16</color> <color name="orange_md_theme_dark_background">#1b1b1b</color>
<color name="orange_md_theme_dark_onBackground">#EBE1D9</color> <color name="orange_md_theme_dark_onBackground">#EBE1D9</color>
<color name="orange_md_theme_dark_surface">#1F1B16</color> <color name="orange_md_theme_dark_surface">#1b1b1b</color>
<color name="orange_md_theme_dark_onSurface">#EBE1D9</color> <color name="orange_md_theme_dark_onSurface">#EBE1D9</color>
<color name="orange_md_theme_dark_surfaceVariant">#504539</color> <color name="orange_md_theme_dark_surfaceVariant">#504539</color>
<color name="orange_md_theme_dark_onSurfaceVariant">#D4C4B5</color> <color name="orange_md_theme_dark_onSurfaceVariant">#D4C4B5</color>

View file

@ -244,7 +244,7 @@
<string name="download_now">Download Now</string> <string name="download_now">Download Now</string>
<string name="download_already_exists">Download already exists</string> <string name="download_already_exists">Download already exists</string>
<string name="preferred_locale">Preferred Locale</string> <string name="preferred_locale">Preferred Locale</string>
<string name="pause">Pause\n</string> <string name="pause">Pause</string>
<string name="resume">Resume</string> <string name="resume">Resume</string>
<string name="updating_download_data">Updating Download Item Data</string> <string name="updating_download_data">Updating Download Item Data</string>
<string name="keep_cache">Keep Cache</string> <string name="keep_cache">Keep Cache</string>
@ -290,7 +290,7 @@
<string name="current">Current Command</string> <string name="current">Current Command</string>
<string name="subtitle_format">Subtitle Format\n</string> <string name="subtitle_format">Subtitle Format\n</string>
<string name="hide_thumbnails">Hide Thumbnails</string> <string name="hide_thumbnails">Hide Thumbnails</string>
<string name="hide_terminal">Hide Terminal from share menu</string> <string name="show_terminal">Show Terminal in share menu</string>
<string name="saved">Saved</string> <string name="saved">Saved</string>
<string name="clear_saved">Clear Saved</string> <string name="clear_saved">Clear Saved</string>
<string name="save_for_later">Save Download for Later?</string> <string name="save_for_later">Save Download for Later?</string>

View file

@ -44,7 +44,6 @@
app:title="@string/socks5_proxy" /> app:title="@string/socks5_proxy" />
<ListPreference <ListPreference
android:dependency="download_card"
android:defaultValue="video" android:defaultValue="video"
android:entries="@array/download_types" android:entries="@array/download_types"
android:entryValues="@array/download_types_values" android:entryValues="@array/download_types_values"

View file

@ -112,9 +112,9 @@
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:widgetLayout="@layout/preferece_material_switch" android:widgetLayout="@layout/preferece_material_switch"
app:defaultValue="true" app:defaultValue="false"
android:icon="@drawable/baseline_share_24" android:icon="@drawable/baseline_share_24"
android:key="hide_terminal" android:key="show_terminal"
app:title="@string/hide_terminal" /> app:title="@string/show_terminal" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>