diff --git a/app/src/androidTest/java/com/infomaniak/drive/ApiRepositoryTest.kt b/app/src/androidTest/java/com/infomaniak/drive/ApiRepositoryTest.kt index 1a185319ed..aa2c4118c0 100644 --- a/app/src/androidTest/java/com/infomaniak/drive/ApiRepositoryTest.kt +++ b/app/src/androidTest/java/com/infomaniak/drive/ApiRepositoryTest.kt @@ -370,21 +370,18 @@ class ApiRepositoryTest : KDriveTest() { } @Test - @DisplayName("Copy the test file to root folder") + @DisplayName("Duplicate the test file to root folder") fun duplicateFile() { - val copyName = "testCopy-$randomSuffix" - val copyFile = duplicateFile(testFile, copyName).let { + val copyFile = duplicateFile(testFile, testFile.parentId).let { assertApiResponseData(it) - assertEquals(copyName, it.data?.name, "The copy name should be equal to $copyName") assertNotEquals(testFile.id, it.data?.id, "The id should be different from the original file") assertEquals(testFile.driveColor, it.data?.driveColor) it.data!! } - // Duplicate one more time with same name and location - with(duplicateFile(testFile, copyName)) { + // Duplicate one more time with location + with(duplicateFile(testFile, testFile.parentId)) { assertApiResponseData(this) - assertEquals("$copyName (1)", data?.name, "The copy name should be equal to $copyName (1)") deleteTestFile(data!!) } @@ -568,6 +565,22 @@ class ApiRepositoryTest : KDriveTest() { } } + @Test + @DisplayName("Duplicate the test file to another folder") + fun duplicateFileToAnotherFolder() { + val file = createFileForTest() + + val copyFile = duplicateFile(file, file.parentId).let { + assertApiResponseData(it) + assertNotEquals(file.id, it.data?.id, "The id should be different from the original file") + assertEquals(file.driveColor, it.data?.driveColor) + it.data!! + } + + // Delete the copy + deleteTestFile(copyFile) + } + @Test @DisplayName("Create a folder then convert it to dropbox") fun createDropboxFromFolder() { diff --git a/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt b/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt index 89cbe2d932..7c461437e0 100644 --- a/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt +++ b/app/src/main/java/com/infomaniak/drive/data/api/ApiRepository.kt @@ -178,14 +178,8 @@ object ApiRepository : ApiRepositoryCore() { return callApi(ApiRoutes.updateFolderColor(file), POST, mapOf("color" to color)) } - fun copyFile(file: File, copyName: String?, destinationId: Int): ApiResponse { - val body = if (copyName == null) mapOf() else mapOf("name" to copyName) - return callApi(ApiRoutes.copyFile(file, destinationId), POST, body) - } - - fun duplicateFile(file: File, duplicateName: String?): ApiResponse { - val body = if (duplicateName == null) mapOf() else mapOf("name" to duplicateName) - return callApi(ApiRoutes.duplicateFile(file), POST, body) + fun duplicateFile(file: File, destinationId: Int): ApiResponse { + return callApi(ApiRoutes.duplicateFile(file, destinationId), POST) } fun moveFile(file: File, newParent: File): ApiResponse { diff --git a/app/src/main/java/com/infomaniak/drive/data/api/ApiRoutes.kt b/app/src/main/java/com/infomaniak/drive/data/api/ApiRoutes.kt index 16ab4c7938..da25963882 100644 --- a/app/src/main/java/com/infomaniak/drive/data/api/ApiRoutes.kt +++ b/app/src/main/java/com/infomaniak/drive/data/api/ApiRoutes.kt @@ -208,9 +208,7 @@ object ApiRoutes { fun moveFile(file: File, newParentId: Int) = "${fileURL(file)}/move/$newParentId" - fun duplicateFile(file: File) = "${fileURL(file)}/duplicate?$fileWithQuery" - - fun copyFile(file: File, destinationId: Int) = "${fileURL(file)}/copy/$destinationId?$fileWithQuery" + fun duplicateFile(file: File, destinationId: Int) = "${fileURL(file)}/copy/$destinationId?$fileWithQuery" fun renameFile(file: File) = "${fileURL(file)}/rename" diff --git a/app/src/main/java/com/infomaniak/drive/data/documentprovider/CloudStorageProvider.kt b/app/src/main/java/com/infomaniak/drive/data/documentprovider/CloudStorageProvider.kt index ebde72bfa8..18d5779836 100644 --- a/app/src/main/java/com/infomaniak/drive/data/documentprovider/CloudStorageProvider.kt +++ b/app/src/main/java/com/infomaniak/drive/data/documentprovider/CloudStorageProvider.kt @@ -411,10 +411,9 @@ class CloudStorageProvider : DocumentsProvider() { val fileId = getFileIdFromDocumentId(sourceDocumentId) val file = FileController.getFileProxyById(fileId, customRealm = realm) ?: throw IllegalStateException("File not found") - val copyName = file.name val targetParentFileId = getFileIdFromDocumentId(targetParentDocumentId) - val apiResponse = ApiRepository.copyFile(file, copyName, targetParentFileId) + val apiResponse = ApiRepository.duplicateFile(file, targetParentFileId) if (apiResponse.isSuccess()) { 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 4ed808152e..ded17b95c9 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt @@ -334,24 +334,12 @@ class MainViewModel( } } - fun copyFile( - file: File, - destinationId: Int? = null, - copyName: String?, - onSuccess: ((apiResponse: ApiResponse) -> Unit)? = null, - ) = liveData(Dispatchers.IO) { - ApiRepository.copyFile(file, copyName, destinationId ?: Utils.ROOT_ID).let { apiResponse -> - if (apiResponse.isSuccess()) onSuccess?.invoke(apiResponse) - emit(apiResponse) - } - } - fun duplicateFile( file: File, - copyName: String?, + destinationId: Int? = null, onSuccess: ((apiResponse: ApiResponse) -> Unit)? = null, ) = liveData(Dispatchers.IO) { - ApiRepository.duplicateFile(file, copyName).let { apiResponse -> + ApiRepository.duplicateFile(file, destinationId ?: Utils.ROOT_ID).let { apiResponse -> if (apiResponse.isSuccess()) onSuccess?.invoke(apiResponse) emit(apiResponse) } 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 a0741913fb..ef0a97e15b 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 @@ -259,21 +259,16 @@ class FileInfoActionsBottomSheetDialog : BottomSheetDialogFragment(), FileInfoAc mainViewModel.updateOfflineFile.value = currentFile.id } - override fun onDuplicateFile(result: String, onApiResponse: () -> Unit) { - if (isResumed) { - mainViewModel.duplicateFile(currentFile, result).observe(viewLifecycleOwner) { apiResponse -> - if (apiResponse.isSuccess()) { - apiResponse.data?.let { - mainViewModel.refreshActivities.value = true - transmitActionAndPopBack(getString(R.string.allFileDuplicate, currentFile.name)) - } - } else { - transmitActionAndPopBack(getString(R.string.errorDuplicate)) - } - onApiResponse() + override fun onDuplicateFile(destinationFolder: File) { + mainViewModel.duplicateFile(currentFile, destinationFolder.id).observe(viewLifecycleOwner) { apiResponse -> + val snackbarMessage = if (apiResponse.isSuccess()) { + mainViewModel.refreshActivities.value = true + getString(R.string.allFileDuplicate, currentFile.name) + } else { + getString(R.string.errorDuplicate) } - } else { - onApiResponse() + + transmitActionAndPopBack(snackbarMessage) } } 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 c59ddba78e..7d7fc068bc 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 @@ -48,6 +48,7 @@ import com.infomaniak.drive.ui.fileList.multiSelect.MultiSelectManager.MultiSele import com.infomaniak.drive.utils.* import com.infomaniak.drive.utils.BulkOperationsUtils.launchBulkOperationWorker import com.infomaniak.drive.utils.NotificationUtils.buildGeneralNotification +import com.infomaniak.drive.utils.Utils.duplicateFilesClicked import com.infomaniak.drive.utils.Utils.moveFileClicked import com.infomaniak.lib.core.utils.ApiErrorCode.Companion.translateError import com.infomaniak.lib.core.utils.capitalizeFirstChar @@ -185,17 +186,7 @@ abstract class MultiSelectFragment(private val matomoCategory: String) : Fragmen } fun duplicateFiles() { - Intent(requireContext(), SelectFolderActivity::class.java).apply { - putExtras( - SelectFolderActivityArgs( - userId = AccountUtils.currentUserId, - driveId = AccountUtils.currentDriveId, - folderId = mainViewModel.currentFolder.value?.id ?: -1, - customArgs = bundleOf(BULK_OPERATION_CUSTOM_TAG to BulkOperationType.COPY) - ).toBundle() - ) - selectFolderResultLauncher.launch(this) - } + requireContext().duplicateFilesClicked(selectFolderResultLauncher, mainViewModel) } fun restoreIn() { @@ -350,10 +341,9 @@ abstract class MultiSelectFragment(private val matomoCategory: String) : Fragmen } BulkOperationType.COPY -> { mediator.addSource( - copyFile( + duplicateFile( file = file, destinationId = destinationFolder!!.id, - copyName = file.name, onSuccess = { it.data?.let { file -> onIndividualActionSuccess(type, file) } }, ), updateMultiSelectMediator(mediator), diff --git a/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewPDFActivity.kt b/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewPDFActivity.kt index 36dbbfc4ac..ca57d81055 100644 --- a/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewPDFActivity.kt +++ b/app/src/main/java/com/infomaniak/drive/ui/fileList/preview/PreviewPDFActivity.kt @@ -148,7 +148,7 @@ class PreviewPDFActivity : AppCompatActivity(), OnItemClickListener { override fun manageCategoriesClicked(fileId: Int) = Unit override fun onCacheAddedToOffline() = Unit override fun onDeleteFile(onApiResponse: () -> Unit) = Unit - override fun onDuplicateFile(result: String, onApiResponse: () -> Unit) = Unit + override fun onDuplicateFile(destinationFolder: File) = Unit override fun onLeaveShare(onApiResponse: () -> Unit) = Unit override fun onMoveFile(destinationFolder: File) = Unit override fun onRenameFile(newName: String, onApiResponse: () -> Unit) = Unit 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 8923ecc792..af774978f2 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 @@ -398,12 +398,15 @@ class PreviewSliderFragment : Fragment(), FileInfoActionsView.OnItemClickListene ) } - override fun onDuplicateFile(result: String, onApiResponse: () -> Unit) { - mainViewModel.duplicateFile(currentFile, result).observe(viewLifecycleOwner) { apiResponse -> + override fun onDuplicateFile(destinationFolder: File) { + mainViewModel.duplicateFile(currentFile, destinationFolder.id).observe(viewLifecycleOwner) { apiResponse -> if (apiResponse.isSuccess()) { apiResponse.data?.let { file -> - mainViewModel.currentPreviewFileList[file.id] = file - previewSliderAdapter.addFile(file) + if (currentFile.parentId == destinationFolder.id) { + mainViewModel.currentPreviewFileList[file.id] = file + previewSliderAdapter.addFile(file) + } + showSnackbar(getString(R.string.allFileDuplicate, currentFile.name)) toggleBottomSheet(shouldShow = true) } @@ -411,7 +414,6 @@ class PreviewSliderFragment : Fragment(), FileInfoActionsView.OnItemClickListene showSnackbar(R.string.errorDuplicate) toggleBottomSheet(shouldShow = true) } - onApiResponse() } } diff --git a/app/src/main/java/com/infomaniak/drive/utils/SingleOperation.kt b/app/src/main/java/com/infomaniak/drive/utils/SingleOperation.kt new file mode 100644 index 0000000000..3a1d3c00a2 --- /dev/null +++ b/app/src/main/java/com/infomaniak/drive/utils/SingleOperation.kt @@ -0,0 +1,22 @@ +/* + * Infomaniak kDrive - Android + * Copyright (C) 2024 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 + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.drive.utils + +enum class SingleOperation { + COPY, MOVE +} 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 fd798abde4..51cab55607 100644 --- a/app/src/main/java/com/infomaniak/drive/utils/Utils.kt +++ b/app/src/main/java/com/infomaniak/drive/utils/Utils.kt @@ -60,6 +60,7 @@ import com.infomaniak.drive.ui.fileList.multiSelect.MultiSelectFragment import com.infomaniak.drive.ui.fileList.preview.PreviewPDFActivity import com.infomaniak.drive.ui.fileList.preview.PreviewSliderFragmentArgs import com.infomaniak.drive.utils.SyncUtils.uploadFolder +import com.infomaniak.drive.views.FileInfoActionsView.Companion.SINGLE_OPERATION_CUSTOM_TAG import com.infomaniak.lib.core.utils.DownloadManagerUtils import com.infomaniak.lib.core.utils.showKeyboard import com.infomaniak.lib.core.utils.showToast @@ -222,7 +223,7 @@ object Utils { fun Context.moveFileClicked( disabledFolderId: Int?, selectFolderResultLauncher: ActivityResultLauncher, - mainViewModel: MainViewModel + mainViewModel: MainViewModel, ) { mainViewModel.ignoreSyncOffline = true Intent(this, SelectFolderActivity::class.java).apply { @@ -232,11 +233,32 @@ object Utils { driveId = AccountUtils.currentDriveId, folderId = disabledFolderId ?: -1, disabledFolderId = disabledFolderId ?: -1, - customArgs = bundleOf(MultiSelectFragment.BULK_OPERATION_CUSTOM_TAG to BulkOperationType.MOVE) - ).toBundle() + customArgs = bundleOf( + MultiSelectFragment.BULK_OPERATION_CUSTOM_TAG to BulkOperationType.MOVE, + SINGLE_OPERATION_CUSTOM_TAG to SingleOperation.MOVE.name, + ), + ).toBundle(), ) - selectFolderResultLauncher.launch(this) - } + }.also(selectFolderResultLauncher::launch) + } + + fun Context.duplicateFilesClicked( + selectFolderResultLauncher: ActivityResultLauncher, + mainViewModel: MainViewModel, + ) { + Intent(this, SelectFolderActivity::class.java).apply { + putExtras( + SelectFolderActivityArgs( + userId = AccountUtils.currentUserId, + driveId = AccountUtils.currentDriveId, + folderId = mainViewModel.currentFolder.value?.id ?: -1, + customArgs = bundleOf( + MultiSelectFragment.BULK_OPERATION_CUSTOM_TAG to BulkOperationType.COPY, + SINGLE_OPERATION_CUSTOM_TAG to SingleOperation.COPY.name, + ), + ).toBundle(), + ) + }.also(selectFolderResultLauncher::launch) } fun Context.openWith(uri: Uri, type: String?, flags: Int) { 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 7eb7fd8039..6f4aa22ea7 100644 --- a/app/src/main/java/com/infomaniak/drive/views/FileInfoActionsView.kt +++ b/app/src/main/java/com/infomaniak/drive/views/FileInfoActionsView.kt @@ -53,6 +53,7 @@ import com.infomaniak.drive.databinding.ViewFileInfoActionsBinding import com.infomaniak.drive.ui.MainViewModel import com.infomaniak.drive.ui.fileList.SelectFolderActivityArgs import com.infomaniak.drive.utils.* +import com.infomaniak.drive.utils.Utils.duplicateFilesClicked import com.infomaniak.drive.utils.Utils.moveFileClicked import com.infomaniak.lib.core.utils.ApiErrorCode.Companion.translateError import com.infomaniak.lib.core.utils.DownloadManagerUtils @@ -239,7 +240,7 @@ class FileInfoActionsView @JvmOverloads constructor( moveFile.setOnClickListener { onItemClickListener.moveFileClicked(currentFile.parentId, selectFolderResultLauncher, mainViewModel) } - duplicateFile.setOnClickListener { onItemClickListener.duplicateFileClicked() } + duplicateFile.setOnClickListener { onItemClickListener.duplicateFileClicked(selectFolderResultLauncher, mainViewModel) } renameFile.setOnClickListener { onItemClickListener.renameFileClicked() } deleteFile.setOnClickListener { onItemClickListener.deleteFileClicked() } goToFolder.setOnClickListener { onItemClickListener.goToFolder() } @@ -486,8 +487,8 @@ class FileInfoActionsView @JvmOverloads constructor( fun manageCategoriesClicked(fileId: Int) fun onCacheAddedToOffline() fun onDeleteFile(onApiResponse: () -> Unit) - fun onDuplicateFile(result: String, onApiResponse: () -> Unit) fun onLeaveShare(onApiResponse: () -> Unit) + fun onDuplicateFile(destinationFolder: File) fun onMoveFile(destinationFolder: File) fun onRenameFile(newName: String, onApiResponse: () -> Unit) fun removeOfflineFile(offlineLocalPath: IOFile, cacheFile: IOFile) @@ -508,7 +509,12 @@ class FileInfoActionsView @JvmOverloads constructor( fun onSelectFolderResult(data: Intent?) { data?.extras?.let { bundle -> SelectFolderActivityArgs.fromBundle(bundle).apply { - onMoveFile(File(id = folderId, name = folderName, driveId = AccountUtils.currentDriveId)) + val file = File(id = folderId, name = folderName, driveId = AccountUtils.currentDriveId) + when (customArgs?.getString(SINGLE_OPERATION_CUSTOM_TAG)) { + SingleOperation.COPY.name -> onDuplicateFile(file) + SingleOperation.MOVE.name -> onMoveFile(file) + else -> Unit + } } } } @@ -546,20 +552,10 @@ class FileInfoActionsView @JvmOverloads constructor( } @CallSuper - fun duplicateFileClicked() = currentFile?.apply { - Utils.createPromptNameDialog( - context = currentContext, - title = R.string.buttonDuplicate, - fieldName = R.string.fileInfoInputDuplicateFile, - positiveButton = R.string.buttonCopy, - fieldValue = name, - selectedRange = getFileName().length - ) { dialog, name -> - onDuplicateFile(name) { - trackFileActionEvent("copy") - dialog.dismiss() - } - } + fun duplicateFileClicked(selectFolderResultLauncher: ActivityResultLauncher, mainViewModel: MainViewModel) { + trackFileActionEvent("copy") + mainViewModel.ignoreSyncOffline = true + currentContext.duplicateFilesClicked(selectFolderResultLauncher, mainViewModel) } @CallSuper @@ -604,4 +600,8 @@ class FileInfoActionsView @JvmOverloads constructor( } } } + + companion object { + const val SINGLE_OPERATION_CUSTOM_TAG = "single_operation" + } }