add function to modify selected items in multiple download card

pull/820/head
deniscerri 1 year ago
parent ffc4ded5af
commit ece2cdf631
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -54,11 +54,16 @@ interface DownloadDao {
""")
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>
@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)")
suspend fun updateItemsToProcessing(ids: List<Long>)
@ -74,9 +79,15 @@ interface DownloadDao {
@Query("UPDATE downloads set downloadPath=:path WHERE status ='Processing'")
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'")
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'")
fun getActiveDownloadsList() : List<DownloadItem>
@ -356,6 +367,12 @@ interface DownloadDao {
@Query("UPDATE downloads set incognito=:incognito WHERE status='Processing'")
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'")
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
}

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

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

@ -1,5 +1,6 @@
package com.deniscerri.ytdl.ui.adapter
import android.annotation.SuppressLint
import android.app.Activity
import android.content.SharedPreferences
import android.os.Handler
@ -7,6 +8,7 @@ import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
@ -32,6 +34,10 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
private val onItemClickListener: OnItemClickListener
private val activity: Activity
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 {
this.onItemClickListener = onItemClickListener
@ -53,6 +59,69 @@ class ConfigureMultipleDownloadsAdapter(onItemClickListener: OnItemClickListener
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) {
val item = getItem(position)
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 {
onItemClickListener.onCardClick(item.id)
if (_isCheckingItems) {
checkbox.performClick()
}else {
onItemClickListener.onCardClick(item.id)
}
}
card.setOnLongClickListener {
onItemClickListener.onDelete(item.id); true
if (_isCheckingItems) {
checkbox.performClick()
}else{
onItemClickListener.onDelete(item.id)
}
true
}
}
interface OnItemClickListener {
fun onButtonClick(id: Long)
fun onCardClick(id: Long)
fun onCardChecked(id: Long)
fun onCardUnChecked(id: Long)
fun onDelete(id: Long)
}

@ -9,6 +9,7 @@ import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
@ -16,14 +17,18 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.children
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.core.view.setPadding
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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.SurfaceColors
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator
import kotlinx.coroutines.CancellationException
@ -68,6 +74,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.abs
class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), ConfigureMultipleDownloadsAdapter.OnItemClickListener, View.OnClickListener,
ConfigureDownloadBottomSheetDialog.OnDownloadItemUpdateListener {
@ -81,6 +88,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var behavior: BottomSheetBehavior<View>
private lateinit var bottomAppBar: BottomAppBar
private lateinit var filesize : TextView
private var itemsFileSize: Long = 0L
private lateinit var count : TextView
private lateinit var downloadBtn : MaterialButton
private lateinit var scheduleBtn : MaterialButton
@ -98,6 +106,10 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
private lateinit var containerBtn : MenuItem
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?) {
super.onCreate(savedInstanceState)
downloadViewModel = ViewModelProvider(requireActivity())[DownloadViewModel::class.java]
@ -182,28 +194,51 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch {
downloadViewModel.processingDownloads.collectLatest { items ->
processingItemsCount = items.size
currentDownloadIDs = items.map { it.id }
count.text = "$processingItemsCount ${getString(R.string.selected)}"
listAdapter.submitList(items)
updateFileSize(items)
if (items.isNotEmpty()){
if (items.all { it2 -> it2.type == items[0].type }) {
formatBtn.icon?.alpha = 255
if (items[0].type != DownloadViewModel.Type.command) {
moreBtn.icon?.alpha = 255
lifecycleScope.launch {
val haveSameType = if (listAdapter.isCheckingItems()) {
val res = withContext(Dispatchers.IO) {
downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
}
res.first
}else {
items.all { it2 -> it2.type == items[0].type }
}
containerBtn.isVisible = items.first().type != DownloadViewModel.Type.command
} else {
formatBtn.icon?.alpha = 30
moreBtn.icon?.alpha = 30
}
if (haveSameType) {
formatBtn.icon?.alpha = 255
if (items[0].type != DownloadViewModel.Type.command) {
moreBtn.icon?.alpha = 255
}
if (items.all { it.container == items[0].container }) {
setContainerText(items[0].container)
}else{
setContainerText("")
containerBtn.isVisible = items.first().type != DownloadViewModel.Type.command
}else {
formatBtn.icon?.alpha = 30
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
@ -233,7 +268,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
toggleLoading(true)
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs)
val result = downloadViewModel.updateProcessingDownloadTimeAndQueueScheduled(cal.timeInMillis)
if (result.message.isNotBlank()){
@ -257,7 +291,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
toggleLoading(true)
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs)
val result = downloadViewModel.queueProcessingDownloads()
if (result.message.isNotBlank()){
@ -283,8 +316,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
dd.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
lifecycleScope.launch{
withContext(Dispatchers.IO){
downloadViewModel.deleteAllWithID(currentDownloadIDs)
downloadViewModel.moveProcessingToSavedCategory()
downloadViewModel.moveProcessingToSavedCategory(null)
historyViewModel.deleteAllWithIDsCheckFiles(currentHistoryIDs)
}
@ -300,7 +332,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
val formatListener = object : OnMultipleFormatClickListener {
override fun onFormatClick(formatTuple: List<MultipleItemFormatTuple>) {
downloadViewModel.updateAllProcessingFormats(formatTuple)
downloadViewModel.updateAllProcessingFormats(listAdapter.getCheckedItemsOrNull(), formatTuple)
}
override fun onFormatUpdated(url: String, formats: List<Format>) {
@ -314,7 +346,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
override fun onContinueOnBackground() {
requireActivity().lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.continueUpdatingFormatsOnBackground()
downloadViewModel.continueUpdatingFormatsOnBackground(listAdapter.getCheckedItemsOrNull())
}
downloadViewModel.processingItemsJob?.cancel(CancellationException())
downloadViewModel.processingItemsJob = null
@ -326,7 +358,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch {
val allIncognito = withContext(Dispatchers.IO){
downloadViewModel.areAllProcessingIncognito()
downloadViewModel.areAllProcessingIncognito(listAdapter.getCheckedItemsOrNull())
}
incognitoBtn.icon!!.apply {
@ -359,40 +391,6 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomAppBar.setOnMenuItemClickListener { m: MenuItem ->
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 -> {
lifecycleScope.launch{
val bottomSheet = BottomSheetDialog(requireContext())
@ -416,7 +414,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
audio!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
downloadViewModel.updateProcessingType(DownloadViewModel.Type.audio)
downloadViewModel.updateProcessingType(listAdapter.getCheckedItemsOrNull(), DownloadViewModel.Type.audio)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_audio_file_24)
formatBtn.icon?.alpha = 255
@ -424,7 +422,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.cancel()
}
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer()
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer(listAdapter.getCheckedItemsOrNull())
withContext(Dispatchers.Main) {
if (!res.first) {
setContainerText("")
@ -438,7 +436,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
video!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(DownloadViewModel.Type.video)
downloadViewModel.updateProcessingType(listAdapter.getCheckedItemsOrNull(), DownloadViewModel.Type.video)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_video_file_24)
formatBtn.icon?.alpha = 255
@ -446,7 +444,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
bottomSheet.cancel()
}
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer()
val res = downloadViewModel.checkIfAllProcessingItemsHaveSameContainer(listAdapter.getCheckedItemsOrNull())
withContext(Dispatchers.Main) {
if (!res.first) {
setContainerText("")
@ -459,7 +457,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
command!!.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch{
downloadViewModel.updateProcessingType(DownloadViewModel.Type.command)
downloadViewModel.updateProcessingType(listAdapter.getCheckedItemsOrNull(), DownloadViewModel.Type.command)
withContext(Dispatchers.Main){
preferredDownloadType.setIcon(R.drawable.baseline_insert_drive_file_24)
formatBtn.icon?.alpha = 255
@ -485,7 +483,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
R.id.format -> {
lifecycleScope.launch {
val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
}
if (!res.first){
Toast.makeText(requireContext(), getString(R.string.format_filtering_hint), Toast.LENGTH_SHORT).show()
@ -505,13 +503,13 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.updateProcessingCommandFormat(format)
downloadViewModel.updateProcessingCommandFormat(listAdapter.getCheckedItemsOrNull(), format)
}
}
}
}else{
val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads()
downloadViewModel.getProcessingDownloads(listAdapter.getCheckedItemsOrNull())
}
formatViewModel.setItems(items)
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 -> {
lifecycleScope.launch {
val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
}
if (!res.first) {
Toast.makeText(
@ -544,7 +595,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding)
val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads()
downloadViewModel.getProcessingDownloads(listAdapter.getCheckedItemsOrNull())
}
UiUtil.configureAudio(
@ -626,7 +677,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
sheetView.findViewById<View>(R.id.adjust).setPadding(padding)
val items = withContext(Dispatchers.IO){
downloadViewModel.getProcessingDownloads()
downloadViewModel.getProcessingDownloads(listAdapter.getCheckedItemsOrNull())
}
UiUtil.configureVideo(
@ -731,7 +782,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
containerTextView.setOnClickListener {
lifecycleScope.launch {
val res = withContext(Dispatchers.IO){
downloadViewModel.checkIfAllProcessingItemsHaveSameType()
downloadViewModel.checkIfAllProcessingItemsHaveSameType(listAdapter.getCheckedItemsOrNull())
}
if (!res.first){
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
lifecycleScope.launch {
withContext(Dispatchers.IO){
downloadViewModel.updateProcessingContainer(container.toString())
downloadViewModel.updateProcessingContainer(listAdapter.getCheckedItemsOrNull(), container.toString())
}
}
setContainerText(container.toString())
@ -758,9 +809,90 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
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()
}
}
@ -819,7 +951,8 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
if (fileSizes.all { it > 5L }){
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"
}else{
filesize.visibility = View.GONE
@ -839,7 +972,7 @@ class DownloadMultipleBottomSheetDialog : BottomSheetDialogFragment(), Configure
}
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())
@ -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) {
lifecycleScope.launch {
val deletedItem = withContext(Dispatchers.IO){

@ -20,6 +20,7 @@ import android.os.Environment
import android.text.Editable
import android.text.TextWatcher
import android.text.format.DateFormat
import android.text.method.DigitsKeyListener
import android.text.method.LinkMovementMethod
import android.util.DisplayMetrics
import android.view.Gravity
@ -2014,6 +2015,73 @@ object UiUtil {
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 {
val tmp = context.layoutInflater.inflate(R.layout.filter_chip, myChipGroup, false) as Chip
tmp.text = text

@ -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>

@ -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: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"/>

@ -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>

@ -21,25 +21,57 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:paddingHorizontal="20dp"
android:paddingEnd="20dp"
android:paddingStart="10dp"
android:paddingVertical="10dp"
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
android:id="@+id/downloads_image_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:layout_marginStart="10dp"
android:background="?attr/colorSurfaceVariant"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toStartOf="@+id/download_item_data"
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/checkBox"
app:layout_constraintTop_toTopOf="parent"
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
android:id="@+id/incognitoLabel"
style="?attr/materialIconButtonStyle"

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<androidx.coordinatorlayout.widget.CoordinatorLayout
@ -34,6 +35,7 @@
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/downloadHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
@ -137,39 +139,126 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/multiple_top_info"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/multipleSelectHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:visibility="gone"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
android:paddingTop="20dp">
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:id="@+id/bottom_sheet_edit_title"
android:layout_width="0dp"
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"
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/filesize"
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:visibility="gone"
android:paddingEnd="20dp"
android:paddingStart="0dp"
android:paddingTop="10dp"
android:text="@string/file_size"
android:textStyle="bold"
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_height="wrap_content">
<LinearLayout
android:id="@+id/multiple_top_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/selectRangeBtn"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/count"
android:layout_width="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"
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content"
app:icon="@drawable/baseline_format_list_numbered_24"
android:layout_height="wrap_content"
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_constraintTop_toTopOf="parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/downloadMultipleRecyclerview"

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

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

@ -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>

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

@ -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>

@ -477,4 +477,5 @@
<string name="generate_potokens_warning">* By enabling this, you need to disable cookies</string>
<string name="custom">Custom</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>

Loading…
Cancel
Save