mirror of
https://github.com/nextcloud/android.git
synced 2024-12-18 15:01:57 +03:00
Merge pull request #13982 from nextcloud/trashbin-multiple-selection
Add Bulk Action Support to Trashbin
This commit is contained in:
commit
3e656486c7
12 changed files with 849 additions and 37 deletions
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
@ -123,6 +124,7 @@ import com.owncloud.android.ui.preview.PreviewTextFileFragment;
|
|||
import com.owncloud.android.ui.preview.PreviewTextFragment;
|
||||
import com.owncloud.android.ui.preview.PreviewTextStringFragment;
|
||||
import com.owncloud.android.ui.preview.pdf.PreviewPdfFragment;
|
||||
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsBottomSheet;
|
||||
import com.owncloud.android.ui.trashbin.TrashbinActivity;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
|
@ -225,6 +227,9 @@ abstract class ComponentsModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract TrashbinActivity trashbinActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract TrashbinFileActionsBottomSheet trashbinFileActionsBottomSheet();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract UploadFilesActivity uploadFilesActivity();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
@ -13,6 +14,7 @@ import com.nextcloud.client.etm.EtmViewModel
|
|||
import com.nextcloud.client.logger.ui.LogsViewModel
|
||||
import com.nextcloud.ui.fileactions.FileActionsViewModel
|
||||
import com.owncloud.android.ui.preview.pdf.PreviewPdfViewModel
|
||||
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsViewModel
|
||||
import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
@ -50,6 +52,11 @@ abstract class ViewModelModule {
|
|||
@ViewModelKey(DocumentScanViewModel::class)
|
||||
abstract fun documentScanViewModel(vm: DocumentScanViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(TrashbinFileActionsViewModel::class)
|
||||
abstract fun trashbinFileActionsViewModel(vm: TrashbinFileActionsViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.ui.trashbinFileActions
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.owncloud.android.R
|
||||
|
||||
enum class TrashbinFileAction(@IdRes val id: Int, @StringRes val title: Int, @DrawableRes val icon: Int? = null) {
|
||||
DELETE_PERMANENTLY(R.id.action_delete, R.string.trashbin_file_remove, R.drawable.ic_delete),
|
||||
RESTORE(R.id.restore, R.string.restore_item, R.drawable.ic_history),
|
||||
SELECT_ALL(R.id.action_select_all_action_menu, R.string.select_all, R.drawable.ic_select_all),
|
||||
SELECT_NONE(R.id.action_deselect_all_action_menu, R.string.deselect_all, R.drawable.ic_select_none);
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* All file actions, in the order they should be displayed
|
||||
*/
|
||||
@JvmField
|
||||
val SORTED_VALUES = listOf(
|
||||
DELETE_PERMANENTLY,
|
||||
RESTORE,
|
||||
SELECT_ALL,
|
||||
SELECT_NONE
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.ui.trashbinFileActions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isEmpty
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
import com.nextcloud.client.account.CurrentAccountProvider
|
||||
import com.nextcloud.client.di.Injectable
|
||||
import com.nextcloud.client.di.ViewModelFactory
|
||||
import com.nextcloud.utils.extensions.toOCFile
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.databinding.FileActionsBottomSheetBinding
|
||||
import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.SyncedFolderProvider
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager
|
||||
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
|
||||
import com.owncloud.android.utils.DisplayUtils
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrashbinFileActionsBottomSheet : BottomSheetDialogFragment(), Injectable {
|
||||
|
||||
@Inject
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
@Inject
|
||||
lateinit var vmFactory: ViewModelFactory
|
||||
|
||||
@Inject
|
||||
lateinit var currentUserProvider: CurrentAccountProvider
|
||||
|
||||
@Inject
|
||||
lateinit var storageManager: FileDataStorageManager
|
||||
|
||||
@Inject
|
||||
lateinit var syncedFolderProvider: SyncedFolderProvider
|
||||
|
||||
private lateinit var viewModel: TrashbinFileActionsViewModel
|
||||
|
||||
private var _binding: FileActionsBottomSheetBinding? = null
|
||||
val binding
|
||||
get() = _binding!!
|
||||
|
||||
private val thumbnailAsyncTasks = mutableListOf<ThumbnailsCacheManager.ThumbnailGenerationTask>()
|
||||
|
||||
fun interface ResultListener {
|
||||
fun onResult(@IdRes actionId: Int)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
viewModel = ViewModelProvider(this, vmFactory)[TrashbinFileActionsViewModel::class.java]
|
||||
_binding = FileActionsBottomSheetBinding.inflate(inflater, container, false)
|
||||
|
||||
viewModel.uiState.observe(viewLifecycleOwner, this::handleState)
|
||||
|
||||
viewModel.clickActionId.observe(viewLifecycleOwner) { id ->
|
||||
dispatchActionClick(id)
|
||||
}
|
||||
|
||||
viewModel.load(requireArguments())
|
||||
|
||||
val bottomSheetDialog = dialog as BottomSheetDialog
|
||||
bottomSheetDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
bottomSheetDialog.behavior.skipCollapsed = true
|
||||
|
||||
viewThemeUtils.platform.colorViewBackground(binding.bottomSheet, ColorRole.SURFACE)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun handleState(state: TrashbinFileActionsViewModel.UiState) {
|
||||
toggleLoadingOrContent(state)
|
||||
when (state) {
|
||||
is TrashbinFileActionsViewModel.UiState.LoadedForSingleFile -> {
|
||||
loadFileThumbnail(state.titleFile)
|
||||
displayActions(state.actions)
|
||||
displayTitle(state.titleFile)
|
||||
}
|
||||
|
||||
is TrashbinFileActionsViewModel.UiState.LoadedForMultipleFiles -> {
|
||||
setMultipleFilesThumbnail()
|
||||
displayActions(state.actions)
|
||||
displayTitle(state.fileCount)
|
||||
}
|
||||
|
||||
TrashbinFileActionsViewModel.UiState.Loading -> {}
|
||||
TrashbinFileActionsViewModel.UiState.Error -> {
|
||||
context?.let {
|
||||
Toast.makeText(it, R.string.error_file_actions, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFileThumbnail(titleFile: TrashbinFile?) {
|
||||
titleFile?.let {
|
||||
DisplayUtils.setThumbnail(
|
||||
it.toOCFile(),
|
||||
binding.thumbnailLayout.thumbnail,
|
||||
currentUserProvider.user,
|
||||
storageManager,
|
||||
thumbnailAsyncTasks,
|
||||
false,
|
||||
context,
|
||||
binding.thumbnailLayout.thumbnailShimmer,
|
||||
syncedFolderProvider.preferences,
|
||||
viewThemeUtils,
|
||||
syncedFolderProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMultipleFilesThumbnail() {
|
||||
context?.let {
|
||||
val drawable = viewThemeUtils.platform.tintDrawable(it, R.drawable.file_multiple, ColorRole.PRIMARY)
|
||||
binding.thumbnailLayout.thumbnail.setImageDrawable(drawable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
fun setResultListener(
|
||||
fragmentManager: FragmentManager,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
listener: ResultListener
|
||||
): TrashbinFileActionsBottomSheet {
|
||||
fragmentManager.setFragmentResultListener(REQUEST_KEY, lifecycleOwner) { _, result ->
|
||||
@IdRes val actionId = result.getInt(RESULT_KEY_ACTION_ID, -1)
|
||||
if (actionId != -1) {
|
||||
listener.onResult(actionId)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun toggleLoadingOrContent(state: TrashbinFileActionsViewModel.UiState) {
|
||||
if (state is TrashbinFileActionsViewModel.UiState.Loading) {
|
||||
binding.bottomSheetLoading.isVisible = true
|
||||
binding.bottomSheetHeader.isVisible = false
|
||||
viewThemeUtils.platform.colorCircularProgressBar(binding.bottomSheetLoading, ColorRole.PRIMARY)
|
||||
} else {
|
||||
binding.bottomSheetLoading.isVisible = false
|
||||
binding.bottomSheetHeader.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayActions(actions: List<TrashbinFileAction>) {
|
||||
if (binding.fileActionsList.isEmpty()) {
|
||||
actions.forEach { action ->
|
||||
val view = inflateActionView(action)
|
||||
binding.fileActionsList.addView(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayTitle(titleFile: TrashbinFile?) {
|
||||
titleFile?.fileName?.let {
|
||||
binding.title.text = it
|
||||
} ?: { binding.title.isVisible = false }
|
||||
}
|
||||
|
||||
private fun displayTitle(fileCount: Int) {
|
||||
binding.title.text = resources.getQuantityString(R.plurals.trashbin_list__footer__file, fileCount, fileCount)
|
||||
}
|
||||
|
||||
private fun inflateActionView(action: TrashbinFileAction): View {
|
||||
val itemBinding = FileActionsBottomSheetItemBinding.inflate(layoutInflater, binding.fileActionsList, false)
|
||||
.apply {
|
||||
root.setOnClickListener {
|
||||
viewModel.onClick(action)
|
||||
}
|
||||
text.setText(action.title)
|
||||
if (action.icon != null) {
|
||||
val drawable =
|
||||
viewThemeUtils.platform.tintDrawable(
|
||||
requireContext(),
|
||||
AppCompatResources.getDrawable(requireContext(), action.icon)!!
|
||||
)
|
||||
icon.setImageDrawable(drawable)
|
||||
}
|
||||
}
|
||||
return itemBinding.root
|
||||
}
|
||||
|
||||
private fun dispatchActionClick(id: Int?) {
|
||||
if (id != null) {
|
||||
setFragmentResult(REQUEST_KEY, bundleOf(RESULT_KEY_ACTION_ID to id))
|
||||
parentFragmentManager.clearFragmentResultListener(REQUEST_KEY)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_KEY = "REQUEST_KEY_ACTION"
|
||||
private const val RESULT_KEY_ACTION_ID = "RESULT_KEY_ACTION_ID"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(numberOfAllFiles: Int, files: Collection<TrashbinFile>): TrashbinFileActionsBottomSheet {
|
||||
return TrashbinFileActionsBottomSheet().apply {
|
||||
val argsBundle = bundleOf(
|
||||
TrashbinFileActionsViewModel.ARG_ALL_FILES_COUNT to numberOfAllFiles,
|
||||
TrashbinFileActionsViewModel.ARG_FILES to ArrayList<TrashbinFile>(files)
|
||||
)
|
||||
arguments = argsBundle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.ui.trashbinFileActions
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.nextcloud.client.logger.Logger
|
||||
import com.nextcloud.ui.fileactions.FileActionsViewModel
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrashbinFileActionsViewModel @Inject constructor(
|
||||
private val logger: Logger
|
||||
) : ViewModel() {
|
||||
|
||||
sealed interface UiState {
|
||||
data object Loading : UiState
|
||||
data object Error : UiState
|
||||
data class LoadedForSingleFile(
|
||||
val actions: List<TrashbinFileAction>,
|
||||
val titleFile: TrashbinFile?
|
||||
) : UiState
|
||||
|
||||
data class LoadedForMultipleFiles(val actions: List<TrashbinFileAction>, val fileCount: Int) : UiState
|
||||
}
|
||||
|
||||
private val _uiState: MutableLiveData<UiState> = MutableLiveData(UiState.Loading)
|
||||
val uiState: LiveData<UiState>
|
||||
get() = _uiState
|
||||
|
||||
private val _clickActionId: MutableLiveData<Int?> = MutableLiveData(null)
|
||||
val clickActionId: LiveData<Int?>
|
||||
@IdRes
|
||||
get() = _clickActionId
|
||||
|
||||
fun load(arguments: Bundle) {
|
||||
val files: List<TrashbinFile>? = arguments.getParcelableArrayList(ARG_FILES)
|
||||
val numberOfAllFiles: Int = arguments.getInt(FileActionsViewModel.ARG_ALL_FILES_COUNT, 1)
|
||||
|
||||
if (files.isNullOrEmpty()) {
|
||||
logger.d(TAG, "No valid files argument for loading actions")
|
||||
_uiState.postValue(UiState.Error)
|
||||
} else {
|
||||
load(files.toList(), numberOfAllFiles)
|
||||
}
|
||||
}
|
||||
|
||||
private fun load(files: Collection<TrashbinFile>, numberOfAllFiles: Int?) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val toHide = getHiddenActions(numberOfAllFiles, files)
|
||||
val availableActions = getActionsToShow(toHide)
|
||||
updateStateLoaded(files, availableActions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHiddenActions(numberOfAllFiles: Int?, files: Collection<TrashbinFile>): List<Int> {
|
||||
numberOfAllFiles?.let {
|
||||
if (files.size >= it) {
|
||||
return listOf(R.id.action_select_all_action_menu)
|
||||
}
|
||||
}
|
||||
|
||||
return listOf()
|
||||
}
|
||||
|
||||
private fun getActionsToShow(toHide: List<Int>) = TrashbinFileAction.SORTED_VALUES.filter { it.id !in toHide }
|
||||
|
||||
private fun updateStateLoaded(files: Collection<TrashbinFile>, availableActions: List<TrashbinFileAction>) {
|
||||
val state: UiState = when (files.size) {
|
||||
1 -> {
|
||||
val file = files.first()
|
||||
UiState.LoadedForSingleFile(availableActions, file)
|
||||
}
|
||||
|
||||
else -> UiState.LoadedForMultipleFiles(availableActions, files.size)
|
||||
}
|
||||
_uiState.postValue(state)
|
||||
}
|
||||
|
||||
fun onClick(action: TrashbinFileAction) {
|
||||
_clickActionId.value = action.id
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ARG_ALL_FILES_COUNT = "ALL_FILES_COUNT"
|
||||
const val ARG_FILES = "FILES"
|
||||
|
||||
private val TAG = TrashbinFileActionsViewModel::class.simpleName!!
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
|
@ -17,6 +18,8 @@ import android.text.method.LinkMovementMethod
|
|||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
@ -79,3 +82,12 @@ fun Long.getFormattedStringDate(format: String): String {
|
|||
val simpleDateFormat = SimpleDateFormat(format, Locale.getDefault())
|
||||
return simpleDateFormat.format(Date(this))
|
||||
}
|
||||
|
||||
fun TrashbinFile.toOCFile(): OCFile {
|
||||
val ocFile = OCFile(this.remotePath)
|
||||
ocFile.mimeType = this.mimeType
|
||||
ocFile.fileLength = this.fileLength
|
||||
ocFile.remoteId = this.remoteId
|
||||
ocFile.fileName = this.fileName
|
||||
return ocFile
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
|
@ -16,12 +17,15 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole;
|
||||
import com.nextcloud.client.account.User;
|
||||
import com.nextcloud.client.preferences.AppPreferences;
|
||||
import com.nextcloud.utils.extensions.ViewExtensionsKt;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.databinding.ListFooterBinding;
|
||||
import com.owncloud.android.databinding.TrashbinItemBinding;
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager;
|
||||
import com.owncloud.android.datamodel.SyncedFolderProvider;
|
||||
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
|
||||
import com.owncloud.android.lib.common.utils.Log_OC;
|
||||
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile;
|
||||
|
@ -32,11 +36,16 @@ import com.owncloud.android.utils.MimeTypeUtil;
|
|||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static com.nextcloud.utils.extensions.ViewExtensionsKt.createRoundedOutline;
|
||||
import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
|
||||
import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
|
||||
|
||||
|
@ -57,11 +66,16 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
private final AppPreferences preferences;
|
||||
private final List<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks = new ArrayList<>();
|
||||
private final ViewThemeUtils viewThemeUtils;
|
||||
private final SyncedFolderProvider syncedFolderProvider;
|
||||
|
||||
private final Set<TrashbinFile> checkedFiles = new HashSet<>();
|
||||
private boolean isMultiSelect = false;
|
||||
|
||||
public TrashbinListAdapter(
|
||||
TrashbinActivityInterface trashbinActivityInterface,
|
||||
FileDataStorageManager storageManager,
|
||||
AppPreferences preferences,
|
||||
SyncedFolderProvider syncedFolderProvider,
|
||||
Context context,
|
||||
User user,
|
||||
ViewThemeUtils viewThemeUtils
|
||||
|
@ -72,6 +86,7 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
this.storageManager = storageManager;
|
||||
this.preferences = preferences;
|
||||
this.context = context;
|
||||
this.syncedFolderProvider = syncedFolderProvider;
|
||||
this.viewThemeUtils = viewThemeUtils;
|
||||
}
|
||||
|
||||
|
@ -110,6 +125,7 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
|
||||
// layout
|
||||
trashbinFileViewHolder.binding.ListItemLayout.setOnClickListener(v -> trashbinActivityInterface.onItemClicked(file));
|
||||
trashbinFileViewHolder.binding.ListItemLayout.setOnLongClickListener(v -> trashbinActivityInterface.onLongItemClicked(file));
|
||||
|
||||
// thumbnail
|
||||
trashbinFileViewHolder.binding.thumbnail.setTag(file.getRemoteId());
|
||||
|
@ -136,15 +152,44 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
file.getDeletionTimestamp() * 1000));
|
||||
|
||||
// checkbox
|
||||
trashbinFileViewHolder.binding.customCheckbox.setVisibility(View.GONE);
|
||||
if (isCheckedFile(file)) {
|
||||
trashbinFileViewHolder.binding.customCheckbox.setImageDrawable(
|
||||
viewThemeUtils.platform.tintDrawable(context, R.drawable.ic_checkbox_marked, ColorRole.PRIMARY));
|
||||
} else {
|
||||
trashbinFileViewHolder.binding.customCheckbox.setImageResource(R.drawable.ic_checkbox_blank_outline);
|
||||
}
|
||||
|
||||
trashbinFileViewHolder.binding.customCheckbox.setVisibility(isMultiSelect ? View.VISIBLE : View.GONE);
|
||||
trashbinFileViewHolder.binding.restore.setVisibility(isMultiSelect ? View.GONE : View.VISIBLE);
|
||||
ViewExtensionsKt.setVisibleIf(trashbinFileViewHolder.binding.overflowMenu, !isMultiSelect());
|
||||
|
||||
// overflow menu
|
||||
trashbinFileViewHolder.binding.overflowMenu.setOnClickListener(v ->
|
||||
trashbinActivityInterface.onOverflowIconClicked(file, v));
|
||||
|
||||
// restore button
|
||||
trashbinFileViewHolder.binding.restore.setOnClickListener(v ->
|
||||
trashbinActivityInterface.onRestoreIconClicked(file, v));
|
||||
trashbinFileViewHolder.binding.restore.setOnClickListener(v -> trashbinActivityInterface.onRestoreIconClicked(file));
|
||||
|
||||
float cornerRadius = context.getResources().getDimension(R.dimen.selected_grid_container_radius);
|
||||
|
||||
boolean isDarkModeActive = (syncedFolderProvider.getPreferences().isDarkModeEnabled());
|
||||
int selectedItemBackgroundColorId;
|
||||
if (isDarkModeActive) {
|
||||
selectedItemBackgroundColorId = R.color.action_mode_background;
|
||||
} else {
|
||||
selectedItemBackgroundColorId = R.color.selected_item_background;
|
||||
}
|
||||
|
||||
int itemLayoutBackgroundColorId;
|
||||
if (isCheckedFile(file)) {
|
||||
itemLayoutBackgroundColorId = selectedItemBackgroundColorId;
|
||||
} else {
|
||||
itemLayoutBackgroundColorId = R.color.bg_default;
|
||||
}
|
||||
|
||||
trashbinFileViewHolder.binding.ListItemLayout.setOutlineProvider(createRoundedOutline(context, cornerRadius));
|
||||
trashbinFileViewHolder.binding.ListItemLayout.setClipToOutline(true);
|
||||
trashbinFileViewHolder.binding.ListItemLayout.setBackgroundColor(ContextCompat.getColor(context, itemLayoutBackgroundColorId));
|
||||
|
||||
} else {
|
||||
TrashbinFooterViewHolder trashbinFooterViewHolder = (TrashbinFooterViewHolder) holder;
|
||||
|
@ -275,6 +320,18 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
return files.size() + 1;
|
||||
}
|
||||
|
||||
public int getFilesCount() {
|
||||
return files.size();
|
||||
}
|
||||
|
||||
public void notifyItemChanged(@NonNull TrashbinFile file) {
|
||||
notifyItemChanged(getItemPosition(file));
|
||||
}
|
||||
|
||||
public int getItemPosition(@NonNull TrashbinFile file) {
|
||||
return files.indexOf(file);
|
||||
}
|
||||
|
||||
public void cancelAllPendingTasks() {
|
||||
for (ThumbnailsCacheManager.ThumbnailGenerationTask task : asyncTasks) {
|
||||
if (task != null) {
|
||||
|
@ -295,6 +352,49 @@ public class TrashbinListAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setMultiSelect(boolean bool) {
|
||||
isMultiSelect = bool;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean isMultiSelect() {
|
||||
return isMultiSelect;
|
||||
}
|
||||
|
||||
public boolean isCheckedFile(TrashbinFile file) {
|
||||
return checkedFiles.contains(file);
|
||||
}
|
||||
|
||||
public void addCheckedFile(TrashbinFile file) {
|
||||
checkedFiles.add(file);
|
||||
}
|
||||
|
||||
public void removeCheckedFile(TrashbinFile file) {
|
||||
checkedFiles.remove(file);
|
||||
}
|
||||
|
||||
public void addToCheckedFiles(@Nullable List<TrashbinFile> files) {
|
||||
checkedFiles.addAll(files);
|
||||
}
|
||||
|
||||
public Set<TrashbinFile> getCheckedItems() {
|
||||
return checkedFiles;
|
||||
}
|
||||
|
||||
public void setCheckedItem(@Nullable Set<TrashbinFile> files) {
|
||||
checkedFiles.clear();
|
||||
checkedFiles.addAll(files);
|
||||
}
|
||||
|
||||
public void clearCheckedItems() {
|
||||
checkedFiles.clear();
|
||||
}
|
||||
|
||||
public void addAllFilesToCheckedFiles() {
|
||||
addToCheckedFiles(files);
|
||||
}
|
||||
|
||||
public class TrashbinFileViewHolder extends RecyclerView.ViewHolder {
|
||||
protected TrashbinItemBinding binding;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
|
@ -10,11 +11,10 @@ package com.owncloud.android.ui.interfaces;
|
|||
import android.view.View;
|
||||
|
||||
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile;
|
||||
import com.owncloud.android.ui.adapter.OCFileListAdapter;
|
||||
|
||||
/**
|
||||
* Interface for communication between {@link com.owncloud.android.ui.fragment.OCFileListFragment}
|
||||
* and {@link OCFileListAdapter}
|
||||
* Interface for communication between {@link com.owncloud.android.ui.trashbin.TrashbinActivity}
|
||||
* and {@link com.owncloud.android.ui.adapter.TrashbinListAdapter}
|
||||
*/
|
||||
|
||||
public interface TrashbinActivityInterface {
|
||||
|
@ -22,5 +22,7 @@ public interface TrashbinActivityInterface {
|
|||
|
||||
void onItemClicked(TrashbinFile file);
|
||||
|
||||
void onRestoreIconClicked(TrashbinFile file, View view);
|
||||
boolean onLongItemClicked(TrashbinFile file);
|
||||
|
||||
void onRestoreIconClicked(TrashbinFile file);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH
|
||||
|
@ -10,23 +11,32 @@ package com.owncloud.android.ui.trashbin
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AbsListView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.client.account.CurrentAccountProvider
|
||||
import com.nextcloud.client.di.Injectable
|
||||
import com.nextcloud.client.network.ClientFactory
|
||||
import com.nextcloud.client.preferences.AppPreferences
|
||||
import com.nextcloud.client.utils.Throttler
|
||||
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsBottomSheet
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.databinding.TrashbinActivityBinding
|
||||
import com.owncloud.android.datamodel.SyncedFolderProvider
|
||||
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
|
||||
import com.owncloud.android.ui.activity.DrawerActivity
|
||||
import com.owncloud.android.ui.adapter.TrashbinListAdapter
|
||||
|
@ -51,6 +61,9 @@ class TrashbinActivity :
|
|||
@Inject
|
||||
var preferences: AppPreferences? = null
|
||||
|
||||
@Inject
|
||||
lateinit var syncedFolderProvider: SyncedFolderProvider
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var accountProvider: CurrentAccountProvider? = null
|
||||
|
@ -59,9 +72,8 @@ class TrashbinActivity :
|
|||
@Inject
|
||||
var clientFactory: ClientFactory? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var viewThemeUtils: ViewThemeUtils? = null
|
||||
lateinit var throttler: Throttler
|
||||
|
||||
private var trashbinListAdapter: TrashbinListAdapter? = null
|
||||
|
||||
|
@ -71,6 +83,8 @@ class TrashbinActivity :
|
|||
private var active = false
|
||||
lateinit var binding: TrashbinActivityBinding
|
||||
|
||||
private var mMultiChoiceModeListener: MultiChoiceModeListener? = null
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
trashbinPresenter?.navigateUp()
|
||||
|
@ -134,6 +148,7 @@ class TrashbinActivity :
|
|||
this,
|
||||
storageManager,
|
||||
preferences,
|
||||
syncedFolderProvider,
|
||||
this,
|
||||
user.orElse(accountProvider!!.user),
|
||||
viewThemeUtils
|
||||
|
@ -161,6 +176,13 @@ class TrashbinActivity :
|
|||
loadFolder()
|
||||
|
||||
handleOnBackPressed()
|
||||
|
||||
mMultiChoiceModeListener = MultiChoiceModeListener(
|
||||
this,
|
||||
trashbinListAdapter,
|
||||
viewThemeUtils
|
||||
) { filesCount, checkedFiles -> openActionsMenu(filesCount, checkedFiles) }
|
||||
addDrawerListener(mMultiChoiceModeListener)
|
||||
}
|
||||
|
||||
private fun handleOnBackPressed() {
|
||||
|
@ -171,6 +193,8 @@ class TrashbinActivity :
|
|||
}
|
||||
|
||||
fun loadFolder(onComplete: () -> Unit = {}, onError: () -> Unit = {}) {
|
||||
// exit action mode on data refresh
|
||||
mMultiChoiceModeListener?.exitSelectionMode()
|
||||
trashbinListAdapter?.let {
|
||||
if (it.itemCount > EMPTY_LIST_COUNT) {
|
||||
binding.swipeContainingList.isRefreshing = true
|
||||
|
@ -205,20 +229,50 @@ class TrashbinActivity :
|
|||
val popup = PopupMenu(this, view)
|
||||
popup.inflate(R.menu.item_trashbin)
|
||||
popup.setOnMenuItemClickListener {
|
||||
trashbinPresenter?.removeTrashbinFile(file)
|
||||
onFileActionChosen(it.itemId, setOf(file))
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
override fun onItemClicked(file: TrashbinFile) {
|
||||
if (file.isFolder) {
|
||||
if (trashbinListAdapter?.isMultiSelect == true) {
|
||||
toggleItemToCheckedList(file)
|
||||
} else if (file.isFolder) {
|
||||
trashbinPresenter?.enterFolder(file.remotePath)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreIconClicked(file: TrashbinFile, view: View) {
|
||||
trashbinPresenter?.restoreTrashbinFile(file)
|
||||
override fun onRestoreIconClicked(file: TrashbinFile) {
|
||||
trashbinPresenter?.restoreTrashbinFile(listOf(file))
|
||||
}
|
||||
|
||||
override fun onLongItemClicked(file: TrashbinFile): Boolean {
|
||||
// Create only once instance of action mode
|
||||
if (mMultiChoiceModeListener?.mActiveActionMode != null) {
|
||||
toggleItemToCheckedList(file)
|
||||
} else {
|
||||
startActionMode(mMultiChoiceModeListener)
|
||||
trashbinListAdapter?.addCheckedFile(file)
|
||||
}
|
||||
mMultiChoiceModeListener?.updateActionModeFile(file)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Will toggle a file selection status from the action mode
|
||||
*
|
||||
* @param file The concerned TrashbinFile by the selection/deselection
|
||||
*/
|
||||
private fun toggleItemToCheckedList(file: TrashbinFile) {
|
||||
trashbinListAdapter?.run {
|
||||
if (isCheckedFile(file)) {
|
||||
removeCheckedFile(file)
|
||||
} else {
|
||||
addCheckedFile(file)
|
||||
}
|
||||
}
|
||||
mMultiChoiceModeListener?.updateActionModeFile(file)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
@ -315,7 +369,262 @@ class TrashbinActivity :
|
|||
}
|
||||
}
|
||||
|
||||
private fun openActionsMenu(filesCount: Int, checkedFiles: Set<TrashbinFile>) {
|
||||
throttler.run("overflowClick") {
|
||||
val supportFragmentManager = supportFragmentManager
|
||||
|
||||
TrashbinFileActionsBottomSheet.newInstance(filesCount, checkedFiles)
|
||||
.setResultListener(
|
||||
supportFragmentManager,
|
||||
this
|
||||
) { id: Int ->
|
||||
onFileActionChosen(
|
||||
id,
|
||||
checkedFiles
|
||||
)
|
||||
}
|
||||
.show(supportFragmentManager, "actions")
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFileActionChosen(@IdRes itemId: Int, checkedFiles: Set<TrashbinFile>): Boolean {
|
||||
if (checkedFiles.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
when (itemId) {
|
||||
R.id.action_delete -> {
|
||||
trashbinPresenter?.removeTrashbinFile(checkedFiles)
|
||||
mMultiChoiceModeListener?.exitSelectionMode()
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.restore -> {
|
||||
trashbinPresenter?.restoreTrashbinFile(checkedFiles)
|
||||
mMultiChoiceModeListener?.exitSelectionMode()
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.action_select_all_action_menu -> {
|
||||
selectAllFiles(true)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.action_deselect_all_action_menu -> {
|
||||
selectAllFiles(false)
|
||||
return true
|
||||
}
|
||||
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* De-/select all elements in the current list view.
|
||||
*
|
||||
* @param select `true` to select all, `false` to deselect all
|
||||
*/
|
||||
private fun selectAllFiles(select: Boolean) {
|
||||
trashbinListAdapter?.let {
|
||||
if (select) {
|
||||
it.addAllFilesToCheckedFiles()
|
||||
} else {
|
||||
it.clearCheckedItems()
|
||||
}
|
||||
for (i in 0 until it.itemCount) {
|
||||
it.notifyItemChanged(i)
|
||||
}
|
||||
|
||||
mMultiChoiceModeListener?.invalidateActionMode()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EMPTY_LIST_COUNT = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for multiple selection mode.
|
||||
*
|
||||
*
|
||||
* Manages input from the user when one or more files or folders are selected in the list.
|
||||
*
|
||||
*
|
||||
* Also listens to changes in navigation drawer to hide and recover multiple selection when it's opened and closed.
|
||||
*/
|
||||
internal class MultiChoiceModeListener(
|
||||
val activity: TrashbinActivity,
|
||||
val adapter: TrashbinListAdapter?,
|
||||
val viewThemeUtils: ViewThemeUtils,
|
||||
val openActionsMenu: (Int, Set<TrashbinFile>) -> Unit
|
||||
) : AbsListView.MultiChoiceModeListener, DrawerLayout.DrawerListener {
|
||||
|
||||
var mActiveActionMode: ActionMode? = null
|
||||
private var mIsActionModeNew = false
|
||||
|
||||
/**
|
||||
* True when action mode is finished because the drawer was opened
|
||||
*/
|
||||
private var mActionModeClosedByDrawer = false
|
||||
|
||||
/**
|
||||
* Selected items in list when action mode is closed by drawer
|
||||
*/
|
||||
private val mSelectionWhenActionModeClosedByDrawer: MutableSet<TrashbinFile> = HashSet()
|
||||
|
||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* When the navigation drawer is closed, action mode is recovered in the same state as was when the drawer was
|
||||
* (started to be) opened.
|
||||
*
|
||||
* @param drawerView Navigation drawer just closed.
|
||||
*/
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
if (mActionModeClosedByDrawer && mSelectionWhenActionModeClosedByDrawer.size > 0) {
|
||||
activity.startActionMode(this)
|
||||
|
||||
adapter?.setCheckedItem(mSelectionWhenActionModeClosedByDrawer)
|
||||
|
||||
mActiveActionMode?.invalidate()
|
||||
|
||||
mSelectionWhenActionModeClosedByDrawer.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the action mode is active when the navigation drawer starts to move, the action mode is closed and the
|
||||
* selection stored to be recovered when the drawer is closed.
|
||||
*
|
||||
* @param newState One of STATE_IDLE, STATE_DRAGGING or STATE_SETTLING.
|
||||
*/
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
if (DrawerLayout.STATE_DRAGGING == newState && mActiveActionMode != null) {
|
||||
adapter?.let {
|
||||
mSelectionWhenActionModeClosedByDrawer.addAll(
|
||||
it.checkedItems
|
||||
)
|
||||
}
|
||||
|
||||
mActiveActionMode?.finish()
|
||||
mActionModeClosedByDrawer = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update action mode bar when an item is selected / unselected in the list
|
||||
*/
|
||||
override fun onItemCheckedStateChanged(mode: ActionMode, position: Int, id: Long, checked: Boolean) {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Load menu and customize UI when action mode is started.
|
||||
*/
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mActiveActionMode = mode
|
||||
// Determine if actionMode is "new" or not (already affected by item-selection)
|
||||
mIsActionModeNew = true
|
||||
|
||||
// fake menu to be able to use bottom sheet instead
|
||||
val inflater: MenuInflater = activity.menuInflater
|
||||
inflater.inflate(R.menu.custom_menu_placeholder, menu)
|
||||
val item = menu.findItem(R.id.custom_menu_placeholder_item)
|
||||
item.icon?.let {
|
||||
item.setIcon(
|
||||
viewThemeUtils.platform.colorDrawable(
|
||||
it,
|
||||
ContextCompat.getColor(activity, R.color.white)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
mode.invalidate()
|
||||
|
||||
// set actionMode color
|
||||
viewThemeUtils.platform.colorStatusBar(
|
||||
activity,
|
||||
ContextCompat.getColor(activity, R.color.action_mode_background)
|
||||
)
|
||||
|
||||
adapter?.setMultiSelect(true)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates available action in menu depending on current selection.
|
||||
*/
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val checkedFiles: Set<TrashbinFile> = adapter?.checkedItems ?: emptySet()
|
||||
val checkedCount = checkedFiles.size
|
||||
val title: String =
|
||||
activity.getResources().getQuantityString(R.plurals.items_selected_count, checkedCount, checkedCount)
|
||||
mode.title = title
|
||||
|
||||
// Determine if we need to finish the action mode because there are no items selected
|
||||
if (checkedCount == 0 && !mIsActionModeNew) {
|
||||
exitSelectionMode()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the multi file selection mode.
|
||||
*/
|
||||
fun exitSelectionMode() {
|
||||
mActiveActionMode?.run {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will update (invalidate) the action mode adapter/mode to refresh an item selection change
|
||||
*
|
||||
* @param file The concerned TrashbinFile to refresh in adapter
|
||||
*/
|
||||
fun updateActionModeFile(file: TrashbinFile) {
|
||||
mIsActionModeNew = false
|
||||
mActiveActionMode?.let {
|
||||
it.invalidate()
|
||||
adapter?.notifyItemChanged(file)
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidateActionMode() {
|
||||
mActiveActionMode?.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the corresponding action when a menu item is tapped by the user.
|
||||
*/
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
adapter?.let {
|
||||
val checkedFiles: Set<TrashbinFile> = it.checkedItems
|
||||
if (item.itemId == R.id.custom_menu_placeholder_item) {
|
||||
openActionsMenu(it.filesCount, checkedFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores UI.
|
||||
*/
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
mActiveActionMode = null
|
||||
|
||||
viewThemeUtils.platform.resetStatusBar(activity)
|
||||
|
||||
adapter?.setMultiSelect(false)
|
||||
adapter?.clearCheckedItems()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
|
@ -28,8 +29,8 @@ interface TrashbinContract {
|
|||
fun loadFolder(onCompleted: () -> Unit = {}, onError: () -> Unit = {})
|
||||
fun navigateUp()
|
||||
fun enterFolder(folder: String?)
|
||||
fun restoreTrashbinFile(file: TrashbinFile?)
|
||||
fun removeTrashbinFile(file: TrashbinFile?)
|
||||
fun restoreTrashbinFile(files: Collection<TrashbinFile?>)
|
||||
fun removeTrashbinFile(files: Collection<TrashbinFile?>)
|
||||
fun emptyTrashbin()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
|
@ -61,34 +62,38 @@ class TrashbinPresenter(
|
|||
trashbinView.atRoot(isRoot)
|
||||
}
|
||||
|
||||
override fun restoreTrashbinFile(file: TrashbinFile?) {
|
||||
trashbinRepository.restoreFile(
|
||||
file,
|
||||
object : TrashbinRepository.OperationCallback {
|
||||
override fun onResult(success: Boolean) {
|
||||
if (success) {
|
||||
trashbinView.removeFile(file)
|
||||
} else {
|
||||
trashbinView.showSnackbarError(R.string.trashbin_file_not_restored, file)
|
||||
override fun restoreTrashbinFile(files: Collection<TrashbinFile?>) {
|
||||
for (file in files) {
|
||||
trashbinRepository.restoreFile(
|
||||
file,
|
||||
object : TrashbinRepository.OperationCallback {
|
||||
override fun onResult(success: Boolean) {
|
||||
if (success) {
|
||||
trashbinView.removeFile(file)
|
||||
} else {
|
||||
trashbinView.showSnackbarError(R.string.trashbin_file_not_restored, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeTrashbinFile(file: TrashbinFile?) {
|
||||
trashbinRepository.removeTrashbinFile(
|
||||
file,
|
||||
object : TrashbinRepository.OperationCallback {
|
||||
override fun onResult(success: Boolean) {
|
||||
if (success) {
|
||||
trashbinView.removeFile(file)
|
||||
} else {
|
||||
trashbinView.showSnackbarError(R.string.trashbin_file_not_deleted, file)
|
||||
override fun removeTrashbinFile(files: Collection<TrashbinFile?>) {
|
||||
for (file in files) {
|
||||
trashbinRepository.removeTrashbinFile(
|
||||
file,
|
||||
object : TrashbinRepository.OperationCallback {
|
||||
override fun onResult(success: Boolean) {
|
||||
if (success) {
|
||||
trashbinView.removeFile(file)
|
||||
} else {
|
||||
trashbinView.showSnackbarError(R.string.trashbin_file_not_deleted, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun emptyTrashbin() {
|
||||
|
|
|
@ -584,6 +584,10 @@
|
|||
<item quantity="one">%1$d file</item>
|
||||
<item quantity="other">%1$d files</item>
|
||||
</plurals>
|
||||
<plurals name="trashbin_list__footer__file">
|
||||
<item quantity="one">%1$d item</item>
|
||||
<item quantity="other">%1$d items</item>
|
||||
</plurals>
|
||||
<string name="set_picture_as">Use picture as</string>
|
||||
<string name="set_as">Set As</string>
|
||||
|
||||
|
@ -880,6 +884,7 @@
|
|||
<string name="first_run_4_text">Screensharing, online meetings and web conferences</string>
|
||||
<string name="restore_button_description">Restore deleted file</string>
|
||||
<string name="restore">Restore file</string>
|
||||
<string name="restore_item">Restore</string>
|
||||
<string name="new_version_was_created">New version was created</string>
|
||||
<string name="new_comment">New comment…</string>
|
||||
<string name="error_comment_file">Error commenting file</string>
|
||||
|
|
Loading…
Reference in a new issue