From eb9244df1df8ec475607cd9a53cc78bb5582efda Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 8 Nov 2024 15:42:01 +0530 Subject: [PATCH 1/3] Trashbin: Bulk action implementation Signed-off-by: A117870935 --- .../nextcloud/client/di/ComponentsModule.java | 5 + .../nextcloud/client/di/ViewModelModule.kt | 7 + .../trashbinFileActions/TrashbinFileAction.kt | 32 ++ .../TrashbinFileActionsBottomSheet.kt | 236 +++++++++++++ .../TrashbinFileActionsViewModel.kt | 107 ++++++ .../nextcloud/utils/extensions/Extensions.kt | 12 + .../ui/adapter/TrashbinListAdapter.java | 106 +++++- .../interfaces/TrashbinActivityInterface.java | 10 +- .../android/ui/trashbin/TrashbinActivity.kt | 318 +++++++++++++++++- .../android/ui/trashbin/TrashbinContract.kt | 5 +- .../android/ui/trashbin/TrashbinPresenter.kt | 49 +-- 11 files changed, 850 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt create mode 100644 app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt create mode 100644 app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java index 1715075ba9..277469a573 100644 --- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java +++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2020 Chris Narkiewicz * 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(); diff --git a/app/src/main/java/com/nextcloud/client/di/ViewModelModule.kt b/app/src/main/java/com/nextcloud/client/di/ViewModelModule.kt index 233fe9d73d..eb3e98a6c5 100644 --- a/app/src/main/java/com/nextcloud/client/di/ViewModelModule.kt +++ b/app/src/main/java/com/nextcloud/client/di/ViewModelModule.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2019 Chris Narkiewicz * 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 } diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt new file mode 100644 index 0000000000..11e5787907 --- /dev/null +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt @@ -0,0 +1,32 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2024 TSI-mc + * 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_button_description, 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, + ) + } +} diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt new file mode 100644 index 0000000000..091c746964 --- /dev/null +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt @@ -0,0 +1,236 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2024 TSI-mc + * 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() + + 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) { + 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.file_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, + ): TrashbinFileActionsBottomSheet { + return TrashbinFileActionsBottomSheet().apply { + val argsBundle = bundleOf( + TrashbinFileActionsViewModel.ARG_ALL_FILES_COUNT to numberOfAllFiles, + TrashbinFileActionsViewModel.ARG_FILES to ArrayList(files), + ) + arguments = argsBundle + } + } + } +} diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt new file mode 100644 index 0000000000..d7cbc9bd17 --- /dev/null +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt @@ -0,0 +1,107 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2024 TSI-mc + * 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, + val titleFile: TrashbinFile?, + ) : UiState + + data class LoadedForMultipleFiles(val actions: List, val fileCount: Int) : UiState + } + + private val _uiState: MutableLiveData = MutableLiveData(UiState.Loading) + val uiState: LiveData + get() = _uiState + + private val _clickActionId: MutableLiveData = MutableLiveData(null) + val clickActionId: LiveData + @IdRes + get() = _clickActionId + + fun load(arguments: Bundle) { + val files: List? = 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, + 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, + ): List { + numberOfAllFiles?.let { + if (files.size >= it) { + return listOf(R.id.action_select_all_action_menu) + } + } + + return listOf() + } + + private fun getActionsToShow(toHide: List) = TrashbinFileAction.SORTED_VALUES.filter { it.id !in toHide } + + private fun updateStateLoaded(files: Collection, availableActions: List) { + 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!! + } +} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/Extensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/Extensions.kt index 1b1d5ac6d0..1194301efd 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/Extensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/Extensions.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2023 Tobias Kaminsky * 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 +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java index 8d3ebfecd9..edf604df5f 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/TrashbinListAdapter.java @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * 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 asyncTasks = new ArrayList<>(); private final ViewThemeUtils viewThemeUtils; + private final SyncedFolderProvider syncedFolderProvider; + + private final Set 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 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 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 files) { + checkedFiles.addAll(files); + } + + public Set getCheckedItems() { + return checkedFiles; + } + + public void setCheckedItem(@Nullable Set 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; diff --git a/app/src/main/java/com/owncloud/android/ui/interfaces/TrashbinActivityInterface.java b/app/src/main/java/com/owncloud/android/ui/interfaces/TrashbinActivityInterface.java index 48714c656d..f3fc99a55b 100644 --- a/app/src/main/java/com/owncloud/android/ui/interfaces/TrashbinActivityInterface.java +++ b/app/src/main/java/com/owncloud/android/ui/interfaces/TrashbinActivityInterface.java @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2017 Mario Danic * 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); } diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt index 04adff7f66..98e51cb7dd 100644 --- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2019 Chris Narkiewicz * SPDX-FileCopyrightText: 2018 Tobias Kaminsky * 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,11 @@ class TrashbinActivity : loadFolder() handleOnBackPressed() + + mMultiChoiceModeListener = MultiChoiceModeListener( + this, trashbinListAdapter, viewThemeUtils, + ) { filesCount, checkedFiles -> openActionsMenu(filesCount, checkedFiles) } + addDrawerListener(mMultiChoiceModeListener) } private fun handleOnBackPressed() { @@ -171,6 +191,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 +227,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 +367,261 @@ class TrashbinActivity : } } + private fun openActionsMenu(filesCount: Int, checkedFiles: Set) { + 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): 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) -> 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 = 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 = 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 = 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() + } + } } diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinContract.kt b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinContract.kt index c5c5efec65..d5a763f378 100644 --- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinContract.kt +++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinContract.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2018 Tobias Kaminsky * 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) + fun removeTrashbinFile(files: Collection) fun emptyTrashbin() } } diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinPresenter.kt b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinPresenter.kt index 985b030309..dbf1813d4f 100644 --- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinPresenter.kt +++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinPresenter.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2024 TSI-mc * SPDX-FileCopyrightText: 2018 Tobias Kaminsky * 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) { + 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) { + 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() { From f5b5f8fab6951e468374e51918b4a5fb3c6a85a8 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Tue, 12 Nov 2024 12:31:10 +0530 Subject: [PATCH 2/3] Update strings for plurals. Signed-off-by: A117870935 --- .../nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt | 2 +- .../ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt | 2 +- app/src/main/res/values/strings.xml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt index 11e5787907..e86c8260e4 100644 --- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt @@ -13,7 +13,7 @@ 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_button_description, R.drawable.ic_history), + 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); diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt index 091c746964..7a5d07d978 100644 --- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt @@ -185,7 +185,7 @@ class TrashbinFileActionsBottomSheet : BottomSheetDialogFragment(), Injectable { } private fun displayTitle(fileCount: Int) { - binding.title.text = resources.getQuantityString(R.plurals.file_list__footer__file, fileCount, fileCount) + binding.title.text = resources.getQuantityString(R.plurals.trashbin_list__footer__file, fileCount, fileCount) } private fun inflateActionView(action: TrashbinFileAction): View { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dd01ccb130..d6e39d92e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -576,6 +576,10 @@ %1$d file %1$d files + + %1$d item + %1$d items + Use picture as Set As @@ -872,6 +876,7 @@ Screensharing, online meetings and web conferences Restore deleted file Restore file + Restore New version was created New comment… Error commenting file From 175a2421dd01b2b740dc5e2699994632bf82c334 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Fri, 13 Dec 2024 09:06:39 +0100 Subject: [PATCH 3/3] spotless Signed-off-by: tobiasKaminsky --- .../ui/trashbinFileActions/TrashbinFileAction.kt | 2 +- .../TrashbinFileActionsBottomSheet.kt | 7 ++----- .../TrashbinFileActionsViewModel.kt | 12 +++--------- .../owncloud/android/ui/trashbin/TrashbinActivity.kt | 9 ++++++--- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt index e86c8260e4..9054e8f443 100644 --- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileAction.kt @@ -26,7 +26,7 @@ enum class TrashbinFileAction(@IdRes val id: Int, @StringRes val title: Int, @Dr DELETE_PERMANENTLY, RESTORE, SELECT_ALL, - SELECT_NONE, + SELECT_NONE ) } } diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt index 7a5d07d978..d90abb75b1 100644 --- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsBottomSheet.kt @@ -220,14 +220,11 @@ class TrashbinFileActionsBottomSheet : BottomSheetDialogFragment(), Injectable { private const val RESULT_KEY_ACTION_ID = "RESULT_KEY_ACTION_ID" @JvmStatic - fun newInstance( - numberOfAllFiles: Int, - files: Collection, - ): TrashbinFileActionsBottomSheet { + fun newInstance(numberOfAllFiles: Int, files: Collection): TrashbinFileActionsBottomSheet { return TrashbinFileActionsBottomSheet().apply { val argsBundle = bundleOf( TrashbinFileActionsViewModel.ARG_ALL_FILES_COUNT to numberOfAllFiles, - TrashbinFileActionsViewModel.ARG_FILES to ArrayList(files), + TrashbinFileActionsViewModel.ARG_FILES to ArrayList(files) ) arguments = argsBundle } diff --git a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt index d7cbc9bd17..fe4f6d6a76 100644 --- a/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt +++ b/app/src/main/java/com/nextcloud/ui/trashbinFileActions/TrashbinFileActionsViewModel.kt @@ -29,7 +29,7 @@ class TrashbinFileActionsViewModel @Inject constructor( data object Error : UiState data class LoadedForSingleFile( val actions: List, - val titleFile: TrashbinFile?, + val titleFile: TrashbinFile? ) : UiState data class LoadedForMultipleFiles(val actions: List, val fileCount: Int) : UiState @@ -56,10 +56,7 @@ class TrashbinFileActionsViewModel @Inject constructor( } } - private fun load( - files: Collection, - numberOfAllFiles: Int?, - ) { + private fun load(files: Collection, numberOfAllFiles: Int?) { viewModelScope.launch(Dispatchers.IO) { val toHide = getHiddenActions(numberOfAllFiles, files) val availableActions = getActionsToShow(toHide) @@ -67,10 +64,7 @@ class TrashbinFileActionsViewModel @Inject constructor( } } - private fun getHiddenActions( - numberOfAllFiles: Int?, - files: Collection, - ): List { + private fun getHiddenActions(numberOfAllFiles: Int?, files: Collection): List { numberOfAllFiles?.let { if (files.size >= it) { return listOf(R.id.action_select_all_action_menu) diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt index 98e51cb7dd..aa16d47b88 100644 --- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.kt @@ -178,7 +178,9 @@ class TrashbinActivity : handleOnBackPressed() mMultiChoiceModeListener = MultiChoiceModeListener( - this, trashbinListAdapter, viewThemeUtils, + this, + trashbinListAdapter, + viewThemeUtils ) { filesCount, checkedFiles -> openActionsMenu(filesCount, checkedFiles) } addDrawerListener(mMultiChoiceModeListener) } @@ -373,7 +375,8 @@ class TrashbinActivity : TrashbinFileActionsBottomSheet.newInstance(filesCount, checkedFiles) .setResultListener( - supportFragmentManager, this + supportFragmentManager, + this ) { id: Int -> onFileActionChosen( id, @@ -533,7 +536,7 @@ class TrashbinActivity : 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.icon?.let { item.setIcon( viewThemeUtils.platform.colorDrawable( it,