implemented processing multiple items from a playlist

This commit is contained in:
Denis Çerri 2023-03-05 18:16:28 +01:00
parent 44f33e1508
commit cc8614d175
No known key found for this signature in database
GPG key ID: 95C43D517D830350
28 changed files with 999 additions and 467 deletions

View file

@ -320,7 +320,7 @@
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
<map> <map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/content_copy/baseline_content_copy_24.xml" /> <entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/select_all/baseline_select_all_24.xml" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>
@ -330,7 +330,7 @@
</option> </option>
<option name="values"> <option name="values">
<map> <map>
<entry key="outputName" value="ic_copy" /> <entry key="outputName" value="ic_select_all" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\adaptiveproduct_youtube_foreground_color_108 (1).svg" /> <entry key="sourceFile" value="C:\Users\denis\Desktop\adaptiveproduct_youtube_foreground_color_108 (1).svg" />
</map> </map>
</option> </option>

View file

@ -25,6 +25,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import okhttp3.internal.format
import java.util.* import java.util.*
class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) { class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<DownloadItem?, ConfigureMultipleDownloadsAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
@ -77,24 +78,45 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
itemTitle.text = title itemTitle.text = title
// Author ---------------------------------- // Author ----------------------------------
val author = card.findViewById<TextView>(R.id.author) val author = card.findViewById<TextView>(R.id.subtitle)
author.text = item.author author.text = item.author
// Container ---------------------------------- // Format Note ----------------------------------
val container = card.findViewById<TextView>(R.id.container) val formatNote = card.findViewById<TextView>(R.id.format_note)
container.text = item.format.container.uppercase(Locale.getDefault()) if (item.format.format_note.isNotEmpty()){
formatNote.text = item.format.format_note.uppercase(Locale.getDefault())
}else{
formatNote.visibility = View.GONE
}
// File Size val codec = card.findViewById<TextView>(R.id.codec)
val fileSize = card.findViewById<TextView>(R.id.filesize) val codecText =
val formattedSize = fileUtil.convertFileSize(item.format.filesize) if (item.format.encoding != "") {
fileSize.text = if (formattedSize == "?") "" else formattedSize.uppercase(Locale.getDefault()) 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"){
codec.visibility = View.GONE
}else{
codec.visibility = View.VISIBLE
codec.text = codecText
}
// Quality val fileSize = card.findViewById<TextView>(R.id.file_size)
val quality = card.findViewById<TextView>(R.id.quality) Log.e("aa", item.format.filesize.toString())
quality.text = item.format.format_note val fileSizeReadable = fileUtil.convertFileSize(item.format.filesize)
Log.e("aa", fileSizeReadable)
if (fileSizeReadable == "?") fileSize.visibility = View.GONE
else {
fileSize.text = fileSizeReadable
fileSize.visibility = View.VISIBLE
}
// Type Icon Button // Type Icon Button
val btn = card.findViewById<MaterialButton>(R.id.downloads_download_button_type) val btn = card.findViewById<MaterialButton>(R.id.action_button)
when(item.type) { when(item.type) {
DownloadViewModel.Type.audio -> { DownloadViewModel.Type.audio -> {
@ -109,23 +131,23 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
} }
card.setOnClickListener { card.setOnClickListener {
onItemClickListener.onCardClick(item.id) onItemClickListener.onCardClick(item.url)
} }
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onButtonClick(videoURL: String, type: String?) fun onButtonClick(videoURL: String, type: String?)
fun onCardClick(itemID: Long) fun onCardClick(itemURL: String)
} }
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 {
return oldItem.id === newItem.id return oldItem.url == newItem.url
} }
override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean { override fun areContentsTheSame(oldItem: DownloadItem, newItem: DownloadItem): Boolean {
return oldItem.url == newItem.url && oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.type == newItem.type && oldItem.format == newItem.format return oldItem.title == newItem.title && oldItem.author == newItem.author && oldItem.type == newItem.type && oldItem.format == newItem.format
} }
} }
} }

View file

@ -23,6 +23,7 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import okhttp3.internal.format
import java.io.File import java.io.File
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -48,7 +49,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context) val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.generic_download_card, parent, false) .inflate(R.layout.download_card, parent, false)
return ViewHolder(cardView, onItemClickListener) return ViewHolder(cardView, onItemClickListener)
} }
@ -56,6 +57,17 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
val item = getItem(position) val item = getItem(position)
val card = holder.cardView val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// TITLE ---------------------------------- // TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title) val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item!!.title var title = item!!.title
@ -64,12 +76,11 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
} }
itemTitle.text = title itemTitle.text = title
// TITLE ---------------------------------- val itemUrl = card.findViewById<TextView>(R.id.subtitle)
val itemUrl = card.findViewById<TextView>(R.id.url)
itemUrl.text = item.url itemUrl.text = item.url
val type = card.findViewById<TextView>(R.id.type) val formatNote = card.findViewById<TextView>(R.id.format_note)
type!!.text = item.type.toString().uppercase() formatNote!!.text = item.format.format_note.uppercase()
val codec = card.findViewById<TextView>(R.id.codec) val codec = card.findViewById<TextView>(R.id.codec)
val codecText = val codecText =

View file

@ -0,0 +1,136 @@
package com.deniscerri.ytdlnis.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.graphics.Color
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.squareup.picasso.Picasso
import java.util.*
class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<ResultItem?, PlaylistAdapter.ViewHolder>(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
private val checkedItems: ArrayList<Long>
private val onItemClickListener: OnItemClickListener
private val activity: Activity
init {
checkedItems = ArrayList()
this.onItemClickListener = onItemClickListener
this.activity = activity
}
class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
val cardView: ConstraintLayout
init {
cardView = itemView.findViewById(R.id.playlist_card_constraintLayout)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = LayoutInflater.from(parent.context)
.inflate(R.layout.playlist_item, parent, false)
return ViewHolder(cardView, onItemClickListener)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
val card = holder.cardView
// THUMBNAIL ----------------------------------
val thumbnail = card.findViewById<ImageView>(R.id.downloads_image_view)
val imageURL = item!!.thumb
val uiHandler = Handler(Looper.getMainLooper())
if (imageURL.isNotEmpty()) {
uiHandler.post { Picasso.get().load(imageURL).into(thumbnail) }
} else {
uiHandler.post { Picasso.get().load(R.color.black).into(thumbnail) }
}
thumbnail.setColorFilter(Color.argb(95, 0, 0, 0))
// TITLE ----------------------------------
val itemTitle = card.findViewById<TextView>(R.id.title)
var title = item.title
if (title.length > 100) {
title = title.substring(0, 40) + "..."
}
itemTitle.text = title
// CHECKBOX ----------------------------------
val check = card.findViewById<CheckBox>(R.id.checkBox)
check.isChecked = checkedItems.contains(item.id)
check.setOnCheckedChangeListener { buttonView, isChecked ->
checkCard(isChecked, item.id)
}
card.setOnClickListener {
check.isChecked = !check.isChecked
}
}
private fun checkCard(isChecked: Boolean, itemID: Long) {
if (isChecked) {
checkedItems.add(itemID)
} else {
checkedItems.remove(itemID)
}
onItemClickListener.onCardSelect(itemID, isChecked, checkedItems)
}
interface OnItemClickListener {
fun onCardSelect(itemID: Long, isChecked: Boolean, checkedItems: List<Long>)
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckeditems() {
for (i in 0 until itemCount){
val item = getItem(i)
if (checkedItems.find { it == item?.id } != null){
checkedItems.remove(item?.id)
notifyItemChanged(i)
}
}
checkedItems.clear()
}
fun checkAll(){
checkedItems.clear()
for (i in 0 until itemCount){
val item = getItem(i)
checkedItems.add(item!!.id)
notifyItemChanged(i)
}
}
fun getCheckedItems() : List<Long>{
return checkedItems
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<ResultItem> = object : DiffUtil.ItemCallback<ResultItem>() {
override fun areItemsTheSame(oldItem: ResultItem, newItem: ResultItem): Boolean {
val ranged = arrayListOf(oldItem.id, newItem.id)
return ranged[0] == ranged[1]
}
override fun areContentsTheSame(oldItem: ResultItem, newItem: ResultItem): Boolean {
return oldItem.id == newItem.id
}
}
}
}

View file

@ -9,6 +9,9 @@ interface ResultDao {
@Query("SELECT * FROM results") @Query("SELECT * FROM results")
fun getResults() : LiveData<List<ResultItem>> fun getResults() : LiveData<List<ResultItem>>
@Query("SELECT COUNT(id) FROM results")
fun getCount() : LiveData<Int>
@Query("SELECT * FROM results LIMIT 1") @Query("SELECT * FROM results LIMIT 1")
fun getFirstResult() : ResultItem fun getFirstResult() : ResultItem

View file

@ -14,7 +14,7 @@ import com.deniscerri.ytdlnis.util.InfoUtil
class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) { class ResultRepository(private val resultDao: ResultDao, private val commandTemplateDao: CommandTemplateDao, private val context: Context) {
private val tag: String = "ResultRepository" private val tag: String = "ResultRepository"
val allResults : LiveData<List<ResultItem>> = resultDao.getResults() val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
var itemCount = MutableLiveData(0) var itemCount = MutableLiveData(-1)
suspend fun insert(it: ResultItem){ suspend fun insert(it: ResultItem){
resultDao.insert(it) resultDao.insert(it)

View file

@ -144,6 +144,11 @@ class DownloadViewModel(application: Application) : AndroidViewModel(application
} }
} }
fun getLatestCommandTemplateAsFormat() : Format {
val t = commandTemplateDao.getFirst()
return Format(t.title, "", "", "", "", 0, t.content)
}
fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> { fun turnResultItemsToDownloadItems(items: List<ResultItem?>) : List<DownloadItem> {
val list : MutableList<DownloadItem> = mutableListOf() val list : MutableList<DownloadItem> = mutableListOf()
items.forEach { items.forEach {

View file

@ -14,22 +14,29 @@ import android.os.Build
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.View
import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.MainActivity import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.PlaylistAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
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.ui.downloadcard.DownloadBottomSheetDialog import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.deniscerri.ytdlnis.ui.downloadcard.SelectPlaylistItemsBottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -111,7 +118,11 @@ class ShareActivity : AppCompatActivity() {
if (it.isNotEmpty() && supportFragmentManager.findFragmentByTag("downloadSingleSheet") == null){ if (it.isNotEmpty() && supportFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
if(resultViewModel.itemCount.value!! == it.size){ if(resultViewModel.itemCount.value!! == it.size){
loadingBottomSheet.cancel() loadingBottomSheet.cancel()
showDownloadSheet(it[0]) if (it.size == 1){
showDownloadSheet(it[0])
}else{
showSelectPlaylistItems(it)
}
} }
} }
} }
@ -130,6 +141,24 @@ class ShareActivity : AppCompatActivity() {
} }
} }
private fun showSelectPlaylistItems(it: List<ResultItem>){
if (sharedPreferences.getBoolean("download_card", true)){
val bottomSheet = SelectPlaylistItemsBottomSheetDialog(it, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
bottomSheet.show(supportFragmentManager, "downloadPlaylistSheet")
}else{
lifecycleScope.launch(Dispatchers.IO){
val downloadItems = mutableListOf<DownloadItem>()
it.forEach { res ->
val i = downloadViewModel.createDownloadItemFromResult(res, DownloadViewModel.Type.valueOf(sharedPreferences.getString("preferred_download_type", "video")!!))
i.format = downloadViewModel.getLatestCommandTemplateAsFormat()
downloadItems.add(i)
}
downloadViewModel.queueDownloads(downloadItems)
}
this.finish()
}
}
private fun askPermissions() { private fun askPermissions() {
if (Build.VERSION.SDK_INT < VERSION_CODES.TIRAMISU && !checkFilePermission()) { if (Build.VERSION.SDK_INT < VERSION_CODES.TIRAMISU && !checkFilePermission()) {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(

View file

@ -134,8 +134,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
resultViewModel.items.observe(viewLifecycleOwner) { resultViewModel.items.observe(viewLifecycleOwner) {
homeAdapter!!.submitList(it) homeAdapter!!.submitList(it)
resultsList = it resultsList = it
Log.e(TAG, resultViewModel.itemCount.toString()) if(resultViewModel.itemCount.value!! > 1 || resultViewModel.itemCount.value!! == -1){
if(resultViewModel.itemCount.value!! > 1){
if (it[0].playlistTitle.isNotEmpty() && it[0].playlistTitle != getString(R.string.trendingPlaylist)){ if (it[0].playlistTitle.isNotEmpty() && it[0].playlistTitle != getString(R.string.trendingPlaylist)){
downloadAllFabCoordinator!!.visibility = VISIBLE downloadAllFabCoordinator!!.visibility = VISIBLE
}else{ }else{
@ -388,30 +387,20 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
if (viewIdName.isNotEmpty()) { if (viewIdName.isNotEmpty()) {
if (viewIdName == "downloadSelected") { if (viewIdName == "downloadSelected") {
val downloadList = downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!) val downloadList = downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
downloadViewModel.putDownloadsForProcessing(selectedObjects!!, downloadList).observe(viewLifecycleOwner) { if (sharedPreferences!!.getBoolean("download_card", true)) {
it.forEachIndexed { i, itemID -> val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
downloadList[i].id = itemID bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} } else {
if (sharedPreferences!!.getBoolean("download_card", true)) { downloadViewModel.queueDownloads(downloadList)
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} else {
downloadViewModel.queueDownloads(downloadList)
}
} }
} }
if (viewIdName == "downloadAll") { if (viewIdName == "downloadAll") {
val downloadList = downloadViewModel.turnResultItemsToDownloadItems(resultsList!!) val downloadList = downloadViewModel.turnResultItemsToDownloadItems(resultsList!!)
downloadViewModel.putDownloadsForProcessing(resultsList!!, downloadList).observe(viewLifecycleOwner) { if (sharedPreferences!!.getBoolean("download_card", true)) {
it.forEachIndexed { i, itemID -> val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
downloadList[i].id = itemID bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} } else {
if (sharedPreferences!!.getBoolean("download_card", true)) { downloadViewModel.queueDownloads(downloadList)
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} else {
downloadViewModel.queueDownloads(downloadList)
}
} }
} }
} }

View file

@ -9,7 +9,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -20,31 +19,33 @@ import com.deniscerri.ytdlnis.database.dao.CommandTemplateDao
import com.deniscerri.ytdlnis.database.models.DownloadItem import com.deniscerri.ytdlnis.database.models.DownloadItem
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.DownloadViewModel.Type
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.google.android.exoplayer2.Player
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, private val type: Type) : BottomSheetDialogFragment() {
class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, private val downloadItem: DownloadItem, private val listener: OnDownloadItemUpdateListener) : BottomSheetDialogFragment() {
private lateinit var tabLayout: TabLayout private lateinit var tabLayout: TabLayout
private lateinit var viewPager2: ViewPager2 private lateinit var viewPager2: ViewPager2
private lateinit var fragmentAdapter : DownloadFragmentAdapter private lateinit var fragmentAdapter : DownloadFragmentAdapter
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var commandTemplateDao: CommandTemplateDao private lateinit var commandTemplateDao: CommandTemplateDao
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
onDownloadItemUpdateListener = listener
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -63,13 +64,13 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
tabLayout = view.findViewById(R.id.download_tablayout) tabLayout = view.findViewById(R.id.download_tablayout)
viewPager2 = view.findViewById(R.id.download_viewpager) viewPager2 = view.findViewById(R.id.download_viewpager)
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem), DownloadVideoFragment(resultItem)) val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, downloadItem), DownloadVideoFragment(resultItem, downloadItem))
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber() val nr = commandTemplateDao.getTotalNumber()
if(nr > 0){ if(nr > 0){
fragments.add(DownloadCommandFragment(resultItem)) fragments.add(DownloadCommandFragment(resultItem, downloadItem))
}else{ }else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false
} }
@ -77,7 +78,6 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
} }
val fragmentManager = parentFragmentManager val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter( fragmentAdapter = DownloadFragmentAdapter(
resultItem, resultItem,
@ -88,7 +88,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
viewPager2.adapter = fragmentAdapter viewPager2.adapter = fragmentAdapter
viewPager2.isSaveFromParentEnabled = false viewPager2.isSaveFromParentEnabled = false
when(type) { when(downloadItem.type) {
Type.audio -> { Type.audio -> {
tabLayout.selectTab(tabLayout.getTabAt(0)) tabLayout.selectTab(tabLayout.getTabAt(0))
viewPager2.setCurrentItem(0, false) viewPager2.setCurrentItem(0, false)
@ -99,7 +99,9 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
} }
else -> { else -> {
tabLayout.selectTab(tabLayout.getTabAt(2)) tabLayout.selectTab(tabLayout.getTabAt(2))
viewPager2.setCurrentItem(2, false) viewPager2.postDelayed( {
viewPager2.setCurrentItem(2, false)
}, 200)
} }
} }
@ -121,11 +123,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
} }
}) })
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
val cancelBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_cancel_button)
cancelBtn.setOnClickListener{
dismiss()
}
val ok = view.findViewById<Button>(R.id.bottom_sheet_ok) val ok = view.findViewById<Button>(R.id.bottom_sheet_ok)
ok!!.setOnClickListener { ok!!.setOnClickListener {
@ -144,12 +142,18 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
f.downloadItem f.downloadItem
} }
} }
downloadViewModel.updateDownload(item) Log.e("aa", item.toString())
onDownloadItemUpdateListener.onDownloadItemUpdate(resultItem.id, item)
dismiss() dismiss()
} }
} }
interface OnDownloadItemUpdateListener {
fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem)
}
override fun onCancel(dialog: DialogInterface) { override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog) super.onCancel(dialog)
//returnPreviousState(); //returnPreviousState();
@ -182,4 +186,3 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
} }
} }

View file

@ -31,10 +31,11 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() { class DownloadAudioFragment(private var resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
@ -46,8 +47,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
private lateinit var author : TextInputLayout private lateinit var author : TextInputLayout
private lateinit var saveDir : TextInputLayout private lateinit var saveDir : TextInputLayout
lateinit var downloadItem: DownloadItem lateinit var downloadItem : DownloadItem
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -57,7 +57,14 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false) fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false)
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
downloadItem = downloadViewModel.createDownloadItemFromResult(resultItem, Type.audio)
downloadItem = if (currentDownloadItem != null && currentDownloadItem!!.type == Type.audio){
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
Gson().fromJson(string, DownloadItem::class.java)
}else{
downloadViewModel.createDownloadItemFromResult(resultItem, Type.audio)
}
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil) uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
@ -90,14 +97,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
} }
}) })
saveDir = view.findViewById(R.id.outputPath) saveDir = view.findViewById(R.id.outputPath)
val downloadPath = sharedPreferences.getString(
"music_path",
getString(R.string.music_path)
)
downloadItem.downloadPath = downloadPath!!
//downloadViewModel.updateDownload(downloadItem)
saveDir.editText!!.setText( saveDir.editText!!.setText(
fileUtil.formatPath(downloadPath) fileUtil.formatPath(downloadItem.downloadPath)
) )
saveDir.editText!!.isFocusable = false; saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true; saveDir.editText!!.isClickable = true;
@ -121,7 +122,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
if (formats.isEmpty()) { if (formats.isEmpty()) {
formatCard.visibility = View.GONE formatCard.visibility = View.GONE
} else { } else {
val chosenFormat = formats[formats.lastIndex] val chosenFormat = downloadItem.format
uiUtil.populateFormatCard(formatCard, chosenFormat) uiUtil.populateFormatCard(formatCard, chosenFormat)
val listener = object : OnFormatClickListener { val listener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<Format>, item: Format) { override fun onFormatClick(allFormats: List<Format>, item: Format) {
@ -158,19 +159,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
) )
) )
val selectedContainer: String = try { containerAutoCompleteTextView!!.setText(downloadItem.format.container, false)
if (containers.contains(formats[formats.lastIndex].container)){
formats[formats.lastIndex].container
}else{
sharedPreferences.getString("audio_format", "mp3")!!
}
}catch (e: Exception){
sharedPreferences.getString("audio_format", "mp3")!!
}
downloadItem.format.container = selectedContainer
Log.e("TAG", selectedContainer)
containerAutoCompleteTextView!!.setText(selectedContainer, false)
(container!!.editText as AutoCompleteTextView?)!!.onItemClickListener = (container!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long -> AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
downloadItem.format.container = containers[index] downloadItem.format.container = containers[index]

View file

@ -81,13 +81,13 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
overScrollMode = View.OVER_SCROLL_NEVER overScrollMode = View.OVER_SCROLL_NEVER
} }
val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem), DownloadVideoFragment(resultItem)) val fragments = mutableListOf<Fragment>(DownloadAudioFragment(resultItem, null), DownloadVideoFragment(resultItem, null))
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber() val nr = commandTemplateDao.getTotalNumber()
if(nr > 0){ if(nr > 0){
fragments.add(DownloadCommandFragment(resultItem)) fragments.add(DownloadCommandFragment(resultItem, null))
}else{ }else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f (tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.alpha = 0.3f

View file

@ -31,11 +31,12 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() { class DownloadCommandFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
@ -57,7 +58,14 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java] commandTemplateViewModel = ViewModelProvider(this)[CommandTemplateViewModel::class.java]
downloadItem = downloadViewModel.createDownloadItemFromResult(resultItem, DownloadViewModel.Type.command)
downloadItem = if (currentDownloadItem != null && currentDownloadItem!!.type == DownloadViewModel.Type.command){
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
Gson().fromJson(string, DownloadItem::class.java)
}else{
downloadViewModel.createDownloadItemFromResult(resultItem, DownloadViewModel.Type.command)
}
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil) uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
@ -138,13 +146,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
} }
saveDir = view.findViewById(R.id.outputPath) saveDir = view.findViewById(R.id.outputPath)
val downloadPath = sharedPreferences.getString(
"command_path",
getString(R.string.command_path)
)
downloadItem.downloadPath = downloadPath!!
saveDir.editText!!.setText( saveDir.editText!!.setText(
fileUtil.formatPath(downloadPath) fileUtil.formatPath(downloadItem.downloadPath)
) )
saveDir.editText!!.isFocusable = false; saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true; saveDir.editText!!.isClickable = true;

View file

@ -1,52 +1,61 @@
package com.deniscerri.ytdlnis.ui.downloadcard package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.DatePickerDialog import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.app.TimePickerDialog
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import androidx.fragment.app.FragmentTransaction import android.widget.TextView
import androidx.fragment.app.findFragment import android.widget.Toast
import androidx.lifecycle.Observer import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.adapter.HomeAdapter
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.models.Format
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.util.FileUtil
import com.google.android.material.bottomappbar.BottomAppBar
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener { class DownloadMultipleBottomSheetDialog(private val items: MutableList<DownloadItem>) : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
private lateinit var downloadViewModel: DownloadViewModel private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel private lateinit var resultViewModel: ResultViewModel
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var fileUtil: FileUtil
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
fileUtil = FileUtil()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -60,12 +69,13 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
super.setupDialog(dialog, style) super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null) val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
dialog.setContentView(view) dialog.setContentView(view)
view.minimumHeight = resources.displayMetrics.heightPixels //view.minimumHeight = resources.displayMetrics.heightPixels
dialog.setOnShowListener { dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View) behavior = BottomSheetBehavior.from(view.parent as View)
behavior.skipCollapsed = true val displayMetrics = DisplayMetrics()
behavior.state = BottomSheetBehavior.STATE_EXPANDED requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
} }
listAdapter = listAdapter =
@ -77,50 +87,48 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview) recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = listAdapter recyclerView.adapter = listAdapter
listAdapter.submitList(items.toList())
downloadViewModel.processingDownloads.observe(this) {
listAdapter.submitList(it)
if (it.isEmpty()){
dismiss()
}
}
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button) val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
scheduleBtn.setOnClickListener{ scheduleBtn.setOnClickListener{
val currentDate = Calendar.getInstance() val currentDate = Calendar.getInstance()
val date = Calendar.getInstance() val date = Calendar.getInstance()
val datepicker = DatePickerDialog( val datepicker = MaterialDatePicker.Builder.datePicker()
requireContext(), .setCalendarConstraints(
{ view, year, monthOfYear, dayOfMonth -> CalendarConstraints.Builder()
date[year, monthOfYear] = dayOfMonth .setValidator(DateValidatorPointForward.now())
TimePickerDialog(context, .build()
{ _, hourOfDay, minute -> )
date[Calendar.HOUR_OF_DAY] = hourOfDay .setSelection(MaterialDatePicker.todayInUtcMilliseconds())
date[Calendar.MINUTE] = minute .build()
items.forEach { datepicker.addOnPositiveButtonClickListener{
it.downloadStartTime = date.timeInMillis date.timeInMillis = it
}
downloadViewModel.queueDownloads(items)
dismiss() val timepicker = MaterialTimePicker.Builder()
}, .setTimeFormat(TimeFormat.CLOCK_24H)
currentDate.get(Calendar.HOUR_OF_DAY), .setHour(currentDate.get(Calendar.HOUR_OF_DAY))
currentDate.get(Calendar.MINUTE), .setMinute(currentDate.get(Calendar.MINUTE))
false .build()
).show()
}, timepicker.addOnPositiveButtonClickListener{
currentDate.get(Calendar.YEAR), date[Calendar.HOUR_OF_DAY] = timepicker.hour
currentDate.get(Calendar.MONTH), date[Calendar.MINUTE] = timepicker.minute
currentDate.get(Calendar.DATE)
)
datepicker.datePicker.minDate = System.currentTimeMillis() - 1000 items.forEach { item ->
datepicker.show() item.downloadStartTime = date.timeInMillis
} }
downloadViewModel.queueDownloads(items)
dismiss()
}
timepicker.show(parentFragmentManager, "timepicker")
}
datepicker.show(parentFragmentManager, "datepicker")
val cancelBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_cancel_button)
cancelBtn.setOnClickListener{
dismiss()
} }
val download = view.findViewById<Button>(R.id.bottomsheet_download_button) val download = view.findViewById<Button>(R.id.bottomsheet_download_button)
@ -128,6 +136,40 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
downloadViewModel.queueDownloads(items) downloadViewModel.queueDownloads(items)
dismiss() dismiss()
} }
val bottomAppBar = view.findViewById<BottomAppBar>(R.id.bottomAppBar)
bottomAppBar!!.setOnMenuItemClickListener { m: MenuItem ->
val itemId = m.itemId
if (itemId == R.id.folder) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
pathResultLauncher.launch(intent)
} else if (itemId == R.id.edit){
}
true
}
}
private var pathResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let {
activity?.contentResolver?.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
items.forEach {
it.downloadPath = result.data?.data.toString()
}
Toast.makeText(requireContext(), "Changed every item's download path to: ${fileUtil.formatPath(result.data!!.data.toString())}", Toast.LENGTH_LONG).show()
}
} }
override fun onCancel(dialog: DialogInterface) { override fun onCancel(dialog: DialogInterface) {
@ -149,15 +191,13 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun onCardClick(itemID: Long) { override fun onCardClick(itemURL: String) {
lifecycleScope.launch{ lifecycleScope.launch{
val downloadItem = withContext(Dispatchers.IO){ val downloadItem = items.find { it.url == itemURL }
downloadViewModel.getItemByID(itemID)
}
val resultItem = withContext(Dispatchers.IO){ val resultItem = withContext(Dispatchers.IO){
resultViewModel.getItemByURL(downloadItem.url) resultViewModel.getItemByURL(downloadItem!!.url)
} }
val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, DownloadViewModel.Type.video) val bottomSheet = ConfigureDownloadBottomSheetDialog(resultItem, downloadItem!!, this@DownloadMultipleBottomSheetDialog)
bottomSheet.show(parentFragmentManager, "configureDownloadSingleSheet") bottomSheet.show(parentFragmentManager, "configureDownloadSingleSheet")
} }
} }
@ -166,5 +206,12 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem) {
val i = items.indexOf(items.find { it.url == item.url })
items[i] = item
Log.e("aa", items.toList().toString())
listAdapter.submitList(items.toList())
}
} }

View file

@ -30,12 +30,13 @@ import com.deniscerri.ytdlnis.util.FileUtil
import com.deniscerri.ytdlnis.util.UiUtil import com.deniscerri.ytdlnis.util.UiUtil
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() { class DownloadVideoFragment(private val resultItem: ResultItem, private var currentDownloadItem: DownloadItem?) : Fragment() {
private var _binding : FragmentHomeBinding? = null private var _binding : FragmentHomeBinding? = null
private var fragmentView: View? = null private var fragmentView: View? = null
private var activity: Activity? = null private var activity: Activity? = null
@ -60,7 +61,14 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
activity = getActivity() activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java] resultViewModel = ViewModelProvider(this@DownloadVideoFragment)[ResultViewModel::class.java]
downloadItem = downloadViewModel.createDownloadItemFromResult(resultItem, Type.video)
downloadItem = if (currentDownloadItem != null && currentDownloadItem!!.type == Type.video){
val string = Gson().toJson(currentDownloadItem, DownloadItem::class.java)
Gson().fromJson(string, DownloadItem::class.java)
}else{
downloadViewModel.createDownloadItemFromResult(resultItem, Type.video)
}
fileUtil = FileUtil() fileUtil = FileUtil()
uiUtil = UiUtil(fileUtil) uiUtil = UiUtil(fileUtil)
return fragmentView return fragmentView
@ -94,13 +102,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
}) })
saveDir = view.findViewById(R.id.outputPath) saveDir = view.findViewById(R.id.outputPath)
val downloadPath = sharedPreferences.getString(
"video_path",
getString(R.string.video_path)
)
downloadItem.downloadPath = downloadPath!!
saveDir.editText!!.setText( saveDir.editText!!.setText(
fileUtil.formatPath(downloadPath) fileUtil.formatPath(downloadItem.downloadPath)
) )
saveDir.editText!!.isFocusable = false; saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true; saveDir.editText!!.isClickable = true;
@ -120,10 +123,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
videoFormats.forEach { formats.add(Format(it, container!!,"","", "",0, it)) } videoFormats.forEach { formats.add(Format(it, container!!,"","", "",0, it)) }
} }
downloadItem.format = formats[formats.lastIndex]
val containers = requireContext().resources.getStringArray(R.array.video_containers) val containers = requireContext().resources.getStringArray(R.array.video_containers)
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer) val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
val containerAutoCompleteTextView = val containerAutoCompleteTextView =
view.findViewById<AutoCompleteTextView>(R.id.container_textview) view.findViewById<AutoCompleteTextView>(R.id.container_textview)
@ -146,7 +147,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
containerAutoCompleteTextView!!.setText(selectedContainer, false) containerAutoCompleteTextView!!.setText(selectedContainer, false)
val formatCard = view.findViewById<ConstraintLayout>(R.id.format_card_constraintLayout) val formatCard = view.findViewById<ConstraintLayout>(R.id.format_card_constraintLayout)
val chosenFormat = formats[formats.lastIndex] val chosenFormat = downloadItem.format
uiUtil.populateFormatCard(formatCard, chosenFormat) uiUtil.populateFormatCard(formatCard, chosenFormat)
val listener = object : OnFormatClickListener { val listener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<Format>, item: Format) { override fun onFormatClick(allFormats: List<Format>, item: Format) {

View file

@ -0,0 +1,141 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.adapter.PlaylistAdapter
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointForward
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class SelectPlaylistItemsBottomSheetDialog(private val items: List<ResultItem>, private val type: DownloadViewModel.Type) : BottomSheetDialogFragment(), PlaylistAdapter.OnItemClickListener {
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var listAdapter : PlaylistAdapter
private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var selectedText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val behavior = BottomSheetBehavior.from(view.parent as View)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
@SuppressLint("RestrictedApi")
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.select_playlist_items, null)
dialog.setContentView(view)
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
}
listAdapter =
PlaylistAdapter(
this,
requireActivity()
)
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = listAdapter
listAdapter.submitList(items)
selectedText = view.findViewById<Button>(R.id.selected)
selectedText.text = "0 ${resources.getString(R.string.selected)}"
val checkAll = view.findViewById<Button>(R.id.check_all)
checkAll!!.setOnClickListener {
if (listAdapter.getCheckedItems().isEmpty()){
listAdapter.checkAll()
selectedText.text = resources.getString(R.string.all_items_selected)
}else{
listAdapter.clearCheckeditems()
selectedText.text = "0 ${resources.getString(R.string.selected)}"
}
}
val ok = view.findViewById<Button>(R.id.bottomsheet_ok)
ok!!.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO){
val checkedItems = listAdapter.getCheckedItems()
val checkedResultItems = items.filter { checkedItems.contains(it.id) }
val downloadItems = mutableListOf<DownloadItem>()
checkedResultItems.forEach {c ->
val i = downloadViewModel.createDownloadItemFromResult(c,type)
i.format = downloadViewModel.getLatestCommandTemplateAsFormat()
downloadItems.add(i)
}
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadItems)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
resultViewModel.deleteAll()
resultViewModel.insert(ArrayList(checkedResultItems))
}
dismiss()
}
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
cleanup()
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
cleanup()
}
private fun cleanup(){
parentFragmentManager.beginTransaction().remove(parentFragmentManager.findFragmentByTag("downloadPlaylistSheet")!!).commit()
}
override fun onCardSelect(itemID: Long, isChecked: Boolean, checkedItems: List<Long>) {
if (checkedItems.size == items.size){
selectedText.text = resources.getString(R.string.all_items_selected)
}else{
selectedText.text = "${checkedItems.size} ${resources.getString(R.string.selected)}"
}
}
}

View file

@ -1,19 +1,17 @@
package com.deniscerri.ytdlnis.util package com.deniscerri.ytdlnis.util
import android.R.attr.src
import android.content.Context import android.content.Context
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.os.Build import android.net.Uri
import android.provider.DocumentsContract
import android.webkit.MimeTypeMap
import com.deniscerri.ytdlnis.R import com.deniscerri.ytdlnis.R
import okhttp3.internal.closeQuietly
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.*
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
@ -26,6 +24,12 @@ class FileUtil() {
} }
} }
internal object Compare {
fun max(a: File, b: File): File {
return if (a.length() > b.length()) a else b
}
}
fun exists(path: String) : Boolean { fun exists(path: String) : Boolean {
val file = File(path) val file = File(path)
if (path.isEmpty()) return false if (path.isEmpty()) return false
@ -56,18 +60,18 @@ class FileUtil() {
return formattedPath.toString() return formattedPath.toString()
} }
private fun scanMedia(destDir: String, fileName: String, context: Context) : String { private fun scanMedia(destDir: String, context: Context) : String {
val file : File val file : File
val path = File(formatPath(destDir)) val path = File(formatPath(destDir))
try { try {
file = path.listFiles()?.find { val files = path.listFiles()!!
it!!.name == fileName files.filter { it.lastModified() > System.currentTimeMillis() - 10000}
}!! Arrays.sort(files) { p0, p1 -> p0!!.lastModified().compareTo(p1!!.lastModified()) }
val paths = arrayOf(file.absolutePath) val paths = files.map { it.absolutePath }.toTypedArray()
MediaScannerConnection.scanFile(context, paths, null, null) MediaScannerConnection.scanFile(context, paths, null, null)
return paths[0] return files.reduce(Compare::max).absolutePath
}catch (e: Exception){ }catch (e: Exception){
e.printStackTrace() e.printStackTrace()
} }
@ -76,81 +80,38 @@ class FileUtil() {
} }
@Throws(Exception::class) @Throws(Exception::class)
fun moveFile(originDir: File, context: Context, destDir: String, progress: (p: Int) -> Unit) : String { fun moveFile(originDir: File, context: Context, destDir: String, progress: (p: Int) -> Unit) : String {
var fileName = ""
var maxSize = 0L
originDir.listFiles()?.forEach { originDir.listFiles()?.forEach {
if (it.name.equals("rList")){ if (it.name.equals("rList")){
it.delete() it.delete()
return@forEach return@forEach
} }
if (it.length() > maxSize){ val mimeType =
maxSize = it.length() MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
fileName = it.name
val dest = Uri.parse(destDir).run {
DocumentsContract.buildDocumentUriUsingTree(
this,
DocumentsContract.getTreeDocumentId(this)
)
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ val destUri = DocumentsContract.createDocument(
val f = File(formatPath(destDir)+"/"+it.name) context.contentResolver,
f.mkdirs() dest,
Files.move(it.toPath(), f.toPath(), StandardCopyOption.REPLACE_EXISTING) mimeType,
progress(100) it.name
}else{ ) ?: return@forEach
val f = File(formatPath(destDir)+"/"+it.name)
val inChannel: FileChannel = FileInputStream(it).channel val inputStream = it.inputStream()
val outChannel: FileChannel = FileOutputStream(f).channel val outputStream =
try { context.contentResolver.openOutputStream(destUri) ?: return@forEach
inChannel.transferTo(0, inChannel.size(), outChannel) inputStream.copyTo(outputStream)
} finally { inputStream.closeQuietly()
inChannel.close() outputStream.closeQuietly()
outChannel.close()
}
//
// val mimeType =
// MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
//
// val destination = Uri.parse(destDir).run {
// DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this))
// }
//
// val destUri = DocumentsContract.createDocument(
// context.contentResolver,
// destination,
// mimeType,
// it.name
// ) ?: return@forEach
//
// val inputStream = it.inputStream()
// val outputStream = context.contentResolver.openOutputStream(destUri) ?: return@forEach
// val fileLength = it.length()
// var bytesCopied: Long = 0
// val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
// var bytes = inputStream.read(buffer)
//
// try {
// while (bytes >= 0) {
// outputStream.write(buffer, 0, bytes)
// bytesCopied += bytes
// progress((bytesCopied * 100 / fileLength).toInt())
// bytes = inputStream.read(buffer)
// }
// }catch (e : Exception){
// e.printStackTrace()
// Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
// }
//
// inputStream.close()
// outputStream.close()
//
// it.delete()
}
} }
originDir.delete() originDir.delete()
return if (maxSize > 0L){ return scanMedia(destDir, context)
scanMedia(destDir, fileName, context)
}else{
context.getString(R.string.unfound_file)
}
} }
fun convertFileSize(s: Long): String{ fun convertFileSize(s: Long): String{

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" <vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M3,10h11v2H3V10zM3,8h11V6H3V8zM3,16h7v-2H3V16zM18.01,12.87l0.71,-0.71c0.39,-0.39 1.02,-0.39 1.41,0l0.71,0.71c0.39,0.39 0.39,1.02 0,1.41l-0.71,0.71L18.01,12.87zM17.3,13.58l-5.3,5.3V21h2.12l5.3,-5.3L17.3,13.58z"/> <path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector> </vector>

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="M3,10h11v2H3V10zM3,8h11V6H3V8zM3,16h7v-2H3V16zM18.01,12.87l0.71,-0.71c0.39,-0.39 1.02,-0.39 1.41,0l0.71,0.71c0.39,0.39 0.39,1.02 0,1.41l-0.71,0.71L18.01,12.87zM17.3,13.58l-5.3,5.3V21h2.12l5.3,-5.3L17.3,13.58z"/>
</vector>

View file

@ -0,0 +1,6 @@
<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="M3,6H1v13c0,1.1 0.9,2 2,2h17v-2H3V6z"/>
<path android:fillColor="?android:textColorPrimary" android:pathData="M21,4h-7l-2,-2H7C5.9,2 5.01,2.9 5.01,4L5,15c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6C23,4.9 22.1,4 21,4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
</vector>

View file

@ -1,86 +1,97 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:textSize="25sp"
android:padding="20dp"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:text="@string/configure_download" />
<com.google.android.material.tabs.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent"
app:tabMode="scrollable"
app:tabGravity="center"
android:id="@+id/download_tablayout" >
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/audio" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/video" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/run_command" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/download_viewpager" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:orientation="vertical"
android:padding="10dp" app:layout_constraintEnd_toEndOf="parent"
android:orientation="horizontal"> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_marginHorizontal="20dp"
android:gravity="end" android:orientation="horizontal"
android:orientation="horizontal"> android:paddingTop="20dp">
<Button <TextView
style="@style/Widget.Material3.Button.OutlinedButton.Icon" android:id="@+id/bottom_sheet_title"
android:id="@+id/bottomsheet_cancel_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/cancel" android:text="@string/download"
android:layout_marginEnd="10dp" android:textSize="25sp"
app:icon="@drawable/ic_cancel" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/configure_download"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
<Button <Button
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/bottom_sheet_ok" android:id="@+id/bottom_sheet_ok"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/ok" android:text="@string/ok"
app:icon="@drawable/ic_check" app:icon="@drawable/ic_check"
android:autoLink="all"/> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/download_tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:backgroundTint="@android:color/transparent"
app:tabGravity="fill"
app:tabMode="scrollable">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/audio" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/video" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="@string/command" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/download_viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -7,23 +7,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<com.google.android.material.button.MaterialButton
android:id="@+id/materialButton"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="end"
android:padding="5dp"
app:icon="@drawable/ic_handle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/download_card_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/download_card_view" android:id="@+id/download_card_view"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="100dp" android:layout_height="100dp"
android:layout_margin="10dp" android:layout_margin="10dp"
android:checkable="true" android:checkable="true"
@ -32,6 +18,7 @@
app:cardPreventCornerOverlap="true" app:cardPreventCornerOverlap="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="0dp"> app:strokeWidth="0dp">
@ -42,26 +29,18 @@
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:scaleType="centerCrop" /> android:scaleType="centerCrop" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/download_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:alpha="0.7"
android:indeterminate="true"
android:scaleY="100"
android:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:id="@+id/title_author" android:id="@+id/title_author"
android:layout_width="300dp" android:layout_width="0dp"
android:layout_height="70dp" android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintEnd_toStartOf="@+id/downloads_download_button_type" app:layout_constraintEnd_toStartOf="@+id/action_button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -69,7 +48,8 @@
android:id="@+id/title" android:id="@+id/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:ellipsize="end"
android:maxLines="2"
android:paddingTop="10dp" android:paddingTop="10dp"
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:shadowColor="@color/black" android:shadowColor="@color/black"
@ -77,15 +57,16 @@
android:shadowDy="4" android:shadowDy="4"
android:shadowRadius="2" android:shadowRadius="2"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="17sp" android:textSize="15sp"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/author" android:id="@+id/subtitle"
android:layout_width="296dp" android:layout_width="296dp"
android:layout_height="23dp" android:layout_height="20dp"
android:ellipsize="end"
android:gravity="bottom" android:gravity="bottom"
android:paddingStart="10dp" android:maxLines="1"
android:paddingEnd="10dp" android:paddingEnd="10dp"
android:paddingBottom="5dp" android:paddingBottom="5dp"
android:shadowColor="@color/black" android:shadowColor="@color/black"
@ -93,61 +74,87 @@
android:shadowDy="4" android:shadowDy="4"
android:shadowRadius="1.5" android:shadowRadius="1.5"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="12sp" android:textSize="11sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
<LinearLayout
<TextView android:layout_width="match_parent"
android:id="@+id/container" android:layout_height="0dp"
android:layout_width="wrap_content" android:paddingStart="10dp"
android:layout_height="wrap_content" android:paddingEnd="10dp"
android:padding="5dp" android:gravity="bottom"
android:textStyle="bold" android:paddingBottom="5dp"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/filesize" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/downloads_download_button_type" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_author">
<TextView <TextView
android:id="@+id/filesize" android:id="@+id/format_note"
android:layout_width="wrap_content" style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:padding="5dp" android:layout_height="wrap_content"
android:textStyle="bold" android:layout_marginEnd="5dp"
android:shadowColor="@color/black" android:background="?attr/colorPrimary"
android:shadowDx="4" android:clickable="false"
android:shadowDy="4" android:gravity="center"
android:shadowRadius="1.5" android:minWidth="30dp"
app:layout_constraintBottom_toBottomOf="parent" android:paddingHorizontal="10dp"
app:layout_constraintEnd_toStartOf="@+id/quality" /> app:cornerRadius="10dp"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/codec"
style="@style/Widget.Material3.FloatingActionButton.Large.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorSecondary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/file_size"
style="@style/Widget.Material3.FloatingActionButton.Large.Tertiary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorTertiary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<TextView
android:id="@+id/quality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textStyle="bold"
android:shadowColor="@color/black"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="1.5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/downloads_download_button_type" android:id="@+id/action_button"
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_margin="10dp" android:layout_margin="10dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:cornerRadius="10dp" app:cornerRadius="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View file

@ -3,113 +3,82 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/configure_download"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/materialButton"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="end"
app:icon="@drawable/ic_format"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/materialButton2"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/materialButton2"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="end"
app:icon="@drawable/ic_download_type"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/constraintLayout"> android:orientation="vertical"
android:elevation="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<RelativeLayout <com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/relativeLayout" android:id="@+id/bottomAppBar"
style="@style/Widget.Material3.BottomAppBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="@menu/download_multiple_menu" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/edit_selected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:srcCompat="@drawable/ic_edit" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="150dp"
android:focusableInTouchMode="true"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<LinearLayout <LinearLayout
android:id="@+id/linearLayout" android:id="@+id/linearLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:orientation="vertical"
android:orientation="horizontal" app:layout_constraintEnd_toEndOf="parent"
android:padding="10dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
<Button
android:id="@+id/bottomsheet_schedule_button"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_clock" />
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_marginHorizontal="20dp"
android:gravity="end" android:orientation="horizontal"
android:orientation="horizontal"> android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download"
android:textSize="25sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/configure_download"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_title" />
<Button <Button
android:id="@+id/bottomsheet_cancel_button" android:id="@+id/bottomsheet_schedule_button"
style="@style/Widget.Material3.Button.OutlinedButton.Icon" style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:text="@string/cancel" app:icon="@drawable/ic_clock"
app:icon="@drawable/ic_cancel" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
app:layout_constraintTop_toTopOf="parent" />
<Button <Button
android:id="@+id/bottomsheet_download_button" android:id="@+id/bottomsheet_download_button"
@ -118,10 +87,26 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="all" android:autoLink="all"
android:text="@string/download" android:text="@string/download"
app:icon="@drawable/ic_down" /> app:icon="@drawable/ic_down"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview"
android:layout_width="match_parent"
android:paddingTop="10dp"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:paddingBottom="70dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playlist_card_constraintLayout"
android:layout_width="match_parent"
android:layout_marginBottom="10dp"
android:layout_height="50dp">
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/downloads_image_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintDimensionRatio="H,16:9"
android:layout_marginEnd="10dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
app:layout_constraintStart_toEndOf="@+id/checkBox" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="15dp"
android:lines="2"
android:maxLines="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/downloads_image_view"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:elevation="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
style="@style/Widget.Material3.BottomAppBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<Button
style="?attr/materialIconButtonStyle"
android:id="@+id/check_all"
android:padding="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/ic_select_all" />
<TextView
android:id="@+id/selected"
android:layout_width="wrap_content"
android:textStyle="bold"
android:paddingStart="17dp"
android:paddingEnd="0dp"
android:layout_height="wrap_content" />
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/edit_selected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:srcCompat="@drawable/ic_edit" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/select"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bottomsheet_ok"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/ok"
app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview"
android:layout_width="match_parent"
android:paddingTop="10dp"
tools:listitem="@layout/playlist_item"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:paddingBottom="70dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,19 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
<item
android:id="@+id/folder"
android:title="@string/directories"
android:icon="@drawable/ic_folders"
app:showAsAction="collapseActionView|ifRoom"/>
<item
android:id="@+id/edit"
android:title="@string/directories"
android:icon="@drawable/ic_edit_multiple"
app:showAsAction="collapseActionView|ifRoom"/>
</menu>

View file

@ -203,4 +203,7 @@
<string name="preferred_download_type">Preferred Download Type</string> <string name="preferred_download_type">Preferred Download Type</string>
<string name="preferred_download_type_summary">Default tab the download card will open to</string> <string name="preferred_download_type_summary">Default tab the download card will open to</string>
<string name="confirm_delete_logs_desc">Delete the entire log list, permanently.</string> <string name="confirm_delete_logs_desc">Delete the entire log list, permanently.</string>
<string name="select">Select</string>
<string name="selected">Selected</string>
<string name="all_items_selected">All items selected</string>
</resources> </resources>