Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selected items controlled and hide/show FAB button #3508

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class FileListAdapter(
private val isShowingJustFolders: Boolean,
private val layoutManager: StaggeredGridLayoutManager,
private val listener: FileListAdapterListener,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
) : SelectableAdapter<RecyclerView.ViewHolder>() {

var files = mutableListOf<Any>()
private var account: Account? = AccountUtils.getCurrentOwnCloudAccount(context)
Expand Down Expand Up @@ -129,6 +129,17 @@ class FileListAdapter(
}
}

fun getCheckedItems(): List<OCFile> {
val checkedItems = mutableListOf<OCFile>()
val checkedPositions = getSelectedItems()

for (i in checkedPositions) {
checkedItems.add(files[i] as OCFile)
}

return checkedItems
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

val viewType = getItemViewType(position)
Expand Down Expand Up @@ -202,13 +213,34 @@ class FileListAdapter(
}


holder.itemView.setOnClickListener { listener.clickItem(file) }
holder.itemView.setOnClickListener {
listener.clickItem(
ocFile = file,
position = position
)
}

holder.itemView.setOnLongClickListener {
listener.longClickItem(
ocFile = file,
position = position
)
}

val checkBoxV = holder.itemView.findViewById<ImageView>(R.id.custom_checkbox).apply {
visibility = View.GONE
}
holder.itemView.setBackgroundColor(Color.WHITE)

if (isSelected(position)) {
holder.itemView.setBackgroundColor(ContextCompat.getColor(context, R.color.selected_item_background))
checkBoxV.setImageResource(R.drawable.ic_checkbox_marked)
} else {
holder.itemView.setBackgroundColor(Color.WHITE)
checkBoxV.setImageResource(R.drawable.ic_checkbox_blank_outline)
}
checkBoxV.visibility = View.VISIBLE

if (file.isFolder) {
//Folder
fileIcon.setImageResource(
Expand Down Expand Up @@ -323,7 +355,8 @@ class FileListAdapter(
}

interface FileListAdapterListener {
fun clickItem(ocFile: OCFile)
fun clickItem(ocFile: OCFile, position: Int)
fun longClickItem(ocFile: OCFile, position: Int): Boolean = true
}

inner class GridViewHolder(val binding: GridItemBinding) : RecyclerView.ViewHolder(binding.root)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* ownCloud Android client application
*
* @author Fernando Sanz Velasco
* Copyright (C) 2022 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package com.owncloud.android.presentation.adapters.filelist

import android.util.SparseBooleanArray
import androidx.recyclerview.widget.RecyclerView

abstract class SelectableAdapter<VH : RecyclerView.ViewHolder?> :
RecyclerView.Adapter<VH>() {
private val selectedItems: SparseBooleanArray = SparseBooleanArray()

/**
* Indicates if the item at position position is selected
* @param position Position of the item to check
* @return true if the item is selected, false otherwise
*/
fun isSelected(position: Int): Boolean {
return getSelectedItems().contains(position)
}

/**
* Toggle the selection status of the item at a given position
* @param position Position of the item to toggle the selection status for
*/
fun toggleSelection(position: Int) {
if (selectedItems[position, false]) {
selectedItems.delete(position)
} else {
selectedItems.put(position, true)
}
notifyItemChanged(position)
}

/**
* Clear the selection status for all items
*/
fun clearSelection() {
val selection = getSelectedItems()
selectedItems.clear()
for (i in selection) {
notifyItemChanged(i)
}
}

/**
* Count the selected items
* @return Selected items count
*/
val selectedItemCount: Int
get() = selectedItems.size()

/**
* Indicates the list of selected items
* @return List of selected items ids
*/
fun getSelectedItems(): List<Int> {
val items: MutableList<Int> = ArrayList(selectedItems.size())
for (i in 0 until selectedItems.size()) {
items.add(selectedItems.keyAt(i))
}
return items
}

companion object {
private val TAG = SelectableAdapter::class.java.simpleName
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
Expand All @@ -44,6 +49,7 @@ import com.owncloud.android.domain.utils.Event
import com.owncloud.android.extensions.cancel
import com.owncloud.android.extensions.parseError
import com.owncloud.android.extensions.showMessageInSnackbar
import com.owncloud.android.files.FileMenuFilter
import com.owncloud.android.presentation.UIResult
import com.owncloud.android.presentation.adapters.filelist.FileListAdapter
import com.owncloud.android.presentation.observers.EmptyDataObserver
Expand All @@ -57,15 +63,15 @@ import com.owncloud.android.presentation.ui.files.SortType
import com.owncloud.android.presentation.ui.files.ViewType
import com.owncloud.android.presentation.ui.files.createfolder.CreateFolderDialogFragment
import com.owncloud.android.presentation.viewmodels.files.FilesViewModel
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.ui.fragment.OCFileListFragment
import com.owncloud.android.utils.ColumnQuantity
import com.owncloud.android.utils.FileStorageUtils
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.java.KoinJavaComponent.get
import timber.log.Timber
import java.io.File
import java.lang.ClassCastException

class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.SortOptionsListener, SortOptionsView.CreateFolderListener,
CreateFolderDialogFragment.CreateFolderListener, SearchView.OnQueryTextListener {
Expand All @@ -81,7 +87,6 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.Sor
private var containerActivity: FileFragment.ContainerActivity? = null
private var files: List<OCFile> = emptyList()


private var miniFabClicked = false
private lateinit var layoutManager: StaggeredGridLayoutManager
private lateinit var fileListAdapter: FileListAdapter
Expand All @@ -91,6 +96,10 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.Sor

private var file: OCFile? = null

var actionMode: ActionMode? = null
private var statusBarColorActionMode: Int? = null
private var statusBarColor: Int? = null

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand Down Expand Up @@ -142,6 +151,8 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.Sor
}

private fun initViews() {
setHasOptionsMenu(true)
statusBarColorActionMode = ContextCompat.getColor(requireContext(), R.color.action_mode_status_bar_background)

//Set view and footer correctly
if (mainFileListViewModel.isGridModeSetAsPreferred()) {
Expand All @@ -164,15 +175,28 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.Sor
isShowingJustFolders = isShowingJustFolders(),
listener = object :
FileListAdapter.FileListAdapterListener {
override fun clickItem(ocFile: OCFile) {
if (ocFile.isFolder) {
file = ocFile
mainFileListViewModel.listDirectory(ocFile)
// TODO Manage animation listDirectoryWithAnimationDown
} else { /// Click on a file
// TODO Click on a file
override fun clickItem(ocFile: OCFile, position: Int) {
if (actionMode != null) {
toggleSelection(position)
} else {
if (ocFile.isFolder) {
file = ocFile
mainFileListViewModel.listDirectory(ocFile)
// TODO Manage animation listDirectoryWithAnimationDown
} else { /// Click on a file
// TODO Click on a file
}
}
}

override fun longClickItem(ocFile: OCFile, position: Int): Boolean {
if (actionMode == null) {
actionMode = (activity as AppCompatActivity).startSupportActionMode(actionModeCallback)
}
toggleSelection(position)
return true
}

})
binding.recyclerViewMainFileList.adapter = fileListAdapter

Expand All @@ -191,6 +215,20 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.Sor
}
}

private fun toggleSelection(position: Int) {
fileListAdapter.toggleSelection(position)
fileListAdapter.selectedItemCount.also {
if (it == 0) {
actionMode?.finish()
} else {
actionMode?.apply {
title = it.toString()
invalidate()
}
}
}
}

private fun subscribeToViewModels() {
// Observe the action of retrieving the list of files from DB.
mainFileListViewModel.getFilesListStatusLiveData.observe(viewLifecycleOwner, Event.EventObserver {
Expand Down Expand Up @@ -505,5 +543,76 @@ class MainFileListFragment : Fragment(), SortDialogListener, SortOptionsView.Sor
return MainFileListFragment().apply { arguments = args }
}
}

private val actionModeCallback: ActionMode.Callback = object : ActionMode.Callback {

override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
actionMode = mode

val inflater = requireActivity().menuInflater
inflater.inflate(R.menu.file_actions_menu, menu)
mode?.invalidate()

//set gray color
val window = activity?.window
statusBarColor = window?.statusBarColor ?: -1

//hide FAB in multi selection mode
binding.fabMain.visibility = View.GONE
(containerActivity as FileDisplayActivity).showBottomNavBar(false)

// Hide sort options view in multi-selection mode
binding.optionsLayout.visibility = View.GONE

return true
}

/**
* Updates available action in menu depending on current selection.
*/
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
val checkedFiles = fileListAdapter.getCheckedItems()
val checkedCount = checkedFiles.size
val title = resources.getQuantityString(
R.plurals.items_selected_count,
checkedCount,
checkedCount
)
mode?.title = title
val fileMenuFilter = FileMenuFilter(checkedFiles, (requireActivity() as FileActivity).account, containerActivity, activity)

fileMenuFilter.filter(
menu,
false,
true,
fileListOption?.isAvailableOffline() ?: false,
fileListOption?.isSharedByLink() ?: false
)

return true
}

override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return false
}

override fun onDestroyActionMode(mode: ActionMode?) {

actionMode = null

// reset to previous color
requireActivity().window.statusBarColor = statusBarColor!!

// show FAB on multi selection mode exit
setFabEnabled(true)

(containerActivity as FileDisplayActivity).showBottomNavBar(true)

// Show sort options view when multi-selection mode finish
binding.optionsLayout.visibility = View.VISIBLE

fileListAdapter.clearSelection()
}
}
}