Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE REQUEST] Folder with "Local only" remove option #4289

Merged
merged 5 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions changelog/unreleased/4289
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Correct "Local only" option in remove dialog

"Local only" option in remove dialog will only be shown if checking selected files and folders recursively, at least one file is available locally.

https://github.com/owncloud/android/issues/3936
https://github.com/owncloud/android/pull/4289
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase
import com.owncloud.android.domain.files.usecases.GetFileWithSyncInfoByIdUseCase
import com.owncloud.android.domain.files.usecases.GetFolderContentAsStreamUseCase
import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase
import com.owncloud.android.domain.files.usecases.IsAnyFileAvailableLocallyUseCase
import com.owncloud.android.domain.files.usecases.GetFolderImagesUseCase
import com.owncloud.android.domain.files.usecases.GetPersonalRootFolderForAccountUseCase
import com.owncloud.android.domain.files.usecases.GetSearchFolderContentUseCase
Expand Down Expand Up @@ -166,6 +167,7 @@ val useCaseModule = module {
factoryOf(::GetFolderContentAsStreamUseCase)
factoryOf(::GetFolderContentUseCase)
factoryOf(::GetFolderImagesUseCase)
factoryOf(::IsAnyFileAvailableLocallyUseCase)
factoryOf(::GetPersonalRootFolderForAccountUseCase)
factoryOf(::GetSearchFolderContentUseCase)
factoryOf(::GetSharedByLinkForAccountAsStreamUseCase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -117,6 +118,43 @@ object ViewModelExt : KoinComponent {
}
}

fun <T, Params> ViewModel.runUseCaseWithResult(
coroutineDispatcher: CoroutineDispatcher,
requiresConnection: Boolean = true,
showLoading: Boolean = false,
sharedFlow: MutableSharedFlow<UIResult<T>>,
useCase: BaseUseCaseWithResult<T, Params>,
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 <T, U, Params> ViewModel.runUseCaseWithResultAndUseCachedData(
coroutineDispatcher: CoroutineDispatcher,
requiresConnection: Boolean = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ class FileDetailsFragment : FileFragment() {
thumbnailImageView.bringToFront()
thumbnailImageView.isVisible = false

val file = ocFileWithSyncInfo.file
val file = ocFileWithSyncInfo.file
if (ocFileWithSyncInfo.isSynchronizing) {
thumbnailImageView.setImageResource(R.drawable.sync_pin)
thumbnailImageView.visibility = View.VISIBLE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class MainFileListFragment : Fragment(),

private var menu: Menu? = null
private var checkedFiles: List<OCFile> = emptyList()
private var filesToRemove: List<OCFile> = emptyList()
private var fileSingleFile: OCFile? = null
private var fileOptionsBottomSheetSingleFileLayout: LinearLayout? = null
private var succeededTransfers: List<OCTransfer>? = null
Expand Down Expand Up @@ -474,8 +475,8 @@ class MainFileListFragment : Fragment(),
}

FileMenuOption.REMOVE -> {
val dialogRemove = RemoveFilesDialogFragment.newInstance(file)
dialogRemove.show(requireActivity().supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
filesToRemove = listOf(file)
fileOperationsViewModel.showRemoveDialog(filesToRemove)
JuancaG05 marked this conversation as resolved.
Show resolved Hide resolved
}

FileMenuOption.OPEN_WITH -> {
Expand Down Expand Up @@ -621,6 +622,22 @@ class MainFileListFragment : Fragment(),
}
}

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(filesToRemove, result) }
}

is UIResult.Error -> {
fileActivity.dismissLoadingDialog()
showMessageInSnackbar(resources.getString(R.string.common_error_unknown))
}
}
}

/* TransfersViewModel observables */
observeTransfers()

Expand Down Expand Up @@ -938,6 +955,13 @@ class MainFileListFragment : Fragment(),
}
}

private fun onShowRemoveDialog(filesToRemove: List<OCFile>, isLocal: Boolean) {
val dialog = RemoveFilesDialogFragment.newInstance(ArrayList(filesToRemove), isLocal)
dialog.show(requireActivity().supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
fileListAdapter.clearSelection()
updateActionModeAfterTogglingSelected()
}

override fun onFolderNameSet(newFolderName: String, parentFolder: OCFile) {
fileOperationsViewModel.performOperation(FileOperation.CreateFolder(newFolderName, parentFolder))
fileOperationsViewModel.createFolder.observe(viewLifecycleOwner, Event.EventObserver { uiResult: UIResult<Unit> ->
Expand Down Expand Up @@ -1099,10 +1123,8 @@ class MainFileListFragment : Fragment(),
}

R.id.action_remove_file -> {
val dialog = RemoveFilesDialogFragment.newInstance(checkedFiles)
dialog.show(requireActivity().supportFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
fileListAdapter.clearSelection()
updateActionModeAfterTogglingSelected()
filesToRemove = checkedFiles
fileOperationsViewModel.showRemoveDialog(filesToRemove)
JuancaG05 marked this conversation as resolved.
Show resolved Hide resolved
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.owncloud.android.domain.exceptions.NoNetworkConnectionException
import com.owncloud.android.domain.files.model.OCFile
import com.owncloud.android.domain.files.usecases.CopyFileUseCase
import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase
import com.owncloud.android.domain.files.usecases.IsAnyFileAvailableLocallyUseCase
import com.owncloud.android.domain.files.usecases.ManageDeepLinkUseCase
import com.owncloud.android.domain.files.usecases.MoveFileUseCase
import com.owncloud.android.domain.files.usecases.RemoveFileUseCase
Expand All @@ -49,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
Expand All @@ -68,6 +71,7 @@ class FileOperationsViewModel(
private val unsetFilesAsAvailableOfflineUseCase: UnsetFilesAsAvailableOfflineUseCase,
private val manageDeepLinkUseCase: ManageDeepLinkUseCase,
private val setLastUsageFileUseCase: SetLastUsageFileUseCase,
private val isAnyFileAvailableLocallyUseCase: IsAnyFileAvailableLocallyUseCase,
private val contextProvider: ContextProvider,
private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider,
) : ViewModel() {
Expand Down Expand Up @@ -102,6 +106,9 @@ class FileOperationsViewModel(
private val _deepLinkFlow = MutableStateFlow<Event<UIResult<OCFile?>>?>(null)
val deepLinkFlow: StateFlow<Event<UIResult<OCFile?>>?> = _deepLinkFlow

private val _checkIfFileLocalSharedFlow = MutableSharedFlow<UIResult<Boolean>>()
val checkIfFileLocalSharedFlow: SharedFlow<UIResult<Boolean>> = _checkIfFileLocalSharedFlow

val openDialogs = mutableListOf<FileAlreadyExistsDialog>()

// Used to save the last operation folder
Expand All @@ -123,6 +130,17 @@ class FileOperationsViewModel(
}
}

fun showRemoveDialog(filesToRemove: List<OCFile>) {
runUseCaseWithResult(
coroutineDispatcher = coroutinesDispatcherProvider.io,
showLoading = true,
sharedFlow = _checkIfFileLocalSharedFlow,
useCase = isAnyFileAvailableLocallyUseCase,
useCaseParams = IsAnyFileAvailableLocallyUseCase.Params(filesToRemove),
requiresConnection = false
)
}

fun setLastUsageFile(file: OCFile) {
viewModelScope.launch(coroutinesDispatcherProvider.io) {
setLastUsageFileUseCase(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,19 @@ class RemoveFilesDialogFragment : ConfirmationDialogFragment(), ConfirmationDial
* @return Dialog ready to show.
*/
@JvmStatic
fun newInstance(files: ArrayList<OCFile>): RemoveFilesDialogFragment {
fun newInstance(files: ArrayList<OCFile>, isAvailableLocally: Boolean): RemoveFilesDialogFragment {
val messageStringId: Int
var containsFolder = false
var containsDown = false
var containsAvailableOffline = false
for (file in files) {
if (file.isFolder) {
containsFolder = true
}
if (file.isAvailableLocally) {
containsDown = true
}
if (file.isAvailableOffline) {
containsAvailableOffline = true
}
}

messageStringId = if (files.size == 1) {
// choose message for a single file
val file = files.first()
Expand All @@ -109,7 +106,7 @@ class RemoveFilesDialogFragment : ConfirmationDialogFragment(), ConfirmationDial
R.string.confirmation_remove_files_alert
}
}
val localRemoveButton = if (!containsAvailableOffline && (containsFolder || containsDown)) {
val localRemoveButton = if (!containsAvailableOffline && isAvailableLocally) {
R.string.confirmation_remove_local
} else {
-1
Expand Down Expand Up @@ -140,7 +137,7 @@ class RemoveFilesDialogFragment : ConfirmationDialogFragment(), ConfirmationDial
@JvmStatic
@JvmName("newInstanceForSingleFile")
fun newInstance(file: OCFile): RemoveFilesDialogFragment {
return newInstance(arrayListOf(file))
return newInstance(arrayListOf(file), file.isAvailableLocally)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class ReleaseNotesViewModel(
subtitle = R.string.release_notes_4_3_0_subtitle_6,
type = ReleaseNoteType.ENHANCEMENT,
),
ReleaseNote(
title = R.string.release_notes_4_3_0_title_7,
subtitle = R.string.release_notes_4_3_0_subtitle_7,
type = ReleaseNoteType.ENHANCEMENT
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,35 +263,43 @@ class PreviewAudioFragment : FileFragment() {
mContainerActivity.fileOperationsHelper.showShareFile(file)
true
}

R.id.action_open_file_with -> {
openFile()
true
}

R.id.action_remove_file -> {
val dialog = RemoveFilesDialogFragment.newInstance(file)
dialog.show(parentFragmentManager, ConfirmationDialogFragment.FTAG_CONFIRMATION)
true
}

R.id.action_see_details -> {
seeDetails()
true
}

R.id.action_send_file -> {
requireActivity().sendDownloadedFilesByShareSheet(listOf(file))
true
}

R.id.action_sync_file -> {
mContainerActivity.fileOperationsHelper.syncFile(file)
true
}

R.id.action_set_available_offline -> {
fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(file)))
true
}

R.id.action_unset_available_offline -> {
fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file)))
true
}

else -> super.onOptionsItemSelected(item)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,35 +216,43 @@ class PreviewImageFragment : FileFragment() {
mContainerActivity.fileOperationsHelper.showShareFile(file)
true
}

R.id.action_open_file_with -> {
openFile()
true
}

R.id.action_remove_file -> {
val dialog = RemoveFilesDialogFragment.newInstance(file)
dialog.show(requireFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION)
true
}

R.id.action_see_details -> {
seeDetails()
true
}

R.id.action_send_file -> {
requireActivity().sendDownloadedFilesByShareSheet(listOf(file))
true
}

R.id.action_sync_file -> {
mContainerActivity.fileOperationsHelper.syncFile(file)
true
}

R.id.action_set_available_offline -> {
fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(file)))
true
}

R.id.action_unset_available_offline -> {
fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file)))
true
}

else -> super.onOptionsItemSelected(item)
}
}
Expand Down
2 changes: 2 additions & 0 deletions owncloudApp/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,8 @@
<string name="release_notes_4_3_0_subtitle_5">The lens icon in the toolbar was removed when listing spaces in folder picker</string>
<string name="release_notes_4_3_0_title_6">Add warning in http connections</string>
<string name="release_notes_4_3_0_subtitle_6">Added a warning dialog in the login view when it is a http connection</string>
<string name="release_notes_4_3_0_title_7">Correct \"Local only\" option in remove dialog</string>
<string name="release_notes_4_3_0_subtitle_7">\"Local only\" option in remove dialog will only be shown if checking selected files and folders recursively, at least one file is available locally</string>

<!-- Open in web -->
<string name="ic_action_open_in_web">Open in web</string>
Expand Down
Loading