From e56ebb6e4309954e97b80c223512dd80a532f364 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Fri, 17 Jul 2026 09:17:35 +0200 Subject: [PATCH 01/16] fix: Prevent moving folders into themselves --- .../com/infomaniak/drive/ui/fileList/FileAdapter.kt | 3 ++- .../drive/ui/fileList/SelectFolderActivity.kt | 7 +++++-- .../drive/ui/fileList/SelectFolderFragment.kt | 5 +++-- .../ui/fileList/multiSelect/MultiSelectFragment.kt | 13 +++++++++++-- .../com/infomaniak/drive/ui/menu/GalleryFragment.kt | 2 +- .../main/java/com/infomaniak/drive/utils/Utils.kt | 8 +++++--- app/src/main/res/navigation/main_navigation.xml | 7 ++++++- 7 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt index 439abec792..293f65d3da 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt @@ -75,6 +75,7 @@ open class FileAdapter( var onStopUploadButtonClicked: ((fileName: String) -> Unit)? = null var isSelectingFolder = false + var disabledNavigationFolderIds: Set = emptySet() var showShareFileButton = true var viewHolderType: DisplayType = DisplayType.LIST var uploadInProgress = false @@ -430,7 +431,7 @@ open class FileAdapter( } else -> { if (isSelectingFolder || offlineMode) { - enabledFile(file.isFolder() || (offlineMode && file.isOffline)) + enabledFile((file.isFolder() && file.id !in disabledNavigationFolderIds) || (offlineMode && file.isOffline)) } else { enabledFile() } diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt index bad2bcf8b7..55a8ee6fa7 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt @@ -63,7 +63,8 @@ class SelectFolderActivity : BaseActivity() { val driveId = navigationArgs.driveId val customArgs = navigationArgs.customArgs val currentFolderId = navigationArgs.folderId.getIntOrNull() - val disabledFolderId = navigationArgs.disabledFolderId.getIntOrNull() + val disabledDestinationFolderId = navigationArgs.disabledDestinationFolderId.getIntOrNull() + val disabledNavigationFolderIdsArg = navigationArgs.disabledNavigationFolderIds?.toSet() ?: emptySet() // We're doing this in the mainthread because the FileListFragment rely on mainViewModel.selectFolderUserDrive. // Moving this call in a background thread we'll break everything @@ -75,7 +76,8 @@ class SelectFolderActivity : BaseActivity() { selectFolderViewModel.apply { userDrive = currentUserDrive currentDrive = DriveInfosController.getDrive(userId, driveId) - disableSelectedFolderId = disabledFolderId + disableSelectedFolderId = disabledDestinationFolderId + disabledNavigationFolderIds = disabledNavigationFolderIdsArg } navController.setGraph( @@ -187,6 +189,7 @@ class SelectFolderActivity : BaseActivity() { var userDrive: UserDrive? = null var currentDrive: Drive? = null var disableSelectedFolderId: Int? = null + var disabledNavigationFolderIds: Set = emptySet() fun getFolderName(folderId: Int): String { val selectedFolderName = if (folderId == ROOT_ID) { diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt index de55f9234a..a735e75249 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt @@ -1,6 +1,6 @@ /* * Infomaniak kDrive - Android - * Copyright (C) 2022-2025 Infomaniak Network SA + * Copyright (C) 2022-2026 Infomaniak Network SA * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -79,8 +79,9 @@ class SelectFolderFragment : FileListFragment() { fileAdapter.apply { isSelectingFolder = true + disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds onFileClicked = { file -> - if (file.isFolder() && !file.isDisabled()) { + if (file.isFolder() && !file.isDisabled() && file.id !in disabledNavigationFolderIds) { fileListViewModel.cancelDownloadFiles() safeNavigate( SelectFolderFragmentDirections.fileListFragmentToFileListFragment( diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index ca0afabdb5..ab0299adfe 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -224,8 +224,17 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : ).toBundle() } - fun moveFiles(disabledFolderId: Int?) { - requireContext().moveFileClicked(disabledFolderId, selectFolderResultLauncher, mainViewModel) + fun moveFiles(disabledDestinationFolderId: Int?) { + val disabledNavigationFolderIds = multiSelectManager.getValidSelectedItems() + .filter { it.isFolder() } + .map { it.id } + .toIntArray() + requireContext().moveFileClicked( + disabledDestinationFolderId, + selectFolderResultLauncher, + mainViewModel, + disabledNavigationFolderIds + ) } fun deleteFiles(allSelectedFilesCount: Int? = null) { diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt index 6d86156553..3b8c18194e 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/GalleryFragment.kt @@ -280,7 +280,7 @@ class GalleryFragment : MultiSelectFragment( override fun getAllSelectedFilesCount(): Int? = null fun onMoveButtonClicked() = with(multiSelectManager.selectedItems) { - moveFiles(disabledFolderId = if (count() == 1) first()?.parentId else null) + moveFiles(disabledDestinationFolderId = if (count() == 1) first()?.parentId else null) } override fun performBulkOperation( diff --git a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt index 7708329d55..eecbce9b28 100644 --- a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt +++ b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt @@ -229,9 +229,10 @@ object Utils { fun convertGigaByteToBytes(gigaBytes: Double) = (gigaBytes * 1024.0.pow(3)).toLong() fun Context.moveFileClicked( - disabledFolderId: Int?, + disabledDestinationFolderId: Int?, selectFolderResultLauncher: ActivityResultLauncher, mainViewModel: MainViewModel, + disabledNavigationFolderIds: IntArray? = null, ) { mainViewModel.ignoreSyncOffline = true Intent(this, SelectFolderActivity::class.java).apply { @@ -239,8 +240,9 @@ object Utils { SelectFolderActivityArgs( userId = AccountUtils.currentUserId, driveId = AccountUtils.currentDriveId, - folderId = disabledFolderId ?: -1, - disabledFolderId = disabledFolderId ?: -1, + folderId = disabledDestinationFolderId ?: -1, + disabledDestinationFolderId = disabledDestinationFolderId ?: -1, + disabledNavigationFolderIds = disabledNavigationFolderIds, customArgs = bundleOf( MultiSelectFragment.BULK_OPERATION_CUSTOM_TAG to BulkOperationType.MOVE, SINGLE_OPERATION_CUSTOM_TAG to SingleOperation.MOVE.name, diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 875093e78a..2f01b33127 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -426,9 +426,14 @@ android:defaultValue="" app:argType="string" /> + Date: Fri, 17 Jul 2026 10:06:27 +0200 Subject: [PATCH 02/16] fix: Prevent moving a single folder into itself --- .../drive/ui/fileList/SelectFolderFragment.kt | 2 +- .../ui/fileList/multiSelect/MultiSelectFragment.kt | 12 ++++-------- .../main/java/com/infomaniak/drive/utils/Utils.kt | 4 ++-- .../infomaniak/drive/views/FileInfoActionsView.kt | 7 ++++++- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt index a735e75249..1e272a299e 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt @@ -81,7 +81,7 @@ class SelectFolderFragment : FileListFragment() { isSelectingFolder = true disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds onFileClicked = { file -> - if (file.isFolder() && !file.isDisabled() && file.id !in disabledNavigationFolderIds) { + if (file.isFolder() && !file.isDisabled()) { fileListViewModel.cancelDownloadFiles() safeNavigate( SelectFolderFragmentDirections.fileListFragmentToFileListFragment( diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index ab0299adfe..62ee1f07aa 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -225,15 +225,11 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : } fun moveFiles(disabledDestinationFolderId: Int?) { - val disabledNavigationFolderIds = multiSelectManager.getValidSelectedItems() - .filter { it.isFolder() } - .map { it.id } - .toIntArray() requireContext().moveFileClicked( - disabledDestinationFolderId, - selectFolderResultLauncher, - mainViewModel, - disabledNavigationFolderIds + disabledDestinationFolderId = disabledDestinationFolderId, + selectFolderResultLauncher = selectFolderResultLauncher, + mainViewModel = mainViewModel, + filesToMove = multiSelectManager.getValidSelectedItems(), ) } diff --git a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt index eecbce9b28..715b1391bc 100644 --- a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt +++ b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt @@ -22,7 +22,6 @@ import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.res.Configuration import android.net.Uri import android.view.LayoutInflater import androidx.activity.result.ActivityResultLauncher @@ -232,9 +231,10 @@ object Utils { disabledDestinationFolderId: Int?, selectFolderResultLauncher: ActivityResultLauncher, mainViewModel: MainViewModel, - disabledNavigationFolderIds: IntArray? = null, + filesToMove: List = emptyList(), ) { mainViewModel.ignoreSyncOffline = true + val disabledNavigationFolderIds = filesToMove.filter { it.isFolder() }.map { it.id }.toIntArray() Intent(this, SelectFolderActivity::class.java).apply { putExtras( SelectFolderActivityArgs( diff --git a/app/src/main/java/com/infomaniak/drive/views/FileInfoActionsView.kt b/app/src/main/java/com/infomaniak/drive/views/FileInfoActionsView.kt index 70934df30d..6952ce539f 100644 --- a/app/src/main/java/com/infomaniak/drive/views/FileInfoActionsView.kt +++ b/app/src/main/java/com/infomaniak/drive/views/FileInfoActionsView.kt @@ -664,7 +664,12 @@ class FileInfoActionsView @JvmOverloads constructor( mainViewModel: MainViewModel ) { trackFileActionEvent(MatomoName.Move) - currentContext.moveFileClicked(folderId, selectFolderResultLauncher, mainViewModel) + currentContext.moveFileClicked( + disabledDestinationFolderId = folderId, + selectFolderResultLauncher = selectFolderResultLauncher, + mainViewModel = mainViewModel, + filesToMove = listOfNotNull(currentFile) + ) } @CallSuper From 3bce38380cec6f8ccbcf901fd00153e1b3add129 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Fri, 17 Jul 2026 13:05:39 +0200 Subject: [PATCH 03/16] fix: Prevent moving a folder into itself in select-all moves --- .../java/com/infomaniak/drive/ui/fileList/FileAdapter.kt | 6 +++++- .../infomaniak/drive/ui/fileList/SelectFolderActivity.kt | 3 +++ .../infomaniak/drive/ui/fileList/SelectFolderFragment.kt | 1 + .../drive/ui/fileList/multiSelect/MultiSelectFragment.kt | 5 +++-- app/src/main/java/com/infomaniak/drive/utils/Utils.kt | 2 ++ app/src/main/res/navigation/main_navigation.xml | 4 ++++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt index 293f65d3da..be04068487 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt @@ -76,6 +76,7 @@ open class FileAdapter( var isSelectingFolder = false var disabledNavigationFolderIds: Set = emptySet() + var disabledNavigationParentFolderId: Int? = null var showShareFileButton = true var viewHolderType: DisplayType = DisplayType.LIST var uploadInProgress = false @@ -431,7 +432,10 @@ open class FileAdapter( } else -> { if (isSelectingFolder || offlineMode) { - enabledFile((file.isFolder() && file.id !in disabledNavigationFolderIds) || (offlineMode && file.isOffline)) + val isNavigableFolder = file.isFolder() + && file.id !in disabledNavigationFolderIds + && file.parentId != disabledNavigationParentFolderId + enabledFile(isNavigableFolder || (offlineMode && file.isOffline)) } else { enabledFile() } diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt index 55a8ee6fa7..39a73d5db0 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt @@ -65,6 +65,7 @@ class SelectFolderActivity : BaseActivity() { val currentFolderId = navigationArgs.folderId.getIntOrNull() val disabledDestinationFolderId = navigationArgs.disabledDestinationFolderId.getIntOrNull() val disabledNavigationFolderIdsArg = navigationArgs.disabledNavigationFolderIds?.toSet() ?: emptySet() + val disabledNavigationParentFolderIdArg = navigationArgs.disabledNavigationParentFolderId.getIntOrNull() // We're doing this in the mainthread because the FileListFragment rely on mainViewModel.selectFolderUserDrive. // Moving this call in a background thread we'll break everything @@ -78,6 +79,7 @@ class SelectFolderActivity : BaseActivity() { currentDrive = DriveInfosController.getDrive(userId, driveId) disableSelectedFolderId = disabledDestinationFolderId disabledNavigationFolderIds = disabledNavigationFolderIdsArg + disabledNavigationParentFolderId = disabledNavigationParentFolderIdArg } navController.setGraph( @@ -190,6 +192,7 @@ class SelectFolderActivity : BaseActivity() { var currentDrive: Drive? = null var disableSelectedFolderId: Int? = null var disabledNavigationFolderIds: Set = emptySet() + var disabledNavigationParentFolderId: Int? = null fun getFolderName(folderId: Int): String { val selectedFolderName = if (folderId == ROOT_ID) { diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt index 1e272a299e..f2c9ca6d88 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt @@ -80,6 +80,7 @@ class SelectFolderFragment : FileListFragment() { fileAdapter.apply { isSelectingFolder = true disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds + disabledNavigationParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId onFileClicked = { file -> if (file.isFolder() && !file.isDisabled()) { fileListViewModel.cancelDownloadFiles() diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index 62ee1f07aa..5543f145aa 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -224,12 +224,13 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : ).toBundle() } - fun moveFiles(disabledDestinationFolderId: Int?) { + fun moveFiles(disabledDestinationFolderId: Int?) = with(multiSelectManager) { requireContext().moveFileClicked( disabledDestinationFolderId = disabledDestinationFolderId, selectFolderResultLauncher = selectFolderResultLauncher, mainViewModel = mainViewModel, - filesToMove = multiSelectManager.getValidSelectedItems(), + filesToMove = getValidSelectedItems(), + disabledNavigationParentFolderId = if (isSelectAllOn) currentFolder?.id else null, ) } diff --git a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt index 715b1391bc..ced5d7344c 100644 --- a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt +++ b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt @@ -232,6 +232,7 @@ object Utils { selectFolderResultLauncher: ActivityResultLauncher, mainViewModel: MainViewModel, filesToMove: List = emptyList(), + disabledNavigationParentFolderId: Int? = null, ) { mainViewModel.ignoreSyncOffline = true val disabledNavigationFolderIds = filesToMove.filter { it.isFolder() }.map { it.id }.toIntArray() @@ -243,6 +244,7 @@ object Utils { folderId = disabledDestinationFolderId ?: -1, disabledDestinationFolderId = disabledDestinationFolderId ?: -1, disabledNavigationFolderIds = disabledNavigationFolderIds, + disabledNavigationParentFolderId = disabledNavigationParentFolderId ?: -1, customArgs = bundleOf( MultiSelectFragment.BULK_OPERATION_CUSTOM_TAG to BulkOperationType.MOVE, SINGLE_OPERATION_CUSTOM_TAG to SingleOperation.MOVE.name, diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index 2f01b33127..c66aab925d 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -434,6 +434,10 @@ android:defaultValue="@null" app:argType="integer[]" app:nullable="true" /> + Date: Fri, 17 Jul 2026 14:02:46 +0200 Subject: [PATCH 04/16] fix: Keep deselected folders as valid destinations in a "select all" move --- .../java/com/infomaniak/drive/ui/fileList/FileAdapter.kt | 5 ++++- .../com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt | 3 +++ .../com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt | 1 + .../drive/ui/fileList/multiSelect/MultiSelectFragment.kt | 1 + app/src/main/java/com/infomaniak/drive/utils/Utils.kt | 2 ++ app/src/main/res/navigation/main_navigation.xml | 5 +++++ 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt index be04068487..ef3bac4144 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt @@ -77,6 +77,7 @@ open class FileAdapter( var isSelectingFolder = false var disabledNavigationFolderIds: Set = emptySet() var disabledNavigationParentFolderId: Int? = null + var exceptedNavigationFolderIds: Set = emptySet() var showShareFileButton = true var viewHolderType: DisplayType = DisplayType.LIST var uploadInProgress = false @@ -432,9 +433,11 @@ open class FileAdapter( } else -> { if (isSelectingFolder || offlineMode) { + val isMovedChildOfSource = file.parentId == disabledNavigationParentFolderId + && file.id !in exceptedNavigationFolderIds val isNavigableFolder = file.isFolder() && file.id !in disabledNavigationFolderIds - && file.parentId != disabledNavigationParentFolderId + && !isMovedChildOfSource enabledFile(isNavigableFolder || (offlineMode && file.isOffline)) } else { enabledFile() diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt index 39a73d5db0..9dac1bd12c 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderActivity.kt @@ -66,6 +66,7 @@ class SelectFolderActivity : BaseActivity() { val disabledDestinationFolderId = navigationArgs.disabledDestinationFolderId.getIntOrNull() val disabledNavigationFolderIdsArg = navigationArgs.disabledNavigationFolderIds?.toSet() ?: emptySet() val disabledNavigationParentFolderIdArg = navigationArgs.disabledNavigationParentFolderId.getIntOrNull() + val exceptedNavigationFolderIdsArg = navigationArgs.exceptedNavigationFolderIds?.toSet() ?: emptySet() // We're doing this in the mainthread because the FileListFragment rely on mainViewModel.selectFolderUserDrive. // Moving this call in a background thread we'll break everything @@ -80,6 +81,7 @@ class SelectFolderActivity : BaseActivity() { disableSelectedFolderId = disabledDestinationFolderId disabledNavigationFolderIds = disabledNavigationFolderIdsArg disabledNavigationParentFolderId = disabledNavigationParentFolderIdArg + exceptedNavigationFolderIds = exceptedNavigationFolderIdsArg } navController.setGraph( @@ -193,6 +195,7 @@ class SelectFolderActivity : BaseActivity() { var disableSelectedFolderId: Int? = null var disabledNavigationFolderIds: Set = emptySet() var disabledNavigationParentFolderId: Int? = null + var exceptedNavigationFolderIds: Set = emptySet() fun getFolderName(folderId: Int): String { val selectedFolderName = if (folderId == ROOT_ID) { diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt index f2c9ca6d88..b707d4a3aa 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt @@ -81,6 +81,7 @@ class SelectFolderFragment : FileListFragment() { isSelectingFolder = true disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds disabledNavigationParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId + exceptedNavigationFolderIds = selectFolderViewModel.exceptedNavigationFolderIds onFileClicked = { file -> if (file.isFolder() && !file.isDisabled()) { fileListViewModel.cancelDownloadFiles() diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index 5543f145aa..e2402e8a56 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -231,6 +231,7 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : mainViewModel = mainViewModel, filesToMove = getValidSelectedItems(), disabledNavigationParentFolderId = if (isSelectAllOn) currentFolder?.id else null, + exceptedNavigationFolderIds = if (isSelectAllOn) exceptedItemsIds.toIntArray() else null, ) } diff --git a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt index ced5d7344c..67823a4cbb 100644 --- a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt +++ b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt @@ -233,6 +233,7 @@ object Utils { mainViewModel: MainViewModel, filesToMove: List = emptyList(), disabledNavigationParentFolderId: Int? = null, + exceptedNavigationFolderIds: IntArray? = null, ) { mainViewModel.ignoreSyncOffline = true val disabledNavigationFolderIds = filesToMove.filter { it.isFolder() }.map { it.id }.toIntArray() @@ -245,6 +246,7 @@ object Utils { disabledDestinationFolderId = disabledDestinationFolderId ?: -1, disabledNavigationFolderIds = disabledNavigationFolderIds, disabledNavigationParentFolderId = disabledNavigationParentFolderId ?: -1, + exceptedNavigationFolderIds = exceptedNavigationFolderIds, customArgs = bundleOf( MultiSelectFragment.BULK_OPERATION_CUSTOM_TAG to BulkOperationType.MOVE, SINGLE_OPERATION_CUSTOM_TAG to SingleOperation.MOVE.name, diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index c66aab925d..5665723e4e 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -438,6 +438,11 @@ android:name="disabledNavigationParentFolderId" android:defaultValue="-1" app:argType="integer" /> + Date: Thu, 30 Jul 2026 11:43:50 +0200 Subject: [PATCH 05/16] fix: Display the API subtree conflict error when moving a folder into itself --- .../java/com/infomaniak/drive/data/api/ErrorCode.kt | 2 ++ .../java/com/infomaniak/drive/ui/MainViewModel.kt | 9 +++++---- .../FileInfoActionsBottomSheetDialog.kt | 7 +------ .../ui/fileList/multiSelect/MultiSelectFragment.kt | 12 +++++++----- .../ui/fileList/preview/PreviewSliderFragment.kt | 7 +------ 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/data/api/ErrorCode.kt b/app/src/main/java/com/infomaniak/drive/data/api/ErrorCode.kt index 610fd648c0..b7e040c4ed 100644 --- a/app/src/main/java/com/infomaniak/drive/data/api/ErrorCode.kt +++ b/app/src/main/java/com/infomaniak/drive/data/api/ErrorCode.kt @@ -42,6 +42,7 @@ object ErrorCode { const val SHARE_LINK_ALREADY_EXISTS = "file_share_link_already_exists" const val STILL_UPLOADING_ERROR = "still_uploading_error" const val YOU_MUST_ADD_AT_LEAST_ONE_FILE = "you_must_add_at_least_one_file" + const val CONFLICT_PART_OF_THE_SAME_SUBTREE = "conflict_part_of_the_same_subtree_error" val apiErrorCodes = listOf( ApiErrorCode(CATEGORY_ALREADY_EXISTS, R.string.errorCategoryAlreadyExists), @@ -63,5 +64,6 @@ object ErrorCode { ApiErrorCode(SHARE_LINK_ALREADY_EXISTS, R.string.errorShareLink), ApiErrorCode(STILL_UPLOADING_ERROR, R.string.errorStillUploading), ApiErrorCode(YOU_MUST_ADD_AT_LEAST_ONE_FILE, R.string.errorDownloadPermission), + ApiErrorCode(CONFLICT_PART_OF_THE_SAME_SUBTREE, R.string.errorConflictPartOfTheSameSubtree) ) } diff --git a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt index 2cf255b211..15d3dad3bf 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt @@ -235,7 +235,7 @@ class MainViewModel( fun createMultiSelectMediator(): MediatorLiveData = MediatorLiveData().apply { - value = MultiSelectMediatorState(numberOfSuccessfulActions = 0, totalOfActions = 0, errorCode = null) + value = MultiSelectMediatorState(numberOfSuccessfulActions = 0, totalOfActions = 0, errorResId = null) } fun updateMultiSelectMediator(mediator: MediatorLiveData): (FileResult) -> Unit = { fileRequest -> @@ -243,11 +243,12 @@ class MainViewModel( if (fileRequest.isSuccess) numberOfSuccessfulActions++ val totalOfActions = mediator.value!!.totalOfActions + 1 + val currentErrorResId = mediator.value!!.errorResId mediator.value = MultiSelectMediatorState( numberOfSuccessfulActions, totalOfActions, - fileRequest.errorCode, + errorResId = currentErrorResId ?: fileRequest.errorResId.takeIf { !fileRequest.isSuccess }, ) } @@ -343,7 +344,7 @@ class MainViewModel( onSuccess?.invoke(file.id) } - emit(FileResult(isSuccess = apiResponse.isSuccess(), errorCode = apiResponse.error?.code)) + emit(FileResult(isSuccess = apiResponse.isSuccess(), errorCode = apiResponse.error?.code, errorResId = apiResponse.translateError().takeIf { !apiResponse.isSuccess() })) } fun renameFile(file: File, newName: String) = liveData(Dispatchers.IO) { @@ -731,7 +732,7 @@ class MainViewModel( data class MultiSelectMediatorState( var numberOfSuccessfulActions: Int, var totalOfActions: Int, - var errorCode: String?, + var errorResId: Int? = null, ) companion object { diff --git a/app/src/main/java/com/infomaniak/drive/ui/bottomSheetDialogs/FileInfoActionsBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/drive/ui/bottomSheetDialogs/FileInfoActionsBottomSheetDialog.kt index 05c0d0eb85..b9bdc0561b 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/bottomSheetDialogs/FileInfoActionsBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/bottomSheetDialogs/FileInfoActionsBottomSheetDialog.kt @@ -387,12 +387,7 @@ class FileInfoActionsBottomSheetDialog : EdgeToEdgeBottomSheetDialog(), FileInfo (fileRequest.data as? CancellableAction)?.setDriveAndReturn(currentFile.driveId) ) } else { - val resource = if (fileRequest.errorCode == LIMIT_EXCEEDED_ERROR_CODE) { - R.string.errorFilesLimitExceeded - } else { - R.string.errorMove - } - + val resource = fileRequest.errorResId ?: R.string.errorMove transmitActionAndPopBack(getString(resource)) } } diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index e2402e8a56..eb4da6dcec 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -494,7 +494,7 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : ) } else { mediator.value = mediator.value?.let { - MultiSelectMediatorState(it.numberOfSuccessfulActions, it.totalOfActions + 1, it.errorCode) + MultiSelectMediatorState(it.numberOfSuccessfulActions, it.totalOfActions + 1) } } } @@ -556,17 +556,19 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : destinationFolder: File?, dialog: Dialog? = null, ) { - mediator.observe(viewLifecycleOwner) { (success, total, error) -> - if (total == fileCount) { + mediator.observe(viewLifecycleOwner) { state -> + val success = state.numberOfSuccessfulActions + val errorResId = state.errorResId + if (state.totalOfActions == fileCount) { dialog?.dismiss() - handleIndividualActionsResult(success, error, type, destinationFolder) + handleIndividualActionsResult(success, errorResId, type, destinationFolder) } } } private fun handleIndividualActionsResult( success: Int, - errorCode: String?, + errorResId: Int?, type: BulkOperationType, destinationFolder: File?, ) { diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewSliderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewSliderFragment.kt index 95fa3ff7cc..d028b0d7d2 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewSliderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewSliderFragment.kt @@ -330,12 +330,7 @@ class PreviewSliderFragment : BasePreviewSliderFragment(), FileInfoActionsView.O mainViewModel.refreshActivities.value = true showSnackbar(getString(R.string.allFileMove, currentFile.name, destinationFolder.name)) } else { - val messageRes = if (fileRequest.errorCode == LIMIT_EXCEEDED_ERROR_CODE) { - R.string.errorFilesLimitExceeded - } else { - R.string.errorMove - } - + val messageRes = fileRequest.errorResId ?: R.string.errorMove showSnackbar(messageRes) } } From 32846df7c48dfcebcff82d35e904a4c208b6aba3 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Thu, 30 Jul 2026 13:07:44 +0200 Subject: [PATCH 06/16] refactor: Split checkIfEnableFile into helpers --- .../drive/ui/fileList/FileAdapter.kt | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt index ef3bac4144..de6794d773 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt @@ -417,34 +417,36 @@ open class FileAdapter( fun contains(fileName: String) = fileList.any { it.name == fileName } - private fun FileItemViewHolder.checkIfEnableFile(file: File) = when { - uploadInProgress -> { - if (file.isPendingUploadFolder()) { - fileDate?.text = file.path - } else { - val enable = file.currentProgress > 0 && binding.context.isSyncActive() - val title = when { - enable -> R.string.uploadInProgressTitle - pendingWifiConnection -> R.string.uploadNetworkErrorWifiRequired - else -> R.string.uploadInProgressPending - } - fileDate?.setText(title) - } + private fun FileItemViewHolder.checkIfEnableFile(file: File) { + if (uploadInProgress) { + displayUploadStatus(file) + } else if (isSelectingFolder || offlineMode) { + enabledFile(file.isNavigableFolder() || (offlineMode && file.isOffline)) + } else { + enabledFile() } - else -> { - if (isSelectingFolder || offlineMode) { - val isMovedChildOfSource = file.parentId == disabledNavigationParentFolderId - && file.id !in exceptedNavigationFolderIds - val isNavigableFolder = file.isFolder() - && file.id !in disabledNavigationFolderIds - && !isMovedChildOfSource - enabledFile(isNavigableFolder || (offlineMode && file.isOffline)) - } else { - enabledFile() + } + + private fun FileItemViewHolder.displayUploadStatus(file: File) { + if (file.isPendingUploadFolder()) { + fileDate?.text = file.path + } else { + val enable = file.currentProgress > 0 && binding.context.isSyncActive() + val title = when { + enable -> R.string.uploadInProgressTitle + pendingWifiConnection -> R.string.uploadNetworkErrorWifiRequired + else -> R.string.uploadInProgressPending } + fileDate?.setText(title) } } + private fun File.isNavigableFolder(): Boolean { + val isMovedChildOfSource = parentId == disabledNavigationParentFolderId + && id !in exceptedNavigationFolderIds + return isFolder() && id !in disabledNavigationFolderIds && !isMovedChildOfSource + } + private fun FileItemViewHolder.enabledFile(enable: Boolean = true) { disabledView.isGone = enable cardView.isEnabled = enable From 40ebedc707ef262af6365e7afa9990b8315e4f53 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Thu, 30 Jul 2026 13:08:27 +0200 Subject: [PATCH 07/16] fix: Disable forbidden destinations in favorites and recent folders --- .../drive/ui/fileList/FavoritesFragment.kt | 3 +++ .../ui/fileList/SelectRootFolderFragment.kt | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt index 49cd067a25..d98723182b 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt @@ -20,6 +20,7 @@ package com.infomaniak.drive.ui.fileList import android.os.Bundle import android.view.View import androidx.core.view.isGone +import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.swiperefreshlayout.widget.SwipeRefreshLayout @@ -44,6 +45,7 @@ class FavoritesFragment : FileListFragment() { override val noItemsRootTitle = R.string.favoritesNoFile private val navigationArgs: FavoritesFragmentArgs by navArgs() + private val selectFolderViewModel: SelectFolderActivity.SelectFolderViewModel by activityViewModels() override val fileIdToPreview: Int get() = navigationArgs.previewFileId override fun initSwipeRefreshLayout(): SwipeRefreshLayout = binding.swipeRefreshLayout @@ -68,6 +70,7 @@ class FavoritesFragment : FileListFragment() { private fun setupAdapter() { fileAdapter.apply { isSelectingFolder = requireActivity() is SelectFolderActivity + disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds onEmptyList = { changeNoFilesLayoutVisibility(hideFileList = true, changeControlsVisibility = false) } onFileClicked = { file -> diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt index 1df72675ab..4f5c3984b5 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt @@ -23,14 +23,17 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.fragment.navArgs import com.infomaniak.core.fragmentnavigation.safelyNavigate import com.infomaniak.core.ui.view.extension.setMargins +import com.infomaniak.core.ui.view.utils.SnackbarUtils.showSnackbar import com.infomaniak.drive.R import com.infomaniak.drive.data.cache.DriveInfosController +import com.infomaniak.drive.data.cache.FileController import com.infomaniak.drive.data.models.File import com.infomaniak.drive.data.models.UiSettings import com.infomaniak.drive.databinding.CardviewFileListBinding @@ -42,8 +45,10 @@ import com.infomaniak.drive.ui.home.RootFilesFragment.FolderToOpen import com.infomaniak.drive.utils.TypeFolder import com.infomaniak.drive.utils.setFileItem import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class SelectRootFolderFragment : BaseRootFolderFragment() { @@ -52,6 +57,7 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { override val fileListViewModel: FileListViewModel by viewModels() private val selectRootFolderViewModel: SelectRootFolderViewModel by viewModels() + private val selectFolderViewModel: SelectFolderActivity.SelectFolderViewModel by activityViewModels() private val navigationArgs: SelectRootFolderFragmentArgs by navArgs() @@ -144,6 +150,10 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { private suspend fun CardviewFileListBinding.setupRecentFolderView(file: File) { root.isVisible = true + + val isForbiddenDestination = isInsideMovedFolder(file) + disabled.isVisible = isForbiddenDestination + root.setOnClickListener { safelyNavigate( SelectRootFolderFragmentDirections.selectRootFolderFragmentToSelectFolderFragment( @@ -153,9 +163,23 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { ) ) } + disabled.setOnClickListener { showSnackbar(R.string.errorConflictPartOfTheSameSubtree) } + itemViewFile.setFileItem(file = file, typeFolder = TypeFolder.recentFolder) } + private suspend fun isInsideMovedFolder(file: File): Boolean = withContext(Dispatchers.IO) { + val movedFolderIds = selectFolderViewModel.disabledNavigationFolderIds + if (movedFolderIds.isEmpty()) return@withContext false + + var current: File? = file + while (current != null) { + if (current.id in movedFolderIds) return@withContext true + current = FileController.getParentFile(current.id, navigationArgs.userDrive) + } + false + } + override fun fileListDirections( folderToOpen: FolderToOpen, ): NavDirections = SelectRootFolderFragmentDirections.selectRootFolderFragmentToSelectFolderFragment( From 02a3d014c2a3146ed368bf5fa60f13b3c17812fb Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Thu, 30 Jul 2026 15:38:28 +0200 Subject: [PATCH 08/16] fix: Handle the remaining move error and forbidden destination cases --- .../java/com/infomaniak/drive/ui/MainViewModel.kt | 8 +++++++- .../drive/ui/fileList/FavoritesFragment.kt | 2 ++ .../drive/ui/fileList/SelectRootFolderFragment.kt | 13 ++++++++++--- .../ui/fileList/multiSelect/MultiSelectFragment.kt | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt index 15d3dad3bf..ed5d2f77e1 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt @@ -344,7 +344,13 @@ class MainViewModel( onSuccess?.invoke(file.id) } - emit(FileResult(isSuccess = apiResponse.isSuccess(), errorCode = apiResponse.error?.code, errorResId = apiResponse.translateError().takeIf { !apiResponse.isSuccess() })) + emit( + FileResult( + isSuccess = apiResponse.isSuccess(), + errorCode = apiResponse.error?.code, + errorResId = apiResponse.translateError(defaultMessage = R.string.errorMove).takeIf { !apiResponse.isSuccess() }, + ) + ) } fun renameFile(file: File, newName: String) = liveData(Dispatchers.IO) { diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt index d98723182b..d9cc996640 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/FavoritesFragment.kt @@ -71,6 +71,8 @@ class FavoritesFragment : FileListFragment() { fileAdapter.apply { isSelectingFolder = requireActivity() is SelectFolderActivity disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds + disabledNavigationParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId + exceptedNavigationFolderIds = selectFolderViewModel.exceptedNavigationFolderIds onEmptyList = { changeNoFilesLayoutVisibility(hideFileList = true, changeControlsVisibility = false) } onFileClicked = { file -> diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt index 4f5c3984b5..9c110b486a 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt @@ -170,13 +170,20 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { private suspend fun isInsideMovedFolder(file: File): Boolean = withContext(Dispatchers.IO) { val movedFolderIds = selectFolderViewModel.disabledNavigationFolderIds - if (movedFolderIds.isEmpty()) return@withContext false + val movedParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId + if (movedFolderIds.isEmpty() && movedParentFolderId == null) return@withContext false + val visitedIds = mutableSetOf() var current: File? = file - while (current != null) { - if (current.id in movedFolderIds) return@withContext true + + while (current != null && visitedIds.add(current.id)) { + val isMovedChildOfSource = current.parentId == movedParentFolderId + && current.id !in selectFolderViewModel.exceptedNavigationFolderIds + if (current.id in movedFolderIds || isMovedChildOfSource) return@withContext true + current = FileController.getParentFile(current.id, navigationArgs.userDrive) } + false } diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index eb4da6dcec..3264fb72d8 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -494,7 +494,7 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : ) } else { mediator.value = mediator.value?.let { - MultiSelectMediatorState(it.numberOfSuccessfulActions, it.totalOfActions + 1) + it.copy(totalOfActions = it.totalOfActions + 1) } } } From 6099a6475638faf05c3d31eb577bdf1e8954bda9 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Thu, 30 Jul 2026 15:43:12 +0200 Subject: [PATCH 09/16] fix: Restore the subtree conflict error string --- app/src/main/res/values-da/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fi/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-nb/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 13 files changed, 13 insertions(+) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 89404ab384..8fecf04c96 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -263,6 +263,7 @@ Cache-fejl En kategori med samme navn findes allerede Der findes allerede et element med samme navn på dette sted + Flytning ikke mulig: destinationsmappen ligger inden i kildemappen Overførsel til et andet drev mislykkedes Sletningsfejl Utilstrækkelig enhedslagerplads diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 36e1cf192b..4e711eac70 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -263,6 +263,7 @@ Fehler bei der Zwischenspeicherung Eine Kategorie mit dem gleichen Namen existiert bereits An dieser Stelle existiert bereits ein Element mit demselben Namen + Verschieben nicht möglich: Der Zielordner befindet sich im Quellordner Übertragung auf ein anderes Laufwerk fehlgeschlagen Fehler beim Löschen Unzureichender Gerätespeicher diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 4aa4ed7c61..c690f81b1b 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -263,6 +263,7 @@ Σφάλμα προσωρινής αποθήκευσης Υπάρχει ήδη κατηγορία με το ίδιο όνομα Υπάρχει ήδη ένα στοιχείο με το ίδιο όνομα σε αυτήν την τοποθεσία + Η μετακίνηση δεν είναι δυνατή: ο φάκελος προορισμού βρίσκεται μέσα στον φάκελο προέλευσης Η μεταφορά σε άλλη μονάδα δίσκου απέτυχε Σφάλμα διαγραφής Ανεπαρκής αποθήκευση συσκευής diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 47ed96de4f..7f19f7c0f9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -263,6 +263,7 @@ Error al almacenar en caché Una categoría con el mismo nombre ya existe Ya existe un elemento con el mismo nombre en esta ubicación + No es posible mover: la carpeta de destino está dentro de la carpeta de origen La transferencia a otra unidad ha fallado Error al eliminar Almacenamiento insuficiente del dispositivo diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 23103c1606..4d53814a71 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -263,6 +263,7 @@ Välimuistivirhe Luokka samalla nimellä on jo olemassa Samanniminen elementti on jo olemassa tässä sijainnissa + Siirto ei ole käytettävissä: kohdekansio on lähdekansion sisällä Tiedonsiirto toiselle asemalle epäonnistui Poistovirhe Laitteen tallennustila ei riitä diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 38f5770a27..0035e3073f 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -263,6 +263,7 @@ Erreur lors de la mise en cache Une catégorie avec le même nom existe déjà Un élément du même nom existe déjà à cet emplacement + Déplacement impossible : le dossier ciblé se trouve dans le dossier source Le transfert vers un autre drive a échoué Erreur lors de la suppression Stockage de l’appareil insuffisant diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0b52f5328e..086efcb8d4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -263,6 +263,7 @@ Errore durante la memorizzazione nella cache Esiste già una categoria con lo stesso nome Un elemento con lo stesso nome esiste già in questa posizione + Spostamento non disponibile: la cartella di destinazione si trova all’interno della cartella di origine Trasferimento verso un’altra unità non riuscito Errore durante l’eliminazione Memoria del dispositivo insufficiente diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 55f50d7856..3157cda314 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -263,6 +263,7 @@ Feil ved mellomlagring En kategori med samme navn finnes allerede Et element med samme navn finnes allerede på denne plasseringen + Flytting ikke mulig: målmappen ligger inne i kildemappen Overføring til en annen stasjon mislyktes Feil ved sletting Manglende lagringsplass på enheten diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 5a42606585..b85bc06f74 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -263,6 +263,7 @@ Cachingfout Een categorie met dezelfde naam bestaat al Een element met dezelfde naam bestaat al op deze locatie + Verplaatsen niet beschikbaar: de doelmap bevindt zich in de bronmap Overdracht naar een ander station mislukt Verwijderfout Onvoldoende apparaatopslag diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a7e1a6c545..e25f46b932 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -273,6 +273,7 @@ Błąd buforowania Kategoria o tej samej nazwie już istnieje Element o tej samej nazwie już istnieje w tym miejscu + Przenoszenie niedostępne: folder docelowy znajduje się wewnątrz folderu źródłowego Przesyłanie na inny dysk nie powiodło się Błąd usuwania Niewystarczająca pamięć urządzenia diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index f286b1a41a..43b6c8c769 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -263,6 +263,7 @@ Erro de cache Já existe uma categoria com o mesmo nome Já existe um elemento com o mesmo nome neste local + Não é possível mover: a pasta de destino encontra-se dentro da pasta de origem A transferência para outra unidade falhou Erro ao eliminar Armazenamento do dispositivo insuficiente diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 6996e61910..7c2e37e047 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -263,6 +263,7 @@ Cachningsfel En kategori med samma namn finns redan Ett element med samma namn finns redan på den här platsen + Flyttning inte möjlig: målmappen finns inuti källmappen Överföring till en annan enhet misslyckades Borttagningsfel Otillräcklig enhetslagring diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e82a85e813..c26dac9dbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -263,6 +263,7 @@ Caching error A category with the same name already exists An element with the same name already exists in this location + Move not available: the target folder is inside the source folder Transfer to another drive failed Deletion error Insufficient device storage From d4c70443ad4896fe0f12d3254fbf9f8745abee23 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Thu, 30 Jul 2026 15:59:39 +0200 Subject: [PATCH 10/16] fix: Repair the multi-select error message broken by the merge --- .../drive/ui/fileList/multiSelect/MultiSelectFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index 3264fb72d8..95ce5e704d 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -573,7 +573,7 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : destinationFolder: File?, ) { val title = when { - errorCode == LIMIT_EXCEEDED_ERROR_CODE -> getString(R.string.errorFilesLimitExceeded) + errorResId != null -> getString(errorResId) success == 0 -> getString(R.string.anErrorHasOccurred) type == BulkOperationType.COPY_TO_DRIVE -> getString(R.string.copyToDriveStarted, pendingCopyToDriveData?.fileName) else -> resources.getQuantityString(type.successMessage, success, success, destinationFolder?.name + "/") From 83ba98b0cbedd5f8a16bd1f926ecc9a413069dec Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Fri, 31 Jul 2026 13:49:03 +0200 Subject: [PATCH 11/16] fix: Disable forbidden destinations in my shares --- .../java/com/infomaniak/drive/ui/menu/MySharesFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/infomaniak/drive/ui/menu/MySharesFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/menu/MySharesFragment.kt index 531d3cb1d6..0c55306e4c 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/menu/MySharesFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/menu/MySharesFragment.kt @@ -19,6 +19,7 @@ package com.infomaniak.drive.ui.menu import android.os.Bundle import android.view.View +import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.swiperefreshlayout.widget.SwipeRefreshLayout @@ -42,6 +43,7 @@ class MySharesFragment : FileSubTypeListFragment() { override val noItemsRootTitle = R.string.mySharesNoFile private val navigationArgs: MySharesFragmentArgs by navArgs() + private val selectFolderViewModel: SelectFolderActivity.SelectFolderViewModel by activityViewModels() override val fileIdToPreview: Int get() = navigationArgs.previewFileId override fun initSwipeRefreshLayout(): SwipeRefreshLayout = binding.swipeRefreshLayout @@ -76,6 +78,9 @@ class MySharesFragment : FileSubTypeListFragment() { } } fileAdapter.isSelectingFolder = requireActivity() is SelectFolderActivity + fileAdapter.disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds + fileAdapter.disabledNavigationParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId + fileAdapter.exceptedNavigationFolderIds = selectFolderViewModel.exceptedNavigationFolderIds } override fun onMenuButtonClicked( From 0d45a153c15fd8f2c22edd02609172260ced7518 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Fri, 31 Jul 2026 13:49:10 +0200 Subject: [PATCH 12/16] fix: Restore the files limit error message when moving --- app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt index ed5d2f77e1..d5d3e6be0e 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt @@ -48,6 +48,7 @@ import com.infomaniak.drive.MatomoDrive.MatomoName import com.infomaniak.drive.MatomoDrive.trackNewElementEvent import com.infomaniak.drive.R import com.infomaniak.drive.data.api.ApiRepository +import com.infomaniak.drive.data.api.ErrorCode import com.infomaniak.drive.data.cache.DriveInfosController import com.infomaniak.drive.data.cache.FileController import com.infomaniak.drive.data.cache.FolderFilesProvider @@ -348,7 +349,11 @@ class MainViewModel( FileResult( isSuccess = apiResponse.isSuccess(), errorCode = apiResponse.error?.code, - errorResId = apiResponse.translateError(defaultMessage = R.string.errorMove).takeIf { !apiResponse.isSuccess() }, + errorResId = when { + apiResponse.isSuccess() -> null + apiResponse.error?.code == ErrorCode.LIMIT_EXCEEDED_ERROR -> R.string.errorFilesLimitExceeded + else -> apiResponse.translateError(defaultMessage = R.string.errorMove) + }, ) ) } From b0718b4058f80e819671c040570cfcc7949c3bb2 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Mon, 3 Aug 2026 12:42:20 +0200 Subject: [PATCH 13/16] refactor: Reuse a single Realm instance when checking recent folders --- .../drive/ui/fileList/SelectFolderFragment.kt | 12 +---------- .../ui/fileList/SelectRootFolderFragment.kt | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt index b707d4a3aa..711cb99be1 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectFolderFragment.kt @@ -21,7 +21,6 @@ import android.os.Bundle import android.view.View import androidx.activity.addCallback import androidx.fragment.app.activityViewModels -import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.infomaniak.core.legacy.utils.safeNavigate @@ -30,7 +29,6 @@ import com.infomaniak.drive.MatomoDrive.MatomoName import com.infomaniak.drive.MatomoDrive.trackNewElementEvent import com.infomaniak.drive.R import com.infomaniak.drive.data.cache.FileController -import com.infomaniak.drive.data.models.Rights import com.infomaniak.drive.ui.fileList.SelectFolderActivity.SelectFolderViewModel import com.infomaniak.drive.utils.Utils import com.infomaniak.drive.utils.Utils.ROOT_ID @@ -96,15 +94,7 @@ class SelectFolderFragment : FileListFragment() { } } - lifecycleScope.launchWhenResumed { - with(requireActivity() as SelectFolderActivity) { - showSaveButton() - val currentFolderRights = FileController.getFileById(folderId, userDrive)?.rights ?: Rights() - val enable = folderId != selectFolderViewModel.disableSelectedFolderId - && (currentFolderRights.canMoveInto || currentFolderRights.canCreateFile) - enableSaveButton(enable) - } - } + setupSaveButton() } private fun onBackPressed() { diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt index 9c110b486a..0cdaf74740 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt @@ -173,18 +173,20 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { val movedParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId if (movedFolderIds.isEmpty() && movedParentFolderId == null) return@withContext false - val visitedIds = mutableSetOf() - var current: File? = file + FileController.getRealmInstance(navigationArgs.userDrive).use { realm -> + val visitedIds = mutableSetOf() + var current: File? = file - while (current != null && visitedIds.add(current.id)) { - val isMovedChildOfSource = current.parentId == movedParentFolderId - && current.id !in selectFolderViewModel.exceptedNavigationFolderIds - if (current.id in movedFolderIds || isMovedChildOfSource) return@withContext true + while (current != null && visitedIds.add(current.id)) { + val isMovedChildOfSource = current.parentId == movedParentFolderId + && current.id !in selectFolderViewModel.exceptedNavigationFolderIds + if (current.id in movedFolderIds || isMovedChildOfSource) return@use true - current = FileController.getParentFile(current.id, navigationArgs.userDrive) - } + current = FileController.getParentFileProxy(current.id, realm = realm) + } - false + false + } } override fun fileListDirections( From 8f4ac01718433d74071772a6b866270f9c3e993e Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Wed, 5 Aug 2026 09:35:17 +0200 Subject: [PATCH 14/16] fix: Restore the files limit error message when copying --- .../java/com/infomaniak/drive/ui/MainViewModel.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt index d5d3e6be0e..fbf1065a67 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt @@ -491,7 +491,18 @@ class MainViewModel( ) = liveData(Dispatchers.IO) { ApiRepository.duplicateFile(file, destinationId ?: Utils.ROOT_ID).let { apiResponse -> if (apiResponse.isSuccess()) onSuccess?.invoke(apiResponse) - emit(FileResult(isSuccess = apiResponse.isSuccess(), data = apiResponse.data, errorCode = apiResponse.error?.code)) + emit( + FileResult( + isSuccess = apiResponse.isSuccess(), + data = apiResponse.data, + errorCode = apiResponse.error?.code, + errorResId = when { + apiResponse.isSuccess() -> null + apiResponse.error?.code == ErrorCode.LIMIT_EXCEEDED_ERROR -> R.string.errorFilesLimitExceeded + else -> apiResponse.translateError(defaultMessage = R.string.errorDuplicate) + }, + ) + ) } } From dc6ecbc6dab266edf8204753ade4a21f7737eeb0 Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Fri, 7 Aug 2026 13:34:12 +0200 Subject: [PATCH 15/16] fix: Resolve the recent folder from the IO Realm instead of crossing threads --- .../drive/ui/fileList/SelectRootFolderFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt index 0cdaf74740..3e67a516de 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/SelectRootFolderFragment.kt @@ -151,7 +151,7 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { private suspend fun CardviewFileListBinding.setupRecentFolderView(file: File) { root.isVisible = true - val isForbiddenDestination = isInsideMovedFolder(file) + val isForbiddenDestination = isInsideMovedFolder(file.id) disabled.isVisible = isForbiddenDestination root.setOnClickListener { @@ -168,14 +168,14 @@ class SelectRootFolderFragment : BaseRootFolderFragment() { itemViewFile.setFileItem(file = file, typeFolder = TypeFolder.recentFolder) } - private suspend fun isInsideMovedFolder(file: File): Boolean = withContext(Dispatchers.IO) { + private suspend fun isInsideMovedFolder(fileId: Int): Boolean = withContext(Dispatchers.IO) { val movedFolderIds = selectFolderViewModel.disabledNavigationFolderIds val movedParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId if (movedFolderIds.isEmpty() && movedParentFolderId == null) return@withContext false FileController.getRealmInstance(navigationArgs.userDrive).use { realm -> val visitedIds = mutableSetOf() - var current: File? = file + var current = FileController.getFileProxyById(fileId, customRealm = realm) while (current != null && visitedIds.add(current.id)) { val isMovedChildOfSource = current.parentId == movedParentFolderId From 24dd60274083079844f6a03ebd89a3a9f4d3f54a Mon Sep 17 00:00:00 2001 From: Aymeric MARIAUX Date: Fri, 7 Aug 2026 14:08:38 +0200 Subject: [PATCH 16/16] fix: Restore the success count when only some multi-select actions fail --- .../drive/ui/fileList/multiSelect/MultiSelectFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt index 95ce5e704d..b8b86ad578 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/multiSelect/MultiSelectFragment.kt @@ -573,8 +573,8 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) : destinationFolder: File?, ) { val title = when { - errorResId != null -> getString(errorResId) - success == 0 -> getString(R.string.anErrorHasOccurred) + errorResId == R.string.errorFilesLimitExceeded -> getString(errorResId) + success == 0 -> getString(errorResId ?: R.string.anErrorHasOccurred) type == BulkOperationType.COPY_TO_DRIVE -> getString(R.string.copyToDriveStarted, pendingCopyToDriveData?.fileName) else -> resources.getQuantityString(type.successMessage, success, success, destinationFolder?.name + "/") }