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>
<option name="values">
<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>
</option>
</PersistentState>
@ -330,7 +330,7 @@
</option>
<option name="values">
<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" />
</map>
</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.progressindicator.LinearProgressIndicator
import com.squareup.picasso.Picasso
import okhttp3.internal.format
import java.util.*
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
// Author ----------------------------------
val author = card.findViewById<TextView>(R.id.author)
val author = card.findViewById<TextView>(R.id.subtitle)
author.text = item.author
// Container ----------------------------------
val container = card.findViewById<TextView>(R.id.container)
container.text = item.format.container.uppercase(Locale.getDefault())
// Format Note ----------------------------------
val formatNote = card.findViewById<TextView>(R.id.format_note)
if (item.format.format_note.isNotEmpty()){
formatNote.text = item.format.format_note.uppercase(Locale.getDefault())
}else{
formatNote.visibility = View.GONE
}
// File Size
val fileSize = card.findViewById<TextView>(R.id.filesize)
val formattedSize = fileUtil.convertFileSize(item.format.filesize)
fileSize.text = if (formattedSize == "?") "" else formattedSize.uppercase(Locale.getDefault())
val codec = card.findViewById<TextView>(R.id.codec)
val codecText =
if (item.format.encoding != "") {
item.format.encoding.uppercase()
}else if (item.format.vcodec != "none" && item.format.vcodec != ""){
item.format.vcodec.uppercase()
} else {
item.format.acodec.uppercase()
}
if (codecText == "" || codecText == "none"){
codec.visibility = View.GONE
}else{
codec.visibility = View.VISIBLE
codec.text = codecText
}
// Quality
val quality = card.findViewById<TextView>(R.id.quality)
quality.text = item.format.format_note
val fileSize = card.findViewById<TextView>(R.id.file_size)
Log.e("aa", item.format.filesize.toString())
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
val btn = card.findViewById<MaterialButton>(R.id.downloads_download_button_type)
val btn = card.findViewById<MaterialButton>(R.id.action_button)
when(item.type) {
DownloadViewModel.Type.audio -> {
@ -109,23 +131,23 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
}
card.setOnClickListener {
onItemClickListener.onCardClick(item.id)
onItemClickListener.onCardClick(item.url)
}
}
interface OnItemClickListener {
fun onButtonClick(videoURL: String, type: String?)
fun onCardClick(itemID: Long)
fun onCardClick(itemURL: String)
}
companion object {
private val DIFF_CALLBACK: DiffUtil.ItemCallback<DownloadItem> = object : DiffUtil.ItemCallback<DownloadItem>() {
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 {
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.card.MaterialCardView
import com.squareup.picasso.Picasso
import okhttp3.internal.format
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
@ -48,7 +49,7 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
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)
}
@ -56,6 +57,17 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
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
@ -64,12 +76,11 @@ class GenericDownloadAdapter(onItemClickListener: OnItemClickListener, activity:
}
itemTitle.text = title
// TITLE ----------------------------------
val itemUrl = card.findViewById<TextView>(R.id.url)
val itemUrl = card.findViewById<TextView>(R.id.subtitle)
itemUrl.text = item.url
val type = card.findViewById<TextView>(R.id.type)
type!!.text = item.type.toString().uppercase()
val formatNote = card.findViewById<TextView>(R.id.format_note)
formatNote!!.text = item.format.format_note.uppercase()
val codec = card.findViewById<TextView>(R.id.codec)
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")
fun getResults() : LiveData<List<ResultItem>>
@Query("SELECT COUNT(id) FROM results")
fun getCount() : LiveData<Int>
@Query("SELECT * FROM results LIMIT 1")
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) {
private val tag: String = "ResultRepository"
val allResults : LiveData<List<ResultItem>> = resultDao.getResults()
var itemCount = MutableLiveData(0)
var itemCount = MutableLiveData(-1)
suspend fun insert(it: ResultItem){
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> {
val list : MutableList<DownloadItem> = mutableListOf()
items.forEach {

View file

@ -14,22 +14,29 @@ import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.provider.Settings
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.Button
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
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.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.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
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.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
@ -111,7 +118,11 @@ class ShareActivity : AppCompatActivity() {
if (it.isNotEmpty() && supportFragmentManager.findFragmentByTag("downloadSingleSheet") == null){
if(resultViewModel.itemCount.value!! == it.size){
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() {
if (Build.VERSION.SDK_INT < VERSION_CODES.TIRAMISU && !checkFilePermission()) {
ActivityCompat.requestPermissions(

View file

@ -134,8 +134,7 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
resultViewModel.items.observe(viewLifecycleOwner) {
homeAdapter!!.submitList(it)
resultsList = it
Log.e(TAG, resultViewModel.itemCount.toString())
if(resultViewModel.itemCount.value!! > 1){
if(resultViewModel.itemCount.value!! > 1 || resultViewModel.itemCount.value!! == -1){
if (it[0].playlistTitle.isNotEmpty() && it[0].playlistTitle != getString(R.string.trendingPlaylist)){
downloadAllFabCoordinator!!.visibility = VISIBLE
}else{
@ -388,30 +387,20 @@ class HomeFragment : Fragment(), HomeAdapter.OnItemClickListener, View.OnClickLi
if (viewIdName.isNotEmpty()) {
if (viewIdName == "downloadSelected") {
val downloadList = downloadViewModel.turnResultItemsToDownloadItems(selectedObjects!!)
downloadViewModel.putDownloadsForProcessing(selectedObjects!!, downloadList).observe(viewLifecycleOwner) {
it.forEachIndexed { i, itemID ->
downloadList[i].id = itemID
}
if (sharedPreferences!!.getBoolean("download_card", true)) {
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} else {
downloadViewModel.queueDownloads(downloadList)
}
if (sharedPreferences!!.getBoolean("download_card", true)) {
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} else {
downloadViewModel.queueDownloads(downloadList)
}
}
if (viewIdName == "downloadAll") {
val downloadList = downloadViewModel.turnResultItemsToDownloadItems(resultsList!!)
downloadViewModel.putDownloadsForProcessing(resultsList!!, downloadList).observe(viewLifecycleOwner) {
it.forEachIndexed { i, itemID ->
downloadList[i].id = itemID
}
if (sharedPreferences!!.getBoolean("download_card", true)) {
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList)
bottomSheet.show(parentFragmentManager, "downloadMultipleSheet")
} else {
downloadViewModel.queueDownloads(downloadList)
}
if (sharedPreferences!!.getBoolean("download_card", true)) {
val bottomSheet = DownloadMultipleBottomSheetDialog(downloadList.toMutableList())
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.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
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.ResultItem
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
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.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.tabs.TabLayout
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel.Type
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 viewPager2: ViewPager2
private lateinit var fragmentAdapter : DownloadFragmentAdapter
private lateinit var downloadViewModel: DownloadViewModel
private lateinit var resultViewModel: ResultViewModel
private lateinit var commandTemplateDao: CommandTemplateDao
private lateinit var onDownloadItemUpdateListener: OnDownloadItemUpdateListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
commandTemplateDao = DBManager.getInstance(requireContext()).commandTemplateDao
onDownloadItemUpdateListener = listener
}
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)
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{
withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber()
if(nr > 0){
fragments.add(DownloadCommandFragment(resultItem))
fragments.add(DownloadCommandFragment(resultItem, downloadItem))
}else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false
}
@ -77,7 +78,6 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
}
val fragmentManager = parentFragmentManager
fragmentAdapter = DownloadFragmentAdapter(
resultItem,
@ -88,7 +88,7 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
viewPager2.adapter = fragmentAdapter
viewPager2.isSaveFromParentEnabled = false
when(type) {
when(downloadItem.type) {
Type.audio -> {
tabLayout.selectTab(tabLayout.getTabAt(0))
viewPager2.setCurrentItem(0, false)
@ -99,7 +99,9 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
}
else -> {
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
}
})
val cancelBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_cancel_button)
cancelBtn.setOnClickListener{
dismiss()
}
viewPager2.setPageTransformer(BackgroundToForegroundPageTransformer())
val ok = view.findViewById<Button>(R.id.bottom_sheet_ok)
ok!!.setOnClickListener {
@ -144,12 +142,18 @@ class ConfigureDownloadBottomSheetDialog(private val resultItem: ResultItem, pri
f.downloadItem
}
}
downloadViewModel.updateDownload(item)
Log.e("aa", item.toString())
onDownloadItemUpdateListener.onDownloadItemUpdate(resultItem.id, item)
dismiss()
}
}
interface OnDownloadItemUpdateListener {
fun onDownloadItemUpdate(resultItemID: Long, item: DownloadItem)
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
//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.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
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 fragmentView: View? = 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 saveDir : TextInputLayout
lateinit var downloadItem: DownloadItem
lateinit var downloadItem : DownloadItem
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -57,7 +57,14 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
fragmentView = inflater.inflate(R.layout.fragment_download_audio, container, false)
activity = getActivity()
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()
uiUtil = UiUtil(fileUtil)
return fragmentView
@ -90,14 +97,8 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
}
})
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(
fileUtil.formatPath(downloadPath)
fileUtil.formatPath(downloadItem.downloadPath)
)
saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true;
@ -121,7 +122,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
if (formats.isEmpty()) {
formatCard.visibility = View.GONE
} else {
val chosenFormat = formats[formats.lastIndex]
val chosenFormat = downloadItem.format
uiUtil.populateFormatCard(formatCard, chosenFormat)
val listener = object : OnFormatClickListener {
override fun onFormatClick(allFormats: List<Format>, item: Format) {
@ -158,19 +159,7 @@ class DownloadAudioFragment(private var resultItem: ResultItem) : Fragment() {
)
)
val selectedContainer: String = try {
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)
containerAutoCompleteTextView!!.setText(downloadItem.format.container, false)
(container!!.editText as AutoCompleteTextView?)!!.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, index: Int, _: Long ->
downloadItem.format.container = containers[index]

View file

@ -81,13 +81,13 @@ class DownloadBottomSheetDialog(private val resultItem: ResultItem, private val
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{
withContext(Dispatchers.IO){
val nr = commandTemplateDao.getTotalNumber()
if(nr > 0){
fragments.add(DownloadCommandFragment(resultItem))
fragments.add(DownloadCommandFragment(resultItem, null))
}else{
(tabLayout.getChildAt(0) as? ViewGroup)?.getChildAt(2)?.isEnabled = false
(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.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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 fragmentView: View? = null
private var activity: Activity? = null
@ -57,7 +58,14 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::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()
uiUtil = UiUtil(fileUtil)
return fragmentView
@ -138,13 +146,8 @@ class DownloadCommandFragment(private val resultItem: ResultItem) : Fragment() {
}
saveDir = view.findViewById(R.id.outputPath)
val downloadPath = sharedPreferences.getString(
"command_path",
getString(R.string.command_path)
)
downloadItem.downloadPath = downloadPath!!
saveDir.editText!!.setText(
fileUtil.formatPath(downloadPath)
fileUtil.formatPath(downloadItem.downloadPath)
)
saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true;

View file

@ -1,52 +1,61 @@
package com.deniscerri.ytdlnis.ui.downloadcard
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.Activity
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.widget.Button
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.findFragment
import androidx.lifecycle.Observer
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.adapter.ConfigureMultipleDownloadsAdapter
import com.deniscerri.ytdlnis.adapter.HomeAdapter
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.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.BottomSheetDialog
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.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.*
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 resultViewModel: ResultViewModel
private lateinit var listAdapter : ConfigureMultipleDownloadsAdapter
private lateinit var recyclerView: RecyclerView
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var fileUtil: FileUtil
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
fileUtil = FileUtil()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -60,12 +69,13 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
super.setupDialog(dialog, style)
val view = LayoutInflater.from(context).inflate(R.layout.download_multiple_bottom_sheet, null)
dialog.setContentView(view)
view.minimumHeight = resources.displayMetrics.heightPixels
//view.minimumHeight = resources.displayMetrics.heightPixels
dialog.setOnShowListener {
behavior = BottomSheetBehavior.from(view.parent as View)
behavior.skipCollapsed = true
behavior.state = BottomSheetBehavior.STATE_EXPANDED
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
behavior.peekHeight = displayMetrics.heightPixels / 2
}
listAdapter =
@ -77,50 +87,48 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
recyclerView = view.findViewById(R.id.downloadMultipleRecyclerview)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = listAdapter
downloadViewModel.processingDownloads.observe(this) {
listAdapter.submitList(it)
if (it.isEmpty()){
dismiss()
}
}
listAdapter.submitList(items.toList())
val scheduleBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_schedule_button)
scheduleBtn.setOnClickListener{
val currentDate = Calendar.getInstance()
val date = Calendar.getInstance()
val datepicker = DatePickerDialog(
requireContext(),
{ view, year, monthOfYear, dayOfMonth ->
date[year, monthOfYear] = dayOfMonth
TimePickerDialog(context,
{ _, hourOfDay, minute ->
date[Calendar.HOUR_OF_DAY] = hourOfDay
date[Calendar.MINUTE] = minute
val datepicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(
CalendarConstraints.Builder()
.setValidator(DateValidatorPointForward.now())
.build()
)
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
.build()
items.forEach {
it.downloadStartTime = date.timeInMillis
}
downloadViewModel.queueDownloads(items)
dismiss()
},
currentDate.get(Calendar.HOUR_OF_DAY),
currentDate.get(Calendar.MINUTE),
false
).show()
},
currentDate.get(Calendar.YEAR),
currentDate.get(Calendar.MONTH),
currentDate.get(Calendar.DATE)
)
datepicker.datePicker.minDate = System.currentTimeMillis() - 1000
datepicker.show()
}
datepicker.addOnPositiveButtonClickListener{
date.timeInMillis = it
val timepicker = MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H)
.setHour(currentDate.get(Calendar.HOUR_OF_DAY))
.setMinute(currentDate.get(Calendar.MINUTE))
.build()
timepicker.addOnPositiveButtonClickListener{
date[Calendar.HOUR_OF_DAY] = timepicker.hour
date[Calendar.MINUTE] = timepicker.minute
items.forEach { item ->
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)
@ -128,6 +136,40 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
downloadViewModel.queueDownloads(items)
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) {
@ -149,15 +191,13 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
TODO("Not yet implemented")
}
override fun onCardClick(itemID: Long) {
override fun onCardClick(itemURL: String) {
lifecycleScope.launch{
val downloadItem = withContext(Dispatchers.IO){
downloadViewModel.getItemByID(itemID)
}
val downloadItem = items.find { it.url == itemURL }
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")
}
}
@ -166,5 +206,12 @@ class DownloadMultipleBottomSheetDialog(private val items: List<DownloadItem>) :
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.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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 fragmentView: View? = null
private var activity: Activity? = null
@ -60,7 +61,14 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
activity = getActivity()
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::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()
uiUtil = UiUtil(fileUtil)
return fragmentView
@ -94,13 +102,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
})
saveDir = view.findViewById(R.id.outputPath)
val downloadPath = sharedPreferences.getString(
"video_path",
getString(R.string.video_path)
)
downloadItem.downloadPath = downloadPath!!
saveDir.editText!!.setText(
fileUtil.formatPath(downloadPath)
fileUtil.formatPath(downloadItem.downloadPath)
)
saveDir.editText!!.isFocusable = false;
saveDir.editText!!.isClickable = true;
@ -120,10 +123,8 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
videoFormats.forEach { formats.add(Format(it, container!!,"","", "",0, it)) }
}
downloadItem.format = formats[formats.lastIndex]
val containers = requireContext().resources.getStringArray(R.array.video_containers)
val container = view.findViewById<TextInputLayout>(R.id.downloadContainer)
val containerAutoCompleteTextView =
view.findViewById<AutoCompleteTextView>(R.id.container_textview)
@ -146,7 +147,7 @@ class DownloadVideoFragment(private val resultItem: ResultItem) : Fragment() {
containerAutoCompleteTextView!!.setText(selectedContainer, false)
val formatCard = view.findViewById<ConstraintLayout>(R.id.format_card_constraintLayout)
val chosenFormat = formats[formats.lastIndex]
val chosenFormat = downloadItem.format
uiUtil.populateFormatCard(formatCard, chosenFormat)
val listener = object : OnFormatClickListener {
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
import android.R.attr.src
import android.content.Context
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 okhttp3.internal.closeQuietly
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URLDecoder
import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.text.DecimalFormat
import java.util.*
import kotlin.math.log10
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 {
val file = File(path)
if (path.isEmpty()) return false
@ -56,18 +60,18 @@ class FileUtil() {
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 path = File(formatPath(destDir))
try {
file = path.listFiles()?.find {
it!!.name == fileName
}!!
val files = path.listFiles()!!
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)
return paths[0]
return files.reduce(Compare::max).absolutePath
}catch (e: Exception){
e.printStackTrace()
}
@ -76,81 +80,38 @@ class FileUtil() {
}
@Throws(Exception::class)
fun moveFile(originDir: File, context: Context, destDir: String, progress: (p: Int) -> Unit) : String {
var fileName = ""
var maxSize = 0L
originDir.listFiles()?.forEach {
if (it.name.equals("rList")){
it.delete()
return@forEach
}
if (it.length() > maxSize){
maxSize = it.length()
fileName = it.name
val mimeType =
MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
val dest = Uri.parse(destDir).run {
DocumentsContract.buildDocumentUriUsingTree(
this,
DocumentsContract.getTreeDocumentId(this)
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
val f = File(formatPath(destDir)+"/"+it.name)
f.mkdirs()
Files.move(it.toPath(), f.toPath(), StandardCopyOption.REPLACE_EXISTING)
progress(100)
}else{
val f = File(formatPath(destDir)+"/"+it.name)
val destUri = DocumentsContract.createDocument(
context.contentResolver,
dest,
mimeType,
it.name
) ?: return@forEach
val inChannel: FileChannel = FileInputStream(it).channel
val outChannel: FileChannel = FileOutputStream(f).channel
try {
inChannel.transferTo(0, inChannel.size(), outChannel)
} finally {
inChannel.close()
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()
}
val inputStream = it.inputStream()
val outputStream =
context.contentResolver.openOutputStream(destUri) ?: return@forEach
inputStream.copyTo(outputStream)
inputStream.closeQuietly()
outputStream.closeQuietly()
}
originDir.delete()
return if (maxSize > 0L){
scanMedia(destDir, fileName, context)
}else{
context.getString(R.string.unfound_file)
}
return scanMedia(destDir, context)
}
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: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>

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"?>
<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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
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
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:padding="10dp"
android:orientation="horizontal">
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:gravity="end"
android:orientation="horizontal">
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<Button
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:id="@+id/bottomsheet_cancel_button"
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel"
android:layout_marginEnd="10dp"
app:icon="@drawable/ic_cancel" />
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
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:id="@+id/bottom_sheet_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"
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>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -7,23 +7,9 @@
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
android:id="@+id/download_card_view"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_margin="10dp"
android:checkable="true"
@ -32,6 +18,7 @@
app:cardPreventCornerOverlap="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="0dp">
@ -42,26 +29,18 @@
android:adjustViewBounds="true"
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
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/title_author"
android:layout_width="300dp"
android:layout_height="70dp"
android:layout_width="0dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_height="wrap_content"
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_constraintTop_toTopOf="parent">
@ -69,7 +48,8 @@
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:ellipsize="end"
android:maxLines="2"
android:paddingTop="10dp"
android:paddingEnd="10dp"
android:shadowColor="@color/black"
@ -77,15 +57,16 @@
android:shadowDy="4"
android:shadowRadius="2"
android:textColor="@color/white"
android:textSize="17sp"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/author"
android:id="@+id/subtitle"
android:layout_width="296dp"
android:layout_height="23dp"
android:layout_height="20dp"
android:ellipsize="end"
android:gravity="bottom"
android:paddingStart="10dp"
android:maxLines="1"
android:paddingEnd="10dp"
android:paddingBottom="5dp"
android:shadowColor="@color/black"
@ -93,61 +74,87 @@
android:shadowDy="4"
android:shadowRadius="1.5"
android:textColor="@color/white"
android:textSize="12sp"
android:textSize="11sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/container"
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"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:gravity="bottom"
android:paddingBottom="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/filesize"
app:layout_constraintTop_toBottomOf="@+id/downloads_download_button_type" />
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_author">
<TextView
android:id="@+id/filesize"
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_toStartOf="@+id/quality" />
<TextView
android:id="@+id/format_note"
style="@style/Widget.Material3.FloatingActionButton.Large.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:background="?attr/colorPrimary"
android:clickable="false"
android:gravity="center"
android:minWidth="30dp"
android:paddingHorizontal="10dp"
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
android:id="@+id/downloads_download_button_type"
android:id="@+id/action_button"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Icon.Secondary"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="10dp"
android:layout_marginEnd="8dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_video"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

View file

@ -3,113 +3,82 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
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
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
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
android:id="@+id/relativeLayout"
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomAppBar"
style="@style/Widget.Material3.BottomAppBar"
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>
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent">
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<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
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:gravity="end"
android:orientation="horizontal">
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: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
android:id="@+id/bottomsheet_cancel_button"
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
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"
android:text="@string/cancel"
app:icon="@drawable/ic_cancel" />
app:icon="@drawable/ic_clock"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_download_button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/bottomsheet_download_button"
@ -118,10 +87,26 @@
android:layout_height="wrap_content"
android:autoLink="all"
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>
</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_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="select">Select</string>
<string name="selected">Selected</string>
<string name="all_items_selected">All items selected</string>
</resources>