Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/main/java/com/infomaniak/drive/data/api/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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)
)
}
20 changes: 16 additions & 4 deletions app/src/main/java/com/infomaniak/drive/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -235,19 +236,20 @@ class MainViewModel(

fun createMultiSelectMediator(): MediatorLiveData<MultiSelectMediatorState> =
MediatorLiveData<MultiSelectMediatorState>().apply {
value = MultiSelectMediatorState(numberOfSuccessfulActions = 0, totalOfActions = 0, errorCode = null)
value = MultiSelectMediatorState(numberOfSuccessfulActions = 0, totalOfActions = 0, errorResId = null)
}

fun updateMultiSelectMediator(mediator: MediatorLiveData<MultiSelectMediatorState>): (FileResult) -> Unit = { fileRequest ->
var numberOfSuccessfulActions = mediator.value!!.numberOfSuccessfulActions
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 },
)
}

Expand Down Expand Up @@ -343,7 +345,17 @@ 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 = when {
apiResponse.isSuccess() -> null
apiResponse.error?.code == ErrorCode.LIMIT_EXCEEDED_ERROR -> R.string.errorFilesLimitExceeded
else -> apiResponse.translateError(defaultMessage = R.string.errorMove)
},
)
)
}

fun renameFile(file: File, newName: String) = liveData(Dispatchers.IO) {
Expand Down Expand Up @@ -731,7 +743,7 @@ class MainViewModel(
data class MultiSelectMediatorState(
var numberOfSuccessfulActions: Int,
var totalOfActions: Int,
var errorCode: String?,
var errorResId: Int? = null,
)

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -68,6 +70,9 @@ class FavoritesFragment : FileListFragment() {
private fun setupAdapter() {
fileAdapter.apply {
isSelectingFolder = requireActivity() is SelectFolderActivity
disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds
disabledNavigationParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId
exceptedNavigationFolderIds = selectFolderViewModel.exceptedNavigationFolderIds
onEmptyList = { changeNoFilesLayoutVisibility(hideFileList = true, changeControlsVisibility = false) }

onFileClicked = { file ->
Expand Down
46 changes: 28 additions & 18 deletions app/src/main/java/com/infomaniak/drive/ui/fileList/FileAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ open class FileAdapter(
var onStopUploadButtonClicked: ((fileName: String) -> Unit)? = null

var isSelectingFolder = false
var disabledNavigationFolderIds: Set<Int> = emptySet()
var disabledNavigationParentFolderId: Int? = null
var exceptedNavigationFolderIds: Set<Int> = emptySet()
var showShareFileButton = true
var viewHolderType: DisplayType = DisplayType.LIST
var uploadInProgress = false
Expand Down Expand Up @@ -414,29 +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) {
enabledFile(file.isFolder() || (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
Comment thread
aymericmariaux marked this conversation as resolved.
}

private fun FileItemViewHolder.enabledFile(enable: Boolean = true) {
disabledView.isGone = enable
cardView.isEnabled = enable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ 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()
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
Expand All @@ -75,7 +78,10 @@ class SelectFolderActivity : BaseActivity() {
selectFolderViewModel.apply {
userDrive = currentUserDrive
currentDrive = DriveInfosController.getDrive(userId, driveId)
disableSelectedFolderId = disabledFolderId
disableSelectedFolderId = disabledDestinationFolderId
disabledNavigationFolderIds = disabledNavigationFolderIdsArg
disabledNavigationParentFolderId = disabledNavigationParentFolderIdArg
exceptedNavigationFolderIds = exceptedNavigationFolderIdsArg
}

navController.setGraph(
Expand Down Expand Up @@ -187,6 +193,9 @@ class SelectFolderActivity : BaseActivity() {
var userDrive: UserDrive? = null
var currentDrive: Drive? = null
var disableSelectedFolderId: Int? = null
var disabledNavigationFolderIds: Set<Int> = emptySet()
var disabledNavigationParentFolderId: Int? = null
var exceptedNavigationFolderIds: Set<Int> = emptySet()

fun getFolderName(folderId: Int): String {
val selectedFolderName = if (folderId == ROOT_ID) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -79,6 +79,9 @@ class SelectFolderFragment : FileListFragment() {

fileAdapter.apply {
isSelectingFolder = true
disabledNavigationFolderIds = selectFolderViewModel.disabledNavigationFolderIds
disabledNavigationParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId
exceptedNavigationFolderIds = selectFolderViewModel.exceptedNavigationFolderIds
Comment thread
aymericmariaux marked this conversation as resolved.
onFileClicked = { file ->
if (file.isFolder() && !file.isDisabled()) {
fileListViewModel.cancelDownloadFiles()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {

Expand All @@ -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()

Expand Down Expand Up @@ -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(
Expand All @@ -153,9 +163,30 @@ 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
val movedParentFolderId = selectFolderViewModel.disabledNavigationParentFolderId
if (movedFolderIds.isEmpty() && movedParentFolderId == null) return@withContext false

val visitedIds = mutableSetOf<Int>()
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

current = FileController.getParentFile(current.id, navigationArgs.userDrive)
}

false
}

override fun fileListDirections(
folderToOpen: FolderToOpen,
): NavDirections = SelectRootFolderFragmentDirections.selectRootFolderFragmentToSelectFolderFragment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,15 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) :
).toBundle()
}

fun moveFiles(disabledFolderId: Int?) {
requireContext().moveFileClicked(disabledFolderId, selectFolderResultLauncher, mainViewModel)
fun moveFiles(disabledDestinationFolderId: Int?) = with(multiSelectManager) {
requireContext().moveFileClicked(
disabledDestinationFolderId = disabledDestinationFolderId,
selectFolderResultLauncher = selectFolderResultLauncher,
mainViewModel = mainViewModel,
filesToMove = getValidSelectedItems(),
disabledNavigationParentFolderId = if (isSelectAllOn) currentFolder?.id else null,
Comment thread
aymericmariaux marked this conversation as resolved.
exceptedNavigationFolderIds = if (isSelectAllOn) exceptedItemsIds.toIntArray() else null,
)
}

fun deleteFiles(allSelectedFilesCount: Int? = null) {
Expand Down Expand Up @@ -487,7 +494,7 @@ abstract class MultiSelectFragment(private val matomoCategory: MatomoCategory) :
)
} else {
mediator.value = mediator.value?.let {
MultiSelectMediatorState(it.numberOfSuccessfulActions, it.totalOfActions + 1, it.errorCode)
it.copy(totalOfActions = it.totalOfActions + 1)
}
}
}
Expand Down Expand Up @@ -549,22 +556,24 @@ 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?,
) {
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 + "/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading
Loading