add function to modify selected items in multiple download card

This commit is contained in:
deniscerri 2025-05-31 16:14:59 +02:00
parent ffc4ded5af
commit ece2cdf631
No known key found for this signature in database
GPG key ID: 95C43D517D830350
17 changed files with 751 additions and 131 deletions

View file

@ -54,11 +54,16 @@ interface DownloadDao {
""") """)
fun getProcessingDownloadTypes() : List<String> fun getProcessingDownloadTypes() : List<String>
@Query("""
SELECT DISTINCT container from downloads where status = 'Processing' @Query("SELECT DISTINCT type from downloads where status = 'Processing' and id in (:ids)")
""") fun getProcessingDownloadTypesByIDs(ids: List<Long>) : List<String>
@Query("""SELECT DISTINCT container from downloads where status = 'Processing'""")
fun getProcessingDownloadContainers() : List<String> fun getProcessingDownloadContainers() : List<String>
@Query("""SELECT DISTINCT container from downloads where id in (:ids)""")
fun getDownloadContainersByIDs(ids: List<Long>) : List<String>
@Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)") @Query("UPDATE downloads set status = 'Processing' WHERE id in (:ids)")
suspend fun updateItemsToProcessing(ids: List<Long>) suspend fun updateItemsToProcessing(ids: List<Long>)
@ -74,9 +79,15 @@ interface DownloadDao {
@Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'") @Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'")
suspend fun updateProcessingDownloadPath(path: String) suspend fun updateProcessingDownloadPath(path: String)
@Query("UPDATE downloads set downloadPath=:path WHERE id in (:ids)")
suspend fun updateDownloadPathByIDs(ids: List<Long>, path: String)
@Query("UPDATE downloads set container=:cont WHERE status ='Processing'") @Query("UPDATE downloads set container=:cont WHERE status ='Processing'")
suspend fun updateProcessingContainer(cont: String) suspend fun updateProcessingContainer(cont: String)
@Query("UPDATE downloads set container=:cont WHERE id in (:ids)")
suspend fun updateContainerByIds(ids: List<Long>, cont: String)
@Query("SELECT * FROM downloads WHERE status='Active'") @Query("SELECT * FROM downloads WHERE status='Active'")
fun getActiveDownloadsList() : List<DownloadItem> fun getActiveDownloadsList() : List<DownloadItem>
@ -356,6 +367,12 @@ interface DownloadDao {
@Query("UPDATE downloads set incognito=:incognito WHERE status='Processing'") @Query("UPDATE downloads set incognito=:incognito WHERE status='Processing'")
suspend fun updateProcessingIncognito(incognito: Boolean) suspend fun updateProcessingIncognito(incognito: Boolean)
@Query("UPDATE downloads set incognito=:incognito WHERE id in (:ids)")
suspend fun updateIncognitoByIDs(incognito: Boolean, ids: List<Long>)
@Query("SELECT COUNT(id) FROM downloads WHERE status='Processing' AND incognito='1'") @Query("SELECT COUNT(id) FROM downloads WHERE status='Processing' AND incognito='1'")
fun getProcessingAsIncognitoCount(): Int fun getProcessingAsIncognitoCount(): Int
@Query("SELECT COUNT(id) FROM downloads WHERE status='Processing' AND incognito='1' and id in (:ids)")
fun getProcessingAsIncognitoCountByIDs(ids: List<Long>): Int
} }

View file

@ -140,7 +140,7 @@ class DownloadRepository(private val downloadDao: DownloadDao) {
downloadDao.deleteProcessingByUrl(url) downloadDao.deleteProcessingByUrl(url)
} }
fun getProcessingDownloads() : List<DownloadItem> { fun getAllProcessingDownloads() : List<DownloadItem> {
return downloadDao.getProcessingDownloadsList() return downloadDao.getProcessingDownloadsList()
} }

View file

@ -890,7 +890,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
suspend fun queueProcessingDownloads() : QueueDownloadsResult { suspend fun queueProcessingDownloads() : QueueDownloadsResult {
val processingItems = repository.getProcessingDownloads() val processingItems = repository.getAllProcessingDownloads()
return queueDownloads(processingItems) return queueDownloads(processingItems)
} }
@ -1087,13 +1087,22 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() }) return dbManager.downloadDao.getDownloadIDsNotPresentInList(items.ifEmpty { listOf(-1L) }, status.map { it.toString() })
} }
suspend fun moveProcessingToSavedCategory(){ suspend fun moveProcessingToSavedCategory(selectedItems: List<Long>?){
dao.updateProcessingtoSavedStatus() if (selectedItems.isNullOrEmpty()) {
dao.updateProcessingtoSavedStatus()
}else {
repository.setDownloadStatusMultiple(selectedItems, DownloadRepository.Status.Saved)
}
} }
fun updateAllProcessingFormats(formatTuples : List<MultipleItemFormatTuple>) = viewModelScope.launch(Dispatchers.IO) { fun updateAllProcessingFormats(selectedItems: List<Long>?, formatTuples : List<MultipleItemFormatTuple>) = viewModelScope.launch(Dispatchers.IO) {
val items = repository.getProcessingDownloads() val items = if (selectedItems.isNullOrEmpty()) {
repository.getAllProcessingDownloads()
}else {
repository.getAllItemsByIDs(selectedItems)
}
items.forEach { items.forEach {
val ft = formatTuples.first { ft -> ft.url == it.url }.formatTuple val ft = formatTuples.first { ft -> ft.url == it.url }.formatTuple
ft.format?.apply { ft.format?.apply {
@ -1112,28 +1121,48 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
suspend fun updateProcessingCommandFormat(format: Format){ suspend fun updateProcessingCommandFormat(selectedItems: List<Long>?, format: Format){
val items = repository.getProcessingDownloads() val items = if (selectedItems.isNullOrEmpty()) {
repository.getAllProcessingDownloads()
}else {
repository.getAllItemsByIDs(selectedItems)
}
items.forEach { items.forEach {
it.format = format it.format = format
repository.update(it) repository.update(it)
} }
} }
suspend fun updateProcessingContainer(cont: String) { suspend fun updateProcessingContainer(checkedItems: List<Long>?, cont: String) {
var container = "" var container = ""
if (cont != resources.getString(R.string.defaultValue)) { if (cont != resources.getString(R.string.defaultValue)) {
container = cont container = cont
} }
dao.updateProcessingContainer(container)
if (checkedItems.isNullOrEmpty()) {
dao.updateProcessingContainer(container)
}else {
dao.updateContainerByIds(checkedItems, container)
}
} }
suspend fun updateProcessingDownloadPath(path: String){ suspend fun updateProcessingDownloadPath(selectedItems: List<Long>?, path: String){
dao.updateProcessingDownloadPath(path) if (selectedItems.isNullOrEmpty()) {
dao.updateProcessingDownloadPath(path)
}else {
dao.updateDownloadPathByIDs(selectedItems, path)
}
} }
fun getProcessingDownloads() : List<DownloadItem> { fun getProcessingDownloads(checkedItems: List<Long>?) : List<DownloadItem> {
return repository.getProcessingDownloads() return if (checkedItems.isNullOrEmpty()) {
repository.getAllProcessingDownloads()
}else {
repository.getAllItemsByIDs(checkedItems)
}
} }
fun updateDownloadItemFormats(id: Long, list: List<Format>) = viewModelScope.launch(Dispatchers.IO) { fun updateDownloadItemFormats(id: Long, list: List<Format>) = viewModelScope.launch(Dispatchers.IO) {
@ -1174,9 +1203,14 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
resultRepository.deleteByUrl(url) resultRepository.deleteByUrl(url)
} }
suspend fun continueUpdatingFormatsOnBackground(){ suspend fun continueUpdatingFormatsOnBackground(selectedItems: List<Long>?){
val ids = repository.getProcessingDownloads().map { it.id } val ids = if (selectedItems.isNullOrEmpty()) {
dao.updateProcessingtoSavedStatus() repository.getAllProcessingDownloads().map { it.id }
}else {
selectedItems
}
moveProcessingToSavedCategory(selectedItems)
val id = System.currentTimeMillis().toInt() val id = System.currentTimeMillis().toInt()
val workRequest = OneTimeWorkRequestBuilder<UpdateMultipleDownloadsFormatsWorker>() val workRequest = OneTimeWorkRequestBuilder<UpdateMultipleDownloadsFormatsWorker>()
@ -1213,8 +1247,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
suspend fun updateProcessingType(newType: Type) { suspend fun updateProcessingType(selectedItems: List<Long>?, newType: Type) {
val processing = repository.getProcessingDownloads() val processing = if (selectedItems.isNullOrEmpty()) {
repository.getAllProcessingDownloads()
}else{
repository.getAllItemsByIDs(selectedItems)
}
processing.apply { processing.apply {
val new = switchDownloadType(this, newType) val new = switchDownloadType(this, newType)
new.forEach { new.forEach {
@ -1224,7 +1263,7 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
} }
suspend fun updateProcessingDownloadTimeAndQueueScheduled(time: Long) : QueueDownloadsResult { suspend fun updateProcessingDownloadTimeAndQueueScheduled(time: Long) : QueueDownloadsResult {
val processing = repository.getProcessingDownloads() val processing = repository.getAllProcessingDownloads()
processing.forEach { processing.forEach {
it.downloadStartTime = time it.downloadStartTime = time
it.status = DownloadRepository.Status.Scheduled.toString() it.status = DownloadRepository.Status.Scheduled.toString()
@ -1232,8 +1271,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
return queueDownloads(processing) return queueDownloads(processing)
} }
fun checkIfAllProcessingItemsHaveSameType() : Pair<Boolean, Type> { fun checkIfAllProcessingItemsHaveSameType(selectedItems: List<Long>?) : Pair<Boolean, Type> {
val types = dao.getProcessingDownloadTypes() val types = if (!selectedItems.isNullOrEmpty()) {
dao.getProcessingDownloadTypesByIDs(selectedItems)
}else {
dao.getProcessingDownloadTypes()
}
if (types.isEmpty()) { if (types.isEmpty()) {
return Pair(false, Type.command) return Pair(false, Type.command)
} }
@ -1241,8 +1285,13 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
return Pair(types.size == 1, Type.valueOf(types.first())) return Pair(types.size == 1, Type.valueOf(types.first()))
} }
fun checkIfAllProcessingItemsHaveSameContainer() : Pair<Boolean, String> { fun checkIfAllProcessingItemsHaveSameContainer(checkedItems: List<Long>?) : Pair<Boolean, String> {
val containers = dao.getProcessingDownloadContainers() val containers = if (checkedItems.isNullOrEmpty()) {
dao.getProcessingDownloadContainers()
}else {
dao.getDownloadContainersByIDs(checkedItems)
}
return Pair(containers.size == 1, containers.first()) return Pair(containers.size == 1, containers.first())
} }
@ -1277,12 +1326,20 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
return dao.getScheduledIDsBetweenTwoItems(item1, item2) return dao.getScheduledIDsBetweenTwoItems(item1, item2)
} }
suspend fun updateProcessingIncognito(incognito: Boolean) { suspend fun updateProcessingIncognito(selectedItems: List<Long>?, incognito: Boolean) {
dao.updateProcessingIncognito(incognito) if (selectedItems.isNullOrEmpty()) {
dao.updateProcessingIncognito(incognito)
}else {
dao.updateIncognitoByIDs(incognito, selectedItems)
}
} }
fun areAllProcessingIncognito() : Boolean { fun areAllProcessingIncognito(selectedItems: List<Long>?) : Boolean {
return dao.getProcessingAsIncognitoCount() > 0 return if (selectedItems.isNullOrEmpty()) {
dao.getProcessingAsIncognitoCount() > 0
}else {
dao.getProcessingAsIncognitoCountByIDs(selectedItems) > 0
}
} }
fun cancelDownloadOnly(id : Long) { fun cancelDownloadOnly(id : Long) {

View file

@ -1,5 +1,6 @@
package com.deniscerri.ytdl.ui.adapter package com.deniscerri.ytdl.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Handler import android.os.Handler
@ -7,6 +8,7 @@ import android.os.Looper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
@ -32,6 +34,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
private val onItemClickListener: OnItemClickListener private val onItemClickListener: OnItemClickListener
private val activity: Activity private val activity: Activity
private val sharedPreferences : SharedPreferences private val sharedPreferences : SharedPreferences
private var checkedItems: MutableSet<Long> = mutableSetOf()
private var currentItems: Set<Long> = setOf()
private var _isCheckingItems: Boolean = false
private var inverted: Boolean = false
init { init {
this.onItemClickListener = onItemClickListener this.onItemClickListener = onItemClickListener
@ -53,6 +59,69 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
return ViewHolder(cardView) return ViewHolder(cardView)
} }
fun isCheckingItems() : Boolean {
return _isCheckingItems
}
fun getCheckedItemsOrNull(): List<Long>? {
val res = if (inverted) {
currentItems.filter { !checkedItems.contains(it) }
}else {
checkedItems
}
return res.toList().ifEmpty { null }
}
fun getCheckedItemsSize() : Int {
return if (inverted){
currentItems.size - checkedItems.size
}else{
checkedItems.size
}
}
fun removeItemsFromCheckList(ids: List<Long>) {
checkedItems.removeAll(ids.toSet())
}
@SuppressLint("NotifyDataSetChanged")
fun clearCheckedItems() {
_isCheckingItems = false
inverted = false
checkedItems = mutableSetOf()
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun selectItems(ids: List<Long>) {
checkedItems = mutableSetOf()
inverted = false
checkedItems.addAll(ids)
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun checkAll() {
checkedItems = mutableSetOf()
inverted = true
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun invertSelected() {
inverted = !inverted
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun initCheckingItems(itemIDs: List<Long>) {
currentItems = itemIDs.toSet()
_isCheckingItems = true
checkedItems = mutableSetOf()
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
val card = holder.cardView val card = holder.cardView
@ -162,18 +231,48 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
} }
} }
val checkbox = card.findViewById<CheckBox>(R.id.checkBox)
checkbox.isVisible = _isCheckingItems
checkbox.isChecked = (checkedItems.contains(item.id) && !inverted) || (inverted && !checkedItems.contains(item.id))
checkbox.setOnClickListener {
if (checkbox.isChecked) {
if (inverted) checkedItems.remove(item.id)
else checkedItems.add(item.id)
onItemClickListener.onCardChecked(item.id)
}else {
if (inverted) checkedItems.add(item.id)
else checkedItems.remove(item.id)
onItemClickListener.onCardUnChecked(item.id)
}
}
val index = card.findViewById<TextView>(R.id.index)
index.isVisible = _isCheckingItems
index.text = (position + 1).toString()
card.setOnClickListener { card.setOnClickListener {
onItemClickListener.onCardClick(item.id) if (_isCheckingItems) {
checkbox.performClick()
}else {
onItemClickListener.onCardClick(item.id)
}
} }
card.setOnLongClickListener { card.setOnLongClickListener {
onItemClickListener.onDelete(item.id); true if (_isCheckingItems) {
checkbox.performClick()
}else{
onItemClickListener.onDelete(item.id)
}
true
} }
} }
interface OnItemClickListener { interface OnItemClickListener {
fun onButtonClick(id: Long) fun onButtonClick(id: Long)
fun onCardClick(id: Long) fun onCardClick(id: Long)
fun onCardChecked(id: Long)
fun onCardUnChecked(id: Long)
fun onDelete(id: Long) fun onDelete(id: Long)
} }

View file

@ -9,6 +9,7 @@ import android.content.SharedPreferences
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
@ -16,14 +17,18 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.get
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.setPadding import androidx.core.view.setPadding
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -59,6 +64,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -68,6 +74,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.math.abs
class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener, class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener { ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
@ -81,6 +88,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var behavior: BottomSheetBehavior<View> private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var bottomAppBar: BottomAppBar private lateinit var bottomAppBar: BottomAppBar
private lateinit var filesize : TextView private lateinit var filesize : TextView
private var itemsFileSize: Long = 0L
private lateinit var count : TextView private lateinit var count : TextView
private lateinit var downloadBtn : MaterialButton private lateinit var downloadBtn : MaterialButton
private lateinit var scheduleBtn : MaterialButton private lateinit var scheduleBtn : MaterialButton
@ -98,6 +106,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var containerBtn : MenuItem private lateinit var containerBtn : MenuItem
private lateinit var containerTextView: TextView private lateinit var containerTextView: TextView
private lateinit var multipleSelectHeader: ConstraintLayout
private lateinit var selectItemsMenuBtn: MaterialButton
private lateinit var selectRangeBtn: MaterialButton
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java] downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -182,28 +194,51 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch { lifecycleScope.launch {
downloadViewModel.processingDownloads.collectLatest { items -> downloadViewModel.processingDownloads.collectLatest { items ->
processingItemsCount = items.size processingItemsCount = items.size
currentDownloadIDs = items.map { it.id }
count.text = "$processingItemsCount ${getString(R.string.selected)}" count.text = "$processingItemsCount ${getString(R.string.selected)}"
listAdapter.submitList(items) listAdapter.submitList(items)
updateFileSize(items) updateFileSize(items)
if (items.isNotEmpty()){ if (items.isNotEmpty()){
if (items.all { it2 -> it2.type == items[0].type }) { lifecycleScope.launch {
formatBtn.icon?.alpha = 255 val haveSameType = if (listAdapter.isCheckingItems()) {
if (items[0].type != DownloadViewModel.Type.command) { val res = withContext(Dispatchers.IO) {
moreBtn.icon?.alpha = 255 downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
}
res.first
}else {
items.all { it2 -> it2.type == items[0].type }
} }
containerBtn.isVisible = items.first().type != DownloadViewModel.Type.command if (haveSameType) {
} else { formatBtn.icon?.alpha = 255
formatBtn.icon?.alpha = 30 if (items[0].type != DownloadViewModel.Type.command) {
moreBtn.icon?.alpha = 30 moreBtn.icon?.alpha = 255
} }
if (items.all { it.container == items[0].container }) { containerBtn.isVisible = items.first().type != DownloadViewModel.Type.command
setContainerText(items[0].container) }else {
}else{ formatBtn.icon?.alpha = 30
setContainerText("") moreBtn.icon?.alpha = 30
}
val haveSameContainer = if (listAdapter.isCheckingItems()) {
val res = withContext(Dispatchers.IO) {
downloadViewModel.checkIfAllProcessingItemsHaveSameContainer(listAdapter.getCheckedItemsOrNull())
}
res.first
}else {
items.all { it.container == items[0].container }
}
if (haveSameContainer) {
setContainerText(items[0].container)
}else {
setContainerText("")
}
} }
val type = items.first().type val type = items.first().type
@ -233,7 +268,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
toggleLoading(true) toggleLoading(true)
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs) historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs)
val result = downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis) val result = downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis)
if (result.message.isNotBlank()){ if (result.message.isNotBlank()){
@ -257,7 +291,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
toggleLoading(true) toggleLoading(true)
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs) historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs)
val result = downloadViewModel.queueProcessingDownloads() val result = downloadViewModel.queueProcessingDownloads()
if (result.message.isNotBlank()){ if (result.message.isNotBlank()){
@ -283,8 +316,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int -> dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch{ lifecycleScope.launch{
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs) downloadViewModel.moveProcessingToSavedCategory(null)
downloadViewModel.moveProcessingToSavedCategory()
historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs) historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs)
} }
@ -300,7 +332,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val formatListener = object : OnMultipleFormatClickListener { val formatListener = object : OnMultipleFormatClickListener {
override fun onFormatClick(formatTuple: List<MultipleItemFormatTuple>) { override fun onFormatClick(formatTuple: List<MultipleItemFormatTuple>) {
downloadViewModel.updateAllProcessingFormats(formatTuple) downloadViewModel.updateAllProcessingFormats(listAdapter.getCheckedItemsOrNull(), formatTuple)
} }
override fun onFormatUpdated(url: String, formats: List<Format>) { override fun onFormatUpdated(url: String, formats: List<Format>) {
@ -314,7 +346,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
override fun onContinueOnBackground() { override fun onContinueOnBackground() {
requireActivity().lifecycleScope.launch { requireActivity().lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.continueUpdatingFormatsOnBackground() downloadViewModel.continueUpdatingFormatsOnBackground(listAdapter.getCheckedItemsOrNull())
} }
downloadViewModel.processingItemsJob?.cancel(CancellationException()) downloadViewModel.processingItemsJob?.cancel(CancellationException())
downloadViewModel.processingItemsJob = null downloadViewModel.processingItemsJob = null
@ -326,7 +358,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch { lifecycleScope.launch {
val allIncognito = withContext(Dispatchers.IO){ val allIncognito = withContext(Dispatchers.IO){
downloadViewModel.areAllProcessingIncognito() downloadViewModel.areAllProcessingIncognito(listAdapter.getCheckedItemsOrNull())
} }
incognitoBtn.icon!!.apply { incognitoBtn.icon!!.apply {
@ -359,40 +391,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomAppBar.setOnMenuItemClickListener { m: MenuItem -> bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
when (m.itemId) { when (m.itemId) {
R.id.incognito -> {
lifecycleScope.launch {
if (m.icon!!.alpha == 255) {
incognitoBtn.isEnabled = false
withContext(Dispatchers.IO) {
downloadViewModel.updateProcessingIncognito(false)
withContext(Dispatchers.Main){
m.icon!!.alpha = 30
m.isEnabled = true
}
}
incognitoBtn.icon?.alpha = 30
incognitoBtn.isEnabled = true
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.disabled)}", Toast.LENGTH_SHORT).show()
}else{
incognitoBtn.isEnabled = false
withContext(Dispatchers.IO) {
downloadViewModel.updateProcessingIncognito(true)
withContext(Dispatchers.Main){
}
}
incognitoBtn.icon?.alpha = 255
incognitoBtn.isEnabled = true
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.ok)}", Toast.LENGTH_SHORT).show()
}
}
}
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)
}
R.id.preferred_download_type -> { R.id.preferred_download_type -> {
lifecycleScope.launch{ lifecycleScope.launch{
val bottomSheet = BottomSheetDialog(requireContext()) val bottomSheet = BottomSheetDialog(requireContext())
@ -416,7 +414,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
audio!!.setOnClickListener { audio!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio) downloadViewModel.updateProcessingType(listAdapter.getCheckedItemsOrNull(), DownloadViewModel.Type.audio)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24) preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
formatBtn.icon?.alpha = 255 formatBtn.icon?.alpha = 255
@ -424,7 +422,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.cancel() bottomSheet.cancel()
} }
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer() val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer(listAdapter.getCheckedItemsOrNull())
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (!res.first) { if (!res.first) {
setContainerText("") setContainerText("")
@ -438,7 +436,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
video!!.setOnClickListener { video!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{ CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video) downloadViewModel.updateProcessingType(listAdapter.getCheckedItemsOrNull(), DownloadViewModel.Type.video)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24) preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
formatBtn.icon?.alpha = 255 formatBtn.icon?.alpha = 255
@ -446,7 +444,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.cancel() bottomSheet.cancel()
} }
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer() val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer(listAdapter.getCheckedItemsOrNull())
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (!res.first) { if (!res.first) {
setContainerText("") setContainerText("")
@ -459,7 +457,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
command!!.setOnClickListener { command!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{ CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command) downloadViewModel.updateProcessingType(listAdapter.getCheckedItemsOrNull(), DownloadViewModel.Type.command)
withContext(Dispatchers.Main){ withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24) preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
formatBtn.icon?.alpha = 255 formatBtn.icon?.alpha = 255
@ -485,7 +483,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.format -> { R.id.format -> {
lifecycleScope.launch { lifecycleScope.launch {
val res = withContext(Dispatchers.IO){ val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType() downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
} }
if (!res.first){ if (!res.first){
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
@ -505,13 +503,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.updateProcessingCommandFormat(format) downloadViewModel.updateProcessingCommandFormat(listAdapter.getCheckedItemsOrNull(), format)
} }
} }
} }
}else{ }else{
val items = withContext(Dispatchers.IO){ val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads() downloadViewModel.getProcessingDownloads(listAdapter.getCheckedItemsOrNull())
} }
formatViewModel.setItems(items) formatViewModel.setItems(items)
val bottomSheet = FormatSelectionBottomSheetDialog( _multipleFormatsListener = formatListener) val bottomSheet = FormatSelectionBottomSheetDialog( _multipleFormatsListener = formatListener)
@ -520,10 +518,63 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
} }
} }
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)
}
R.id.select_items -> {
if (listAdapter.isCheckingItems()) {
multipleSelectHeader.isVisible = false
view.findViewById<ConstraintLayout>(R.id.downloadHeader).isVisible = true
filesize.isVisible = itemsFileSize > 0L
selectRangeBtn.isVisible = false
count.text = "${currentDownloadIDs.size} ${getString(R.string.selected)}"
listAdapter.clearCheckedItems()
}else {
multipleSelectHeader.apply {
isVisible = true
}
view.findViewById<ConstraintLayout>(R.id.downloadHeader).isVisible = false
filesize.isVisible = false
listAdapter.initCheckingItems(currentDownloadIDs)
selectRangeBtn.isVisible = true
count.text = "0 ${getString(R.string.selected)}"
}
}
R.id.incognito -> {
lifecycleScope.launch {
if (m.icon!!.alpha == 255) {
incognitoBtn.isEnabled = false
withContext(Dispatchers.IO) {
downloadViewModel.updateProcessingIncognito(listAdapter.getCheckedItemsOrNull(), false)
withContext(Dispatchers.Main){
m.icon!!.alpha = 30
m.isEnabled = true
}
}
incognitoBtn.icon?.alpha = 30
incognitoBtn.isEnabled = true
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.disabled)}", Toast.LENGTH_SHORT).show()
}else{
incognitoBtn.isEnabled = false
withContext(Dispatchers.IO) {
downloadViewModel.updateProcessingIncognito(listAdapter.getCheckedItemsOrNull(),true)
withContext(Dispatchers.Main){
}
}
incognitoBtn.icon?.alpha = 255
incognitoBtn.isEnabled = true
Toast.makeText(requireContext(), "${getString(R.string.incognito)}: ${getString(R.string.ok)}", Toast.LENGTH_SHORT).show()
}
}
}
R.id.more -> { R.id.more -> {
lifecycleScope.launch { lifecycleScope.launch {
val res = withContext(Dispatchers.IO){ val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType() downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
} }
if (!res.first) { if (!res.first) {
Toast.makeText( Toast.makeText(
@ -544,7 +595,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding) sheetView.findViewById<View>(R.id.adjust).setPadding(padding)
val items = withContext(Dispatchers.IO){ val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads() downloadViewModel.getProcessingDownloads(listAdapter.getCheckedItemsOrNull())
} }
UiUtil.configureAudio( UiUtil.configureAudio(
@ -626,7 +677,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding) sheetView.findViewById<View>(R.id.adjust).setPadding(padding)
val items = withContext(Dispatchers.IO){ val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads() downloadViewModel.getProcessingDownloads(listAdapter.getCheckedItemsOrNull())
} }
UiUtil.configureVideo( UiUtil.configureVideo(
@ -731,7 +782,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
containerTextView.setOnClickListener { containerTextView.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
val res = withContext(Dispatchers.IO){ val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType() downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
} }
if (!res.first){ if (!res.first){
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
@ -749,7 +800,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val container = mm.title val container = mm.title
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO){ withContext(Dispatchers.IO){
downloadViewModel.updateProcessingContainer(container.toString()) downloadViewModel.updateProcessingContainer(listAdapter.getCheckedItemsOrNull(), container.toString())
} }
} }
setContainerText(container.toString()) setContainerText(container.toString())
@ -758,11 +809,92 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
popup.show() popup.show()
} }
} }
} }
multipleSelectHeader = view.findViewById(R.id.multipleSelectHeader)
selectItemsMenuBtn = view.findViewById(R.id.selectItemsMenu)
selectItemsMenuBtn.setOnClickListener {
val popup = PopupMenu(activity, it)
popup.menuInflater.inflate(R.menu.select_multiple_items_menu_context, popup.menu)
if (Build.VERSION.SDK_INT > 27) popup.menu.setGroupDividerEnabled(true)
val selectedItems = listAdapter.getCheckedItemsOrNull() ?: listOf()
popup.menu.findItem(R.id.delete).isVisible = selectedItems.isNotEmpty()
popup.menu.findItem(R.id.select_between).apply {
if (selectedItems.size == 2) {
val firstIndex = currentDownloadIDs.indexOf(selectedItems.first())
val secondIndex = currentDownloadIDs.indexOf(selectedItems.last())
isVisible = abs(firstIndex - secondIndex) > 1
}else{
isVisible = false
}
}
popup.setOnMenuItemClickListener { m: MenuItem ->
when(m.itemId) {
R.id.select_between -> {
val firstIndex = currentDownloadIDs.indexOf(selectedItems.first())
val secondIndex = currentDownloadIDs.indexOf(selectedItems.last())
val itemsBetween = currentDownloadIDs.filterIndexed { index, _ -> index >= firstIndex && index <= secondIndex }
listAdapter.selectItems(itemsBetween)
count.text = "${itemsBetween.size} ${getString(R.string.selected)}"
}
R.id.delete -> {
UiUtil.showGenericDeleteAllDialog(requireContext()) {
lifecycleScope.launch {
val deletedAll = processingItemsCount == listAdapter.getCheckedItemsSize()
val toDelete = listAdapter.getCheckedItemsOrNull() ?: listOf()
withContext(Dispatchers.IO) {
downloadViewModel.deleteAllWithID(toDelete)
}
listAdapter.removeItemsFromCheckList(toDelete)
val checkedSize = listAdapter.getCheckedItemsSize()
count.text = "$checkedSize ${getString(R.string.selected)}"
if (deletedAll) dismiss()
}
}
}
R.id.select_all -> {
listAdapter.checkAll()
val checkedSize = listAdapter.getCheckedItemsSize()
count.text = "$checkedSize ${getString(R.string.selected)}"
}
R.id.invert_selected -> {
listAdapter.invertSelected()
val checkedSize = listAdapter.getCheckedItemsSize()
count.text = "$checkedSize ${getString(R.string.selected)}"
}
}
true
}
popup.show()
}
selectRangeBtn = view.findViewById(R.id.selectRangeBtn)
selectRangeBtn.setOnClickListener {
UiUtil.showSelectRangeDialog(requireActivity(), currentDownloadIDs.size) {
val itemsBetween = currentDownloadIDs.filterIndexed { index, _ -> index >= it.first && index <= it.second }
listAdapter.selectItems(itemsBetween)
selectItemsMenuBtn.isVisible = true
count.text = "${itemsBetween.size} ${getString(R.string.selected)}"
}
}
val editSelectedOkBtn = view.findViewById<MaterialButton>(R.id.bottomsheet_ok_button)
editSelectedOkBtn.setOnClickListener {
multipleSelectHeader.isVisible = false
view.findViewById<ConstraintLayout>(R.id.downloadHeader).isVisible = true
selectItemsMenuBtn.isVisible = false
filesize.isVisible = itemsFileSize > 0
count.text = "${currentDownloadIDs.size} ${getString(R.string.selected)}"
selectRangeBtn.isVisible = false
listAdapter.clearCheckedItems()
}
} }
override fun onResume() { override fun onResume() {
@ -819,7 +951,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
if (fileSizes.all { it > 5L }){ if (fileSizes.all { it > 5L }){
val size = FileUtil.convertFileSize(fileSizes.sum()) val size = FileUtil.convertFileSize(fileSizes.sum())
filesize.isVisible = size != "?" itemsFileSize = fileSizes.sum()
filesize.isVisible = size != "?" && !listAdapter.isCheckingItems()
filesize.text = "${getString(R.string.file_size)}: >~ $size" filesize.text = "${getString(R.string.file_size)}: >~ $size"
}else{ }else{
filesize.visibility = View.GONE filesize.visibility = View.GONE
@ -839,7 +972,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingDownloadPath(result.data?.data.toString()) downloadViewModel.updateProcessingDownloadPath(listAdapter.getCheckedItemsOrNull(), result.data?.data.toString())
} }
val path = FileUtil.formatPath(result.data!!.data.toString()) val path = FileUtil.formatPath(result.data!!.data.toString())
@ -926,6 +1059,20 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
} }
} }
override fun onCardChecked(id: Long) {
selectItemsMenuBtn.isVisible = true
val checkedSize = listAdapter.getCheckedItemsSize()
count.text = "$checkedSize ${getString(R.string.selected)}"
}
override fun onCardUnChecked(id: Long) {
val checkedSize = listAdapter.getCheckedItemsSize()
if (checkedSize == 0) {
selectItemsMenuBtn.isVisible = false
}
count.text = "$checkedSize ${getString(R.string.selected)}"
}
override fun onDelete(id: Long) { override fun onDelete(id: Long) {
lifecycleScope.launch { lifecycleScope.launch {
val deletedItem = withContext(Dispatchers.IO){ val deletedItem = withContext(Dispatchers.IO){

View file

@ -20,6 +20,7 @@ import android.os.Environment
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.text.format.DateFormat import android.text.format.DateFormat
import android.text.method.DigitsKeyListener
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.Gravity
@ -2014,6 +2015,73 @@ object UiUtil {
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START
} }
fun showSelectRangeDialog(context: Activity, itemCount: Int, rangeSelected: (rangeSelected: Pair<Int, Int>) -> Unit) {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(context.getString(R.string.select_between))
builder.setMessage(context.getString(R.string.select_between_desc))
builder.setIcon(R.drawable.baseline_format_list_numbered_24)
val view = context.layoutInflater.inflate(R.layout.select_range_dialog, null)
val fromTextInput = view.findViewById<TextInputLayout>(R.id.from_textinput)
fromTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
val toTextInput = view.findViewById<TextInputLayout>(R.id.to_textinput)
toTextInput.editText!!.keyListener = DigitsKeyListener.getInstance("0123456789")
builder.setView(view)
builder.setPositiveButton(
context.getString(R.string.ok)
) { _: DialogInterface?, _: Int ->
val firstIndex = fromTextInput.editText!!.text.toString().toInt() - 1
val secondIndex = toTextInput.editText!!.text.toString().toInt() - 1
rangeSelected(Pair(firstIndex,secondIndex))
}
// handle the negative button of the alert dialog
builder.setNegativeButton(
context.getString(R.string.cancel)
) { _: DialogInterface?, _: Int -> }
val dialog = builder.create()
dialog.show()
fun checkRanges(start: String, end: String) : Boolean {
fromTextInput.error = ""
toTextInput.error = ""
if (start.isBlank() || end.isBlank()) return false
val startValid = start.toInt() >= 0
val endValid = end.toInt() <= itemCount
if (!startValid) {
fromTextInput.editText?.setText("")
fromTextInput.error = "Invalid Number"
}
if (!endValid) {
toTextInput.editText?.setText("")
toTextInput.error = "Invalid Number"
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = startValid && endValid
return startValid && endValid
}
fromTextInput.editText!!.doAfterTextChanged { editable ->
val start = editable.toString()
val end = toTextInput.editText!!.text.toString()
checkRanges(start, end)
}
toTextInput.editText!!.doAfterTextChanged { editable ->
val start = fromTextInput.editText!!.text.toString()
val end = editable.toString()
checkRanges(start, end)
}
}
private fun createPersonalFilenameTemplateChip(context: Activity, text: String, myChipGroup: ChipGroup, onClick: (f: Chip) -> Unit, onLongClick: (f: Chip) -> Unit) : Chip { private fun createPersonalFilenameTemplateChip(context: Activity, text: String, myChipGroup: ChipGroup, onClick: (f: Chip) -> Unit, onLongClick: (f: Chip) -> Unit) : Chip {
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, myChipGroup, false) as Chip val tmp = context.layoutInflater.inflate(R.layout.filter_chip, myChipGroup, false) as Chip
tmp.text = text tmp.text = text

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?attr/colorAccent" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M2,17h2v0.5L3,17.5v1h1v0.5L2,19v1h3v-4L2,16v1zM3,8h1L4,4L2,4v1h1v3zM2,11h1.8L2,13.1v0.9h3v-1L3.2,13L5,10.9L5,10L2,10v1zM7,5v2h14L21,5L7,5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2z"/>
</vector>

View file

@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000" <vector android:height="24dp" android:tint="?attr/colorAccent"
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: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"/> <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"/>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="?android:colorBackground" />
<padding
android:left="1dp"
android:right="1dp"
android:bottom="1dp"
android:top="1dp" />
<corners
android:topLeftRadius="5dp"
android:bottomRightRadius="5dp" />
</shape>

View file

@ -21,25 +21,57 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingHorizontal="20dp" android:paddingEnd="20dp"
android:paddingStart="10dp"
android:paddingVertical="10dp" android:paddingVertical="10dp"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:minWidth="0dp"
android:visibility="gone"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/downloads_image_view" android:id="@+id/downloads_image_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:layout_marginStart="10dp"
android:background="?attr/colorSurfaceVariant" android:background="?attr/colorSurfaceVariant"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,16:9" app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toStartOf="@+id/download_item_data" app:layout_constraintEnd_toStartOf="@+id/download_item_data"
app:layout_constraintHorizontal_weight="0.3" app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@id/checkBox"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" /> app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar2" />
<TextView
android:id="@+id/index"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:ellipsize="end"
android:background="@drawable/rounded_corner_index"
android:gravity="center"
android:textStyle="bold"
android:maxLength="17"
android:maxLines="1"
android:text="1"
android:minWidth="10dp"
android:paddingHorizontal="5dp"
android:textSize="12sp"
app:layout_constraintTop_toTopOf="@+id/downloads_image_view"
app:layout_constraintStart_toStartOf="@+id/downloads_image_view" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/incognitoLabel" android:id="@+id/incognitoLabel"
style="?attr/materialIconButtonStyle" style="?attr/materialIconButtonStyle"

View file

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"> android:orientation="vertical">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
@ -34,6 +35,7 @@
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/downloadHeader"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp" android:layout_marginHorizontal="20dp"
@ -137,39 +139,126 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/multiple_top_info" android:id="@+id/multipleSelectHeader"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/bottom_sheet_edit_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/edit"
android:textSize="25sp"
android:maxLines="2"
android:singleLine="false"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_ok_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_sheet_edit_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/edit_selected"
android:textSize="12sp"
app:layout_constraintEnd_toStartOf="@+id/bottomsheet_ok_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bottom_sheet_edit_title" />
<Button
android:id="@+id/bottomsheet_ok_button"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:outlineProvider="none"
android:stateListAnimator="@null"
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.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <LinearLayout
android:id="@+id/count" android:id="@+id/multiple_top_info"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="20dp" app:layout_constraintBottom_toBottomOf="parent"
android:paddingTop="10dp" app:layout_constraintEnd_toStartOf="@+id/selectRangeBtn"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:id="@+id/filesize" android:id="@+id/count"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="20dp"
android:paddingTop="10dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/filesize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingTop="10dp"
android:paddingEnd="20dp"
android:text="@string/file_size"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/selectRangeBtn"
android:visibility="gone" android:visibility="gone"
android:paddingEnd="20dp" style="@style/Widget.Material3.Button.IconButton"
android:paddingStart="0dp" android:layout_width="wrap_content"
android:paddingTop="10dp" app:icon="@drawable/baseline_format_list_numbered_24"
android:text="@string/file_size" android:layout_height="wrap_content"
android:textStyle="bold" android:contentDescription="@string/select_between"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/selectItemsMenu"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/selectItemsMenu"
android:visibility="gone"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
android:layout_marginEnd="20dp"
android:contentDescription="@string/settings"
app:icon="@drawable/baseline_more_vert_24"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview" android:id="@+id/downloadMultipleRecyclerview"

View file

@ -38,8 +38,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="false" android:clickable="false"
android:ellipsize="end" android:ellipsize="end"
android:layout_margin="5dp" android:background="@drawable/rounded_corner_index"
android:background="#80000000"
android:gravity="center" android:gravity="center"
android:textStyle="bold" android:textStyle="bold"
android:maxLength="17" android:maxLength="17"

View file

@ -74,6 +74,7 @@
app:borderWidth="0dp" app:borderWidth="0dp"
android:contentDescription="@string/select_all" android:contentDescription="@string/select_all"
android:layout_height="55dp" android:layout_height="55dp"
android:backgroundTint="@android:color/transparent"
app:layout_anchor="@id/bottomAppBar" app:layout_anchor="@id/bottomAppBar"
android:layout_marginHorizontal="5dp" android:layout_marginHorizontal="5dp"
android:src="@drawable/ic_select_all" android:src="@drawable/ic_select_all"
@ -129,6 +130,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:autoLink="all" android:autoLink="all"
android:text="@string/ok" android:text="@string/ok"
android:outlineProvider="none"
app:icon="@drawable/ic_check" app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -0,0 +1,54 @@
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:layout_marginHorizontal="20dp"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/from_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="45"
android:layout_marginEnd="10dp"
android:hint="@string/start">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="9" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/to_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/end"
android:layout_weight="45"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/colon">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLength="9" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -28,13 +28,19 @@
app:actionLayout="@layout/bottom_app_bar_text" app:actionLayout="@layout/bottom_app_bar_text"
app:showAsAction="always"/> app:showAsAction="always"/>
<item
android:id="@+id/select_items"
android:title="@string/select"
android:icon="@drawable/ic_select_all"
app:showAsAction="always"/>
<item <item
android:id="@+id/incognito" android:id="@+id/incognito"
android:title="@string/incognito" android:title="@string/incognito"
android:icon="@drawable/ic_nightly" android:icon="@drawable/ic_nightly"
app:showAsAction="always"/> app:showAsAction="always"/>
<item <item
android:id="@+id/more" android:id="@+id/more"
android:title="@string/more" android:title="@string/more"

View file

@ -0,0 +1,30 @@
<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/select_between"
android:visible="false"
android:title="@string/select_between"
android:icon="@drawable/ic_select_between"
app:showAsAction="ifRoom" />
<item
android:id="@+id/delete"
android:title="@string/Remove"
android:icon="@drawable/baseline_delete_24"
app:showAsAction="ifRoom" />
<item
android:id="@+id/select_all"
android:title="@string/select_all"
app:showAsAction="never" />
<item
android:id="@+id/invert_selected"
android:title="@string/invert_selected"
app:showAsAction="never" />
</menu>

View file

@ -477,4 +477,5 @@
<string name="generate_potokens_warning">* By enabling this, you need to disable cookies</string> <string name="generate_potokens_warning">* By enabling this, you need to disable cookies</string>
<string name="custom">Custom</string> <string name="custom">Custom</string>
<string name="cut_unsupported">Cutting is not available for this item</string> <string name="cut_unsupported">Cutting is not available for this item</string>
<string name="select_between_desc">Specify the range of items to select</string>
</resources> </resources>