diff --git a/changelog/unreleased/4289 b/changelog/unreleased/4289 new file mode 100644 index 00000000000..45c68289819 --- /dev/null +++ b/changelog/unreleased/4289 @@ -0,0 +1,6 @@ +Enhancement: Local-only option in file remove dialog is checks selected file/folder (files in folder recursively) and shown if at least one local file exists. + +Now local-only option in remove dialog will check file/folder (files in folder) from selection and will show it if at least one local file (downloaded) exists. + +https://github.com/owncloud/android/issues/3936 +https://github.com/owncloud/android/pull/4289 diff --git a/owncloudApp/src/main/java/com/owncloud/android/extensions/ViewModelExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/ViewModelExt.kt index bdcb934fda2..8a3a01813da 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/extensions/ViewModelExt.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/ViewModelExt.kt @@ -30,6 +30,7 @@ import com.owncloud.android.domain.utils.Event import com.owncloud.android.presentation.common.UIResult import com.owncloud.android.providers.ContextProvider import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -117,6 +118,43 @@ object ViewModelExt : KoinComponent { } } + fun ViewModel.runUseCaseWithResult( + coroutineDispatcher: CoroutineDispatcher, + requiresConnection: Boolean = true, + showLoading: Boolean = false, + sharedFlow: MutableSharedFlow>, + useCase: BaseUseCaseWithResult, + useCaseParams: Params, + postSuccess: Boolean = true, + postSuccessWithData: Boolean = true + ) { + viewModelScope.launch(coroutineDispatcher) { + if (showLoading) { + sharedFlow.emit(UIResult.Loading()) + } + + // If use case requires connection and is not connected, it is not needed to execute use case + if (requiresConnection and !contextProvider.isConnected()) { + sharedFlow.emit(UIResult.Error(error = NoNetworkConnectionException())) + Timber.w("${useCase.javaClass.simpleName} will not be executed due to lack of network connection") + return@launch + } + + val useCaseResult = useCase(useCaseParams) + Timber.d("Use case executed: ${useCase.javaClass.simpleName} with result: $useCaseResult") + + if (useCaseResult.isSuccess && postSuccess) { + if (postSuccessWithData) { + sharedFlow.emit(UIResult.Success(useCaseResult.getDataOrNull())) + } else { + sharedFlow.emit(UIResult.Success()) + } + } else if (useCaseResult.isError) { + sharedFlow.emit(UIResult.Error(error = useCaseResult.getThrowableOrNull())) + } + } + } + fun ViewModel.runUseCaseWithResultAndUseCachedData( coroutineDispatcher: CoroutineDispatcher, requiresConnection: Boolean = true, diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt index 31934c22373..6a1ca70b456 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/filelist/MainFileListFragment.kt @@ -156,6 +156,7 @@ class MainFileListFragment : Fragment(), private var menu: Menu? = null private var checkedFiles: List = emptyList() + private var filesToRemove: List = emptyList() private var fileSingleFile: OCFile? = null private var fileOptionsBottomSheetSingleFileLayout: LinearLayout? = null private var succeededTransfers: List? = null @@ -474,7 +475,8 @@ class MainFileListFragment : Fragment(), } FileMenuOption.REMOVE -> { - fileOperationsViewModel.showRemoveDialog(arrayListOf(file)) + filesToRemove = listOf(file) + fileOperationsViewModel.showRemoveDialog(filesToRemove) } FileMenuOption.OPEN_WITH -> { @@ -620,13 +622,13 @@ class MainFileListFragment : Fragment(), } } - fileOperationsViewModel.checkIfFileLocalLiveData.observe(viewLifecycleOwner, Event.EventObserver { + collectLatestLifecycleFlow(fileOperationsViewModel.checkIfFileLocalSharedFlow) { val fileActivity = (requireActivity() as FileActivity) when (it) { is UIResult.Loading -> fileActivity.showLoadingDialog(R.string.common_loading) is UIResult.Success -> { fileActivity.dismissLoadingDialog() - it.data?.let { result -> onShowRemoveDialog(result.first as ArrayList, result.second) } + it.data?.let { result -> onShowRemoveDialog(filesToRemove, result) } } is UIResult.Error -> { @@ -634,7 +636,7 @@ class MainFileListFragment : Fragment(), showMessageInSnackbar(resources.getString(R.string.common_error_unknown)) } } - }) + } /* TransfersViewModel observables */ observeTransfers() @@ -953,8 +955,8 @@ class MainFileListFragment : Fragment(), } } - private fun onShowRemoveDialog(filesToRemove: ArrayList, isLocal: Boolean) { - val dialog = RemoveFilesDialogFragment.newInstance(filesToRemove, isLocal) + private fun onShowRemoveDialog(filesToRemove: List, isLocal: Boolean) { + val dialog = RemoveFilesDialogFragment.newInstance(ArrayList(filesToRemove), isLocal) dialog.show(requireActivity().supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION) fileListAdapter.clearSelection() updateActionModeAfterTogglingSelected() @@ -1121,7 +1123,8 @@ class MainFileListFragment : Fragment(), } R.id.action_remove_file -> { - fileOperationsViewModel.showRemoveDialog(checkedFiles) + filesToRemove = checkedFiles + fileOperationsViewModel.showRemoveDialog(filesToRemove) return true } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt index 756ed37d16a..1e35c8dbd49 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/files/operations/FileOperationsViewModel.kt @@ -50,7 +50,9 @@ import com.owncloud.android.providers.CoroutinesDispatcherProvider import com.owncloud.android.ui.dialog.FileAlreadyExistsDialog import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import timber.log.Timber @@ -104,8 +106,8 @@ class FileOperationsViewModel( private val _deepLinkFlow = MutableStateFlow>?>(null) val deepLinkFlow: StateFlow>?> = _deepLinkFlow - private val _checkIfFileLocalLiveData = MediatorLiveData, Boolean>>>>() - val checkIfFileLocalLiveData: LiveData, Boolean>>>> = _checkIfFileLocalLiveData + private val _checkIfFileLocalSharedFlow = MutableSharedFlow>() + val checkIfFileLocalSharedFlow: SharedFlow> = _checkIfFileLocalSharedFlow val openDialogs = mutableListOf() @@ -132,7 +134,7 @@ class FileOperationsViewModel( runUseCaseWithResult( coroutineDispatcher = coroutinesDispatcherProvider.io, showLoading = true, - liveData = _checkIfFileLocalLiveData, + sharedFlow = _checkIfFileLocalSharedFlow, useCase = getIfFileFolderLocalUseCase, useCaseParams = GetIfFileFolderLocalUseCase.Params(filesToRemove), requiresConnection = false diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetIfFileFolderLocalUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetIfFileFolderLocalUseCase.kt index efb645a9b72..1eb7ac2dff7 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetIfFileFolderLocalUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetIfFileFolderLocalUseCase.kt @@ -23,21 +23,21 @@ import com.owncloud.android.domain.files.FileRepository import com.owncloud.android.domain.files.model.OCFile class GetIfFileFolderLocalUseCase(private val fileRepository: FileRepository) : - BaseUseCaseWithResult, Boolean>, GetIfFileFolderLocalUseCase.Params>() { + BaseUseCaseWithResult() { - override fun run(params: Params): Pair, Boolean> = getIfFileFolderLocal(params.filesToRemove) - private fun getIfFileFolderLocal(filesToRemove: List): Pair, Boolean> { + override fun run(params: Params): Boolean = getIfFileFolderLocal(params.filesToRemove) + private fun getIfFileFolderLocal(filesToRemove: List): Boolean { if (filesToRemove.any { it.isAvailableLocally }) { - return Pair(filesToRemove, true) + return true } else { filesToRemove.filter { it.isFolder }.forEach { folder -> - if (getIfFileFolderLocal(fileRepository.getFolderContent(folder.id!!)).second) { - return Pair(filesToRemove, true) + if (getIfFileFolderLocal(fileRepository.getFolderContent(folder.id!!))) { + return true } } } - return Pair(filesToRemove, false) + return false } data class Params(val filesToRemove: List)