diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b224bcb5aa..c6b06927d1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ ownCloud admins and users. * Enhancement - Add account ID to the user information: [#4605](https://github.com/owncloud/android/issues/4605) * Enhancement - Create a new space: [#4606](https://github.com/owncloud/android/issues/4606) * Enhancement - Edit a space: [#4607](https://github.com/owncloud/android/issues/4607) +* Enhancement - Disable/Remove a space: [#4611](https://github.com/owncloud/android/issues/4611) * Enhancement - Update test in GitHub Actions: [#4663](https://github.com/owncloud/android/pull/4663) * Enhancement - New workflow to generate a build from "latest" tag on demand: [#4681](https://github.com/owncloud/android/pull/4681) * Enhancement - Make Update test more robust: [#4690](https://github.com/owncloud/android/pull/4690) @@ -143,6 +144,15 @@ ownCloud admins and users. https://github.com/owncloud/android/pull/4687 https://github.com/owncloud/android/pull/4694 +* Enhancement - Disable/Remove a space: [#4611](https://github.com/owncloud/android/issues/4611) + + New options to disable, enable and remove spaces have been added to the bottom + sheet, available only to users with the required permissions when the three-dot + menu button is tapped. + + https://github.com/owncloud/android/issues/4611 + https://github.com/owncloud/android/pull/4696 + * Enhancement - Update test in GitHub Actions: [#4663](https://github.com/owncloud/android/pull/4663) A new Github Actions workflow has been added, in order to check whether the diff --git a/changelog/unreleased/4696 b/changelog/unreleased/4696 new file mode 100644 index 00000000000..885f287614b --- /dev/null +++ b/changelog/unreleased/4696 @@ -0,0 +1,7 @@ +Enhancement: Disable/Remove a space + +New options to disable, enable and remove spaces have been added to the bottom sheet, available only to users +with the required permissions when the three-dot menu button is tapped. + +https://github.com/owncloud/android/issues/4611 +https://github.com/owncloud/android/pull/4696 diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index 799cff81337..692c3e48383 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -90,7 +90,9 @@ import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUse import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase import com.owncloud.android.domain.spaces.usecases.CreateSpaceUseCase +import com.owncloud.android.domain.spaces.usecases.DisableSpaceUseCase import com.owncloud.android.domain.spaces.usecases.EditSpaceUseCase +import com.owncloud.android.domain.spaces.usecases.EnableSpaceUseCase import com.owncloud.android.domain.spaces.usecases.FilterSpaceMenuOptionsUseCase import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase @@ -226,7 +228,9 @@ val useCaseModule = module { // Spaces factoryOf(::CreateSpaceUseCase) + factoryOf(::DisableSpaceUseCase) factoryOf(::EditSpaceUseCase) + factoryOf(::EnableSpaceUseCase) factoryOf(::FilterSpaceMenuOptionsUseCase) factoryOf(::GetPersonalAndProjectSpacesForAccountUseCase) factoryOf(::GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index 72bcb6c6040..926a829f6ec 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -102,6 +102,7 @@ val viewModelModule = module { get()) } viewModel { ReceiveExternalFilesViewModel(get(), get(), get(), get()) } viewModel { (accountName: String, showPersonalSpace: Boolean) -> - SpacesListViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), accountName, showPersonalSpace) + SpacesListViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), accountName, + showPersonalSpace) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMenuOptionExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMenuOptionExt.kt index 892c75cb603..788031197e3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMenuOptionExt.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMenuOptionExt.kt @@ -26,9 +26,15 @@ import com.owncloud.android.domain.spaces.model.SpaceMenuOption fun SpaceMenuOption.toStringResId() = when (this) { SpaceMenuOption.EDIT -> R.string.edit_space + SpaceMenuOption.DISABLE -> R.string.disable_space + SpaceMenuOption.ENABLE -> R.string.enable_space + SpaceMenuOption.DELETE -> R.string.delete_space } fun SpaceMenuOption.toDrawableResId() = when (this) { SpaceMenuOption.EDIT -> R.drawable.ic_pencil + SpaceMenuOption.DISABLE -> R.drawable.ic_disable_space + SpaceMenuOption.ENABLE -> R.drawable.ic_enable_space + SpaceMenuOption.DELETE -> R.drawable.ic_action_delete_white } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt index d1e83c86855..19826575bb0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListAdapter.kt @@ -24,6 +24,7 @@ package com.owncloud.android.presentation.spaces import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.Button import androidx.core.content.ContextCompat @@ -55,10 +56,13 @@ class SpacesListAdapter( val spacesViewHolder = holder as SpacesViewHolder spacesViewHolder.binding.apply { val space = spacesList[position] - - spacesListItemCard.setOnClickListener { - listener.onItemClick(space) + if (space.isDisabled) { + spacesListItemCard.isClickable = false + } else { + spacesListItemCard.setOnClickListener { listener.onItemClick(space) } } + spacesListItemImage.alpha = if (space.isDisabled) 0.5f else 1f + spacesListItemDisabledLabel.visibility = if (space.isDisabled) View.VISIBLE else View.GONE spacesListItemCard.setAccessibilityRole(className = Button::class.java) spacesListItemName.contentDescription = holder.itemView.context.getString(R.string.content_description_space_name, space.name) spacesThreeDotMenu.contentDescription = holder.itemView.context.getString(R.string.content_description_space_three_dot_menu, space.name) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 1f1af976183..788fd8a93ef 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt @@ -22,6 +22,7 @@ package com.owncloud.android.presentation.spaces +import android.content.DialogInterface import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater @@ -44,7 +45,10 @@ import com.owncloud.android.databinding.SpacesListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceMenuOption +import com.owncloud.android.domain.user.model.UserPermissions +import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.collectLatestLifecycleFlow +import com.owncloud.android.extensions.showAlertDialog import com.owncloud.android.extensions.showErrorInSnackbar import com.owncloud.android.extensions.showMessageInSnackbar import com.owncloud.android.extensions.toDrawableRes @@ -57,6 +61,7 @@ import com.owncloud.android.presentation.common.BottomSheetFragmentItemView import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.presentation.common.UIResult import com.owncloud.android.presentation.spaces.createspace.CreateSpaceDialogFragment +import kotlinx.coroutines.flow.SharedFlow import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import timber.log.Timber @@ -71,7 +76,7 @@ class SpacesListFragment : private val binding get() = _binding!! private var isMultiPersonal = false - private var editSpacesPermission = false + private var userPermissions = mutableSetOf() private var editQuotaPermission = false private lateinit var currentSpace: OCSpace @@ -136,7 +141,7 @@ class SpacesListFragment : collectLatestLifecycleFlow(spacesListViewModel.spacesList) { uiState -> if (uiState.searchFilter != "") { var spacesToListFiltered = - uiState.spaces.filter { it.name.lowercase().contains(uiState.searchFilter.lowercase()) && !it.isPersonal && !it.isDisabled } + uiState.spaces.filter { it.name.lowercase().contains(uiState.searchFilter.lowercase()) && !it.isPersonal } val personalSpace = uiState.spaces.find { it.isPersonal } personalSpace?.let { spacesToListFiltered = spacesToListFiltered.toMutableList().apply { @@ -147,7 +152,7 @@ class SpacesListFragment : spacesListAdapter.setData(spacesToListFiltered, isMultiPersonal) } else { showOrHideEmptyView(uiState.spaces) - spacesListAdapter.setData(uiState.spaces.filter { !it.isDisabled }, isMultiPersonal) + spacesListAdapter.setData(uiState.spaces, isMultiPersonal) } binding.swipeRefreshSpacesList.isRefreshing = uiState.refreshing uiState.error?.let { showErrorInSnackbar(R.string.spaces_sync_failed, it) } @@ -181,8 +186,9 @@ class SpacesListFragment : Timber.d("The permissions for $accountName are: ${uiResult.data}") uiResult.data?.let { binding.fabCreateSpace.isVisible = it.contains(DRIVES_CREATE_ALL_PERMISSION) - editSpacesPermission = it.contains(DRIVES_READ_WRITE_ALL_PERMISSION) + if(it.contains(DRIVES_READ_WRITE_ALL_PERMISSION)) userPermissions.add(UserPermissions.CAN_EDIT_SPACES) editQuotaPermission = it.contains(DRIVES_READ_WRITE_PROJECT_QUOTA_ALL_PERMISSION) + if(it.contains(DRIVES_DELETE_PROJECT_ALL_PERMISSION)) userPermissions.add(UserPermissions.CAN_DELETE_SPACES) } } is UIResult.Loading -> { } @@ -194,30 +200,28 @@ class SpacesListFragment : } } - collectLatestLifecycleFlow(spacesListViewModel.createSpaceFlow) { event -> - event?.let { - when (val uiResult = event.peekContent()) { - is UIResult.Success -> { showMessageInSnackbar(getString(R.string.create_space_correctly)) } - is UIResult.Loading -> { } - is UIResult.Error -> { showErrorInSnackbar(R.string.create_space_failed, uiResult.error) } - } - } + collectSpaceOperationsFlow(spacesListViewModel.createSpaceFlow, R.string.create_space_correctly, R.string.create_space_failed) + collectSpaceOperationsFlow(spacesListViewModel.editSpaceFlow, R.string.edit_space_correctly, R.string.edit_space_failed) + collectSpaceOperationsFlow(spacesListViewModel.disableSpaceFlow, R.string.disable_space_correctly, R.string.disable_space_failed) + collectSpaceOperationsFlow(spacesListViewModel.enableSpaceFlow, R.string.enable_space_correctly, R.string.enable_space_failed) + collectSpaceOperationsFlow(spacesListViewModel.deleteSpaceFlow, R.string.delete_space_correctly, R.string.delete_space_failed) + + collectLatestLifecycleFlow(spacesListViewModel.menuOptions) { menuOptions -> + showSpaceMenuOptionsDialog(menuOptions) } - collectLatestLifecycleFlow(spacesListViewModel.editSpaceFlow) { event -> + } + + private fun collectSpaceOperationsFlow(flow: SharedFlow>?>, successMessage: Int, errorMessage: Int) { + collectLatestLifecycleFlow(flow) { event -> event?.let { when (val uiResult = event.peekContent()) { - is UIResult.Success -> { showMessageInSnackbar(getString(R.string.edit_space_correctly)) } + is UIResult.Success -> { showMessageInSnackbar(getString(successMessage)) } is UIResult.Loading -> { } - is UIResult.Error -> { showErrorInSnackbar(R.string.edit_space_failed, uiResult.error) } + is UIResult.Error -> { showErrorInSnackbar(errorMessage, uiResult.error) } } } } - - collectLatestLifecycleFlow(spacesListViewModel.menuOptions) { menuOptions -> - showSpaceMenuOptionsDialog(menuOptions) - } - } private fun showOrHideEmptyView(spacesList: List) { @@ -243,7 +247,7 @@ class SpacesListFragment : override fun onThreeDotButtonClick(ocSpace: OCSpace) { currentSpace = ocSpace - spacesListViewModel.filterMenuOptions(ocSpace, editSpacesPermission) + spacesListViewModel.filterMenuOptions(ocSpace, userPermissions) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -337,6 +341,33 @@ class SpacesListFragment : ) editDialog.show(requireActivity().supportFragmentManager, DIALOG_CREATE_SPACE) } + SpaceMenuOption.DISABLE -> { + showAlertDialog( + title = getString(R.string.disable_space_dialog_title, currentSpace.name), + message = getString(R.string.disable_space_dialog_message), + positiveButtonText = getString(R.string.common_yes), + positiveButtonListener = { _: DialogInterface?, _: Int -> spacesListViewModel.disableSpace(currentSpace.id) }, + negativeButtonText = getString(R.string.common_no) + ) + } + SpaceMenuOption.ENABLE -> { + showAlertDialog( + title = getString(R.string.enable_space_dialog_title, currentSpace.name), + message = getString(R.string.enable_space_dialog_message), + positiveButtonText = getString(R.string.common_yes), + positiveButtonListener = { _: DialogInterface?, _: Int -> spacesListViewModel.enableSpace(currentSpace.id) }, + negativeButtonText = getString(R.string.common_no) + ) + } + SpaceMenuOption.DELETE -> { + showAlertDialog( + title = getString(R.string.delete_space_dialog_title, currentSpace.name), + message = getString(R.string.delete_space_dialog_message), + positiveButtonText = getString(R.string.common_yes), + positiveButtonListener = { _: DialogInterface?, _: Int -> spacesListViewModel.deleteSpace(currentSpace.id) }, + negativeButtonText = getString(R.string.common_no) + ) + } } } } @@ -351,6 +382,7 @@ class SpacesListFragment : const val DRIVES_CREATE_ALL_PERMISSION = "Drives.Create.all" const val DRIVES_READ_WRITE_ALL_PERMISSION = "Drives.ReadWrite.all" const val DRIVES_READ_WRITE_PROJECT_QUOTA_ALL_PERMISSION = "Drives.ReadWriteProjectQuota.all" + const val DRIVES_DELETE_PROJECT_ALL_PERMISSION = "Drives.DeleteProject.all" private const val DIALOG_CREATE_SPACE = "DIALOG_CREATE_SPACE" diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt index 5eb9b52c9bf..278fe0b07bf 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListViewModel.kt @@ -23,6 +23,7 @@ package com.owncloud.android.presentation.spaces import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.UseCaseResult import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import com.owncloud.android.domain.files.model.OCFile @@ -31,12 +32,15 @@ import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceMenuOption import com.owncloud.android.domain.spaces.usecases.CreateSpaceUseCase +import com.owncloud.android.domain.spaces.usecases.DisableSpaceUseCase import com.owncloud.android.domain.spaces.usecases.EditSpaceUseCase +import com.owncloud.android.domain.spaces.usecases.EnableSpaceUseCase import com.owncloud.android.domain.spaces.usecases.FilterSpaceMenuOptionsUseCase import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.GetPersonalSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.GetProjectSpacesWithSpecialsForAccountAsStreamUseCase import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase +import com.owncloud.android.domain.user.model.UserPermissions import com.owncloud.android.domain.user.usecases.GetUserIdAsyncUseCase import com.owncloud.android.domain.user.usecases.GetUserPermissionsAsyncUseCase import com.owncloud.android.domain.utils.Event @@ -62,6 +66,8 @@ class SpacesListViewModel( private val createSpaceUseCase: CreateSpaceUseCase, private val filterSpaceMenuOptionsUseCase: FilterSpaceMenuOptionsUseCase, private val editSpaceUseCase: EditSpaceUseCase, + private val disableSpaceUseCase: DisableSpaceUseCase, + private val enableSpaceUseCase: EnableSpaceUseCase, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, private val accountName: String, private val showPersonalSpace: Boolean, @@ -86,6 +92,15 @@ class SpacesListViewModel( private val _editSpaceFlow = MutableSharedFlow>?>() val editSpaceFlow: SharedFlow>?> = _editSpaceFlow + private val _disableSpaceFlow = MutableSharedFlow>?>() + val disableSpaceFlow: SharedFlow>?> = _disableSpaceFlow + + private val _enableSpaceFlow = MutableSharedFlow>?>() + val enableSpaceFlow: SharedFlow>?> = _enableSpaceFlow + + private val _deleteSpaceFlow = MutableSharedFlow>?>() + val deleteSpaceFlow: SharedFlow>?> = _deleteSpaceFlow + init { viewModelScope.launch(coroutinesDispatcherProvider.io) { refreshSpacesFromServer() @@ -157,38 +172,72 @@ class SpacesListViewModel( ) fun createSpace(spaceName: String, spaceSubtitle: String, spaceQuota: Long) { - viewModelScope.launch(coroutinesDispatcherProvider.io) { - when (val result = createSpaceUseCase(CreateSpaceUseCase.Params(accountName, spaceName, spaceSubtitle, spaceQuota))) { - is UseCaseResult.Success -> _createSpaceFlow.emit(Event(UIResult.Success(result.getDataOrNull()))) - is UseCaseResult.Error -> _createSpaceFlow.emit(Event(UIResult.Error(error = result.getThrowableOrNull()))) - } - refreshSpacesFromServerAsyncUseCase(RefreshSpacesFromServerAsyncUseCase.Params(accountName)) - } + runSpaceOperation( + flow = _createSpaceFlow, + useCase = createSpaceUseCase, + useCaseParams = CreateSpaceUseCase.Params(accountName, spaceName, spaceSubtitle, spaceQuota) + ) } fun editSpace(spaceId: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long?) { - viewModelScope.launch(coroutinesDispatcherProvider.io) { - when (val result = editSpaceUseCase(EditSpaceUseCase.Params(accountName, spaceId, spaceName, spaceSubtitle, spaceQuota))) { - is UseCaseResult.Success -> _editSpaceFlow.emit(Event(UIResult.Success(result.getDataOrNull()))) - is UseCaseResult.Error -> _editSpaceFlow.emit(Event(UIResult.Error(error = result.getThrowableOrNull()))) - } - refreshSpacesFromServerAsyncUseCase(RefreshSpacesFromServerAsyncUseCase.Params(accountName)) - } + runSpaceOperation( + flow = _editSpaceFlow, + useCase = editSpaceUseCase, + useCaseParams = EditSpaceUseCase.Params(accountName, spaceId, spaceName, spaceSubtitle, spaceQuota) + ) } - fun filterMenuOptions(space: OCSpace, editSpacesPermission: Boolean) { + fun filterMenuOptions(space: OCSpace, userPermissions: Set) { viewModelScope.launch(coroutinesDispatcherProvider.io) { val result = filterSpaceMenuOptionsUseCase( FilterSpaceMenuOptionsUseCase.Params( accountName = accountName, space = space, - editSpacesPermission = editSpacesPermission + userPermissions = userPermissions ) ) _menuOptions.emit(result) } } + fun disableSpace(spaceId: String){ + runSpaceOperation( + flow = _disableSpaceFlow, + useCase = disableSpaceUseCase, + useCaseParams = DisableSpaceUseCase.Params(accountName, spaceId, false) + ) + } + + fun enableSpace(spaceId: String){ + runSpaceOperation( + flow = _enableSpaceFlow, + useCase = enableSpaceUseCase, + useCaseParams = EnableSpaceUseCase.Params(accountName,spaceId) + ) + } + + fun deleteSpace(spaceId: String){ + runSpaceOperation( + flow = _deleteSpaceFlow, + useCase = disableSpaceUseCase, + useCaseParams = DisableSpaceUseCase.Params(accountName,spaceId,true) + ) + } + + private fun runSpaceOperation( + flow: MutableSharedFlow>?>, + useCase: BaseUseCaseWithResult, + useCaseParams: Params, + ) { + viewModelScope.launch(coroutinesDispatcherProvider.io) { + when (val result = useCase(useCaseParams)) { + is UseCaseResult.Success -> flow.emit(Event(UIResult.Success(result.getDataOrNull()))) + is UseCaseResult.Error -> flow.emit(Event(UIResult.Error(error = result.getThrowableOrNull()))) + } + refreshSpacesFromServerAsyncUseCase(RefreshSpacesFromServerAsyncUseCase.Params(accountName)) + } + } + data class SpacesListUiState( val spaces: List, val rootFolderFromSelectedSpace: OCFile? = null, diff --git a/owncloudApp/src/main/res/drawable/disabled_space_label_background.xml b/owncloudApp/src/main/res/drawable/disabled_space_label_background.xml new file mode 100644 index 00000000000..0414a9ba904 --- /dev/null +++ b/owncloudApp/src/main/res/drawable/disabled_space_label_background.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/owncloudApp/src/main/res/drawable/ic_disable_space.xml b/owncloudApp/src/main/res/drawable/ic_disable_space.xml new file mode 100644 index 00000000000..a8011d88a7f --- /dev/null +++ b/owncloudApp/src/main/res/drawable/ic_disable_space.xml @@ -0,0 +1,10 @@ + + + diff --git a/owncloudApp/src/main/res/drawable/ic_enable_space.xml b/owncloudApp/src/main/res/drawable/ic_enable_space.xml new file mode 100644 index 00000000000..53360e33837 --- /dev/null +++ b/owncloudApp/src/main/res/drawable/ic_enable_space.xml @@ -0,0 +1,10 @@ + + + diff --git a/owncloudApp/src/main/res/layout/spaces_list_item.xml b/owncloudApp/src/main/res/layout/spaces_list_item.xml index edfce0a1c43..cf0711ec24a 100644 --- a/owncloudApp/src/main/res/layout/spaces_list_item.xml +++ b/owncloudApp/src/main/res/layout/spaces_list_item.xml @@ -46,13 +46,42 @@ android:layout_height="match_parent" android:orientation="vertical"> - + android:layout_height="match_parent"> + + + + + + Edit space Space updated correctly Space could not be updated + Disable space + Do you really want to disable the space: %1$s? + If you disable the selected space, it can no longer be accessed. Only Space managers will still have access. Note: No files will be deleted from the server. + Space disabled correctly + Space could not be disabled + Disabled + Enable space + Do you really want to enable the space: %1$s? + If you enable the selected space, it can be accessed again. + Space enabled correctly + Space could not be enabled + Delete space + Do you really want to delete the space: %1$s? + If you delete the selected space, it can no longer be accessed and files will be deleted from the server. + Space deleted correctly + Space could not be deleted forum or contribute in our GitHub repo]]> diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/DisableRemoteSpaceOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/DisableRemoteSpaceOperation.kt new file mode 100644 index 00000000000..6f3de81b875 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/DisableRemoteSpaceOperation.kt @@ -0,0 +1,72 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2025 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.lib.resources.spaces + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.nonwebdav.DeleteMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import timber.log.Timber +import java.net.URL + +class DisableRemoteSpaceOperation( + private val spaceId: String, + private val deleteMode: Boolean +): RemoteOperation() { + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val uriBuilder = client.baseUri.buildUpon().apply { + appendEncodedPath(GRAPH_API_SPACES_PATH) + appendEncodedPath(spaceId) + } + + val deleteMethod = DeleteMethod(URL(uriBuilder.build().toString())).apply { + if (deleteMode) addRequestHeader(PURGE_HEADER, PURGE_HEADER_VALUE) + } + + val status = client.executeHttpMethod(deleteMethod) + + val response = deleteMethod.getResponseBodyAsString() + + if (status == HttpConstants.HTTP_NO_CONTENT) { + Timber.d("Successful response: $response") + result = RemoteOperationResult(ResultCode.OK) + } else { + result = RemoteOperationResult(deleteMethod) + Timber.e("Failed response while disabling/deleting the space; status code: $status, response: $response") + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Exception while disabling/deleting the space $spaceId") + } + return result + } + + companion object { + private const val GRAPH_API_SPACES_PATH = "graph/v1.0/drives/" + private const val PURGE_HEADER = "Purge" + private const val PURGE_HEADER_VALUE = "T" + } + +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/EnableRemoteSpaceOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/EnableRemoteSpaceOperation.kt new file mode 100644 index 00000000000..93685219c0c --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/EnableRemoteSpaceOperation.kt @@ -0,0 +1,89 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2025 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.lib.resources.spaces + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.HttpConstants.CONTENT_TYPE_JSON +import com.owncloud.android.lib.common.http.methods.nonwebdav.PatchMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import timber.log.Timber +import java.net.URL + +class EnableRemoteSpaceOperation( + private val spaceId: String, +): RemoteOperation() { + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val moshi = Moshi.Builder().build() + + val uriBuilder = client.baseUri.buildUpon().apply { + appendEncodedPath(GRAPH_API_SPACES_PATH) + appendEncodedPath(spaceId) + } + + val requestBody = JSONObject().toString().toRequestBody(CONTENT_TYPE_JSON.toMediaType()) + + val patchMethod = PatchMethod(URL(uriBuilder.build().toString()), requestBody).apply { + addRequestHeader(RESTORE_HEADER, RESTORE_HEADER_VALUE) + } + + val status = client.executeHttpMethod(patchMethod) + + val response = patchMethod.getResponseBodyAsString() + + if (status == HttpConstants.HTTP_OK) { + Timber.d("Successful response: $response") + + val responseAdapter: JsonAdapter = moshi.adapter(SpaceResponse::class.java) + + result = RemoteOperationResult(ResultCode.OK) + result.data = responseAdapter.fromJson(response) + + Timber.d("Space enabled successfully and parsed to ${result.data}") + } else { + result = RemoteOperationResult(patchMethod) + Timber.e("Failed response while enabling the space; status code: $status, response: $response") + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Exception while enabling the space $spaceId") + } + return result + } + + companion object { + private const val GRAPH_API_SPACES_PATH = "graph/v1.0/drives/" + private const val RESTORE_HEADER = "Restore" + private const val RESTORE_HEADER_VALUE = "T" + } + +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/OCSpacesService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/OCSpacesService.kt index 3a60f427ad2..a0d4466cc2b 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/OCSpacesService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/OCSpacesService.kt @@ -25,7 +25,9 @@ package com.owncloud.android.lib.resources.spaces.services import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.spaces.CreateRemoteSpaceOperation +import com.owncloud.android.lib.resources.spaces.DisableRemoteSpaceOperation import com.owncloud.android.lib.resources.spaces.EditRemoteSpaceOperation +import com.owncloud.android.lib.resources.spaces.EnableRemoteSpaceOperation import com.owncloud.android.lib.resources.spaces.GetRemoteSpacePermissionsOperation import com.owncloud.android.lib.resources.spaces.GetRemoteSpacesOperation import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse @@ -43,4 +45,10 @@ class OCSpacesService(override val client: OwnCloudClient) : SpacesService { override fun editSpace(spaceId: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long?): RemoteOperationResult = EditRemoteSpaceOperation(spaceId, spaceName, spaceSubtitle, spaceQuota).execute(client) + override fun disableSpace(spaceId: String, deleteMode: Boolean): RemoteOperationResult = + DisableRemoteSpaceOperation(spaceId, deleteMode).execute(client) + + override fun enableSpace(spaceId: String): RemoteOperationResult = + EnableRemoteSpaceOperation(spaceId).execute(client) + } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/SpacesService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/SpacesService.kt index ba1bc0dc268..304184339e7 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/SpacesService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/services/SpacesService.kt @@ -31,4 +31,6 @@ interface SpacesService : Service { fun createSpace(spaceName: String, spaceSubtitle: String, spaceQuota: Long): RemoteOperationResult fun getSpacePermissions(spaceId: String): RemoteOperationResult> fun editSpace(spaceId: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long?): RemoteOperationResult + fun disableSpace(spaceId: String, deleteMode: Boolean): RemoteOperationResult + fun enableSpace(spaceId: String): RemoteOperationResult } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt index ca313d274b7..04e38744547 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/RemoteSpacesDataSource.kt @@ -27,4 +27,6 @@ interface RemoteSpacesDataSource { fun createSpace(accountName: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long): OCSpace fun getSpacePermissions(accountName: String, spaceId: String): List fun editSpace(accountName: String, spaceId: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long?): OCSpace + fun disableSpace(accountName: String, spaceId: String, deleteMode: Boolean) + fun enableSpace(accountName: String, spaceId: String) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt index fe1e7bb0c36..1624345727a 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSource.kt @@ -64,6 +64,14 @@ class OCRemoteSpacesDataSource( return spaceResponse.toModel(accountName) } + override fun disableSpace(accountName: String, spaceId: String, deleteMode: Boolean) { + executeRemoteOperation { clientManager.getSpacesService(accountName).disableSpace(spaceId, deleteMode) } + } + + override fun enableSpace(accountName: String, spaceId: String) { + executeRemoteOperation { clientManager.getSpacesService(accountName).enableSpace(spaceId) } + } + companion object { private const val MANAGER_ROLE = "manager" private const val EDITOR_ROLE = "editor" diff --git a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt index f352583c078..0e38ad737f3 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/spaces/repository/OCSpacesRepository.kt @@ -88,4 +88,12 @@ class OCSpacesRepository( remoteSpacesDataSource.editSpace(accountName, spaceId, spaceName, spaceSubtitle, spaceQuota) } + override fun disableSpace(accountName: String, spaceId: String, deleteMode: Boolean) { + remoteSpacesDataSource.disableSpace(accountName, spaceId, deleteMode) + } + + override fun enableSpace(accountName: String, spaceId: String) { + remoteSpacesDataSource.enableSpace(accountName, spaceId) + } + } diff --git a/owncloudData/src/test/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSourceTest.kt index ba1dd1c19df..6e8d9c8ad00 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/spaces/datasources/implementation/OCRemoteSpacesDataSourceTest.kt @@ -150,4 +150,72 @@ class OCRemoteSpacesDataSourceTest { } } + @Test + fun `disableSpace disables a project space correctly when delete mode is false`() { + val disableSpaceResult = createRemoteOperationResultMock(Unit, isSuccess = true) + + every { + ocSpaceService.disableSpace( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + deleteMode = false + ) + } returns disableSpaceResult + + ocRemoteSpacesDataSource.disableSpace( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + deleteMode = false + ) + + verify(exactly = 1) { + clientManager.getSpacesService(OC_ACCOUNT_NAME) + ocSpaceService.disableSpace( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + deleteMode = false + ) + } + } + + @Test + fun `disableSpace deletes a project space correctly when delete mode is true`() { + val disableSpaceResult = createRemoteOperationResultMock(Unit, isSuccess = true) + + every { + ocSpaceService.disableSpace( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + deleteMode = true + ) + } returns disableSpaceResult + + ocRemoteSpacesDataSource.disableSpace( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + deleteMode = true + ) + + verify(exactly = 1) { + clientManager.getSpacesService(OC_ACCOUNT_NAME) + ocSpaceService.disableSpace( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + deleteMode = true + ) + } + } + + @Test + fun `enableSpace enables a project space correctly`() { + val enableSpaceResult = createRemoteOperationResultMock(SPACE_RESPONSE, isSuccess = true) + + every { + ocSpaceService.enableSpace(OC_SPACE_PROJECT_WITH_IMAGE.id) + } returns enableSpaceResult + + ocRemoteSpacesDataSource.enableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id) + + verify(exactly = 1) { + clientManager.getSpacesService(OC_ACCOUNT_NAME) + ocSpaceService.enableSpace(OC_SPACE_PROJECT_WITH_IMAGE.id) + } + } + } diff --git a/owncloudData/src/test/java/com/owncloud/android/data/spaces/repository/OCSpacesRepositoryTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/spaces/repository/OCSpacesRepositoryTest.kt index dfcb50a15c4..d68200fc764 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/spaces/repository/OCSpacesRepositoryTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/spaces/repository/OCSpacesRepositoryTest.kt @@ -51,7 +51,7 @@ class OCSpacesRepositoryTest { private val localSpacesDataSource = mockk(relaxUnitFun = true) private val localUserDataSource = mockk(relaxUnitFun = true) - private val remoteSpacesDataSource = mockk() + private val remoteSpacesDataSource = mockk(relaxUnitFun = true) private val localCapabilitiesDataSource = mockk(relaxUnitFun = true) private val ocSpacesRepository = OCSpacesRepository(localSpacesDataSource, localUserDataSource, remoteSpacesDataSource, localCapabilitiesDataSource) @@ -350,4 +350,31 @@ class OCSpacesRepositoryTest { } } + @Test + fun `disableSpace disables a space correctly when delete mode is false`() { + ocSpacesRepository.disableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, false) + + verify(exactly = 1) { + remoteSpacesDataSource.disableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, false) + } + } + + @Test + fun `disableSpace deletes a space correctly when delete mode is true`() { + ocSpacesRepository.disableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, true) + + verify(exactly = 1) { + remoteSpacesDataSource.disableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, true) + } + } + + @Test + fun `enableSpace enables a space correctly`() { + ocSpacesRepository.enableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id) + + verify(exactly = 1) { + remoteSpacesDataSource.enableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id) + } + } + } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt index 280cac87a8a..40ec29d1cbd 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/SpacesRepository.kt @@ -37,4 +37,6 @@ interface SpacesRepository { fun getWebDavUrlForSpace(accountName: String, spaceId: String?): String? fun createSpace(accountName: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long) fun editSpace(accountName: String, spaceId: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long?) + fun disableSpace(accountName: String, spaceId: String, deleteMode: Boolean) + fun enableSpace(accountName: String, spaceId: String) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/SpaceMenuOption.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/SpaceMenuOption.kt index 75c73510a0b..99a97f0bd9c 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/SpaceMenuOption.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/SpaceMenuOption.kt @@ -21,5 +21,5 @@ package com.owncloud.android.domain.spaces.model enum class SpaceMenuOption { - EDIT + EDIT, DISABLE, ENABLE, DELETE } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/DisableSpaceUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/DisableSpaceUseCase.kt new file mode 100644 index 00000000000..ef7c899a92b --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/DisableSpaceUseCase.kt @@ -0,0 +1,38 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2025 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.spaces.SpacesRepository + +class DisableSpaceUseCase( + private val spacesRepository: SpacesRepository +): BaseUseCaseWithResult() { + + override fun run(params: Params) = spacesRepository.disableSpace(params.accountName, params.spaceId, params.deleteMode) + + data class Params ( + val accountName: String, + val spaceId: String, + val deleteMode: Boolean + ) + +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/EnableSpaceUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/EnableSpaceUseCase.kt new file mode 100644 index 00000000000..caa3c5c0dfd --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/EnableSpaceUseCase.kt @@ -0,0 +1,37 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2025 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.spaces.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.spaces.SpacesRepository + +class EnableSpaceUseCase( + private val spacesRepository: SpacesRepository +): BaseUseCaseWithResult() { + + override fun run(params: Params) = spacesRepository.enableSpace(params.accountName, params.spaceId) + + data class Params ( + val accountName: String, + val spaceId: String + ) + +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/FilterSpaceMenuOptionsUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/FilterSpaceMenuOptionsUseCase.kt index 39e9172d280..f79337989ca 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/FilterSpaceMenuOptionsUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/FilterSpaceMenuOptionsUseCase.kt @@ -24,6 +24,7 @@ import com.owncloud.android.domain.BaseUseCase import com.owncloud.android.domain.UseCaseResult import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceMenuOption +import com.owncloud.android.domain.user.model.UserPermissions class FilterSpaceMenuOptionsUseCase( private val getSpacePermissionsAsyncUseCase: GetSpacePermissionsAsyncUseCase, @@ -31,31 +32,48 @@ class FilterSpaceMenuOptionsUseCase( override fun run(params: Params): MutableList { val optionsToShow = mutableListOf() + val currentSpace = params.space + val isSpaceManager = currentSpace.root.role == DRIVES_MANAGER_ROLE - val editPermission = if (params.editSpacesPermission) { - true - } else { - when (val spacePermissionsResult = - getSpacePermissionsAsyncUseCase(GetSpacePermissionsAsyncUseCase.Params(params.accountName, params.space.id))) { - is UseCaseResult.Success -> DRIVES_MANAGE_PERMISSION in spacePermissionsResult.data - is UseCaseResult.Error -> false - } - } + val spacePermissionsResult = getSpacePermissionsAsyncUseCase(GetSpacePermissionsAsyncUseCase.Params(params.accountName, params.space.id)) + + val editPermission = + (UserPermissions.CAN_EDIT_SPACES in params.userPermissions || hasSpacePermission(spacePermissionsResult, DRIVES_MANAGE_PERMISSION)) + + val deletePermission = + (UserPermissions.CAN_DELETE_SPACES in params.userPermissions || hasSpacePermission(spacePermissionsResult, DRIVES_DELETE_PERMISSION)) - if (editPermission) { + if (editPermission || isSpaceManager) { optionsToShow.add(SpaceMenuOption.EDIT) } + if (!currentSpace.isDisabled && deletePermission) { + optionsToShow.add(SpaceMenuOption.DISABLE) + } + + if (currentSpace.isDisabled) { + optionsToShow.add(SpaceMenuOption.ENABLE) + optionsToShow.add(SpaceMenuOption.DELETE) + } + return optionsToShow } + private fun hasSpacePermission(spacePermissions: UseCaseResult>, requiredPermission: String) = + when (spacePermissions) { + is UseCaseResult.Success -> requiredPermission in spacePermissions.data + is UseCaseResult.Error -> false + } + data class Params( val accountName: String, val space: OCSpace, - val editSpacesPermission: Boolean + val userPermissions: Set ) companion object { private const val DRIVES_MANAGE_PERMISSION = "libre.graph/driveItem/permissions/update" + private const val DRIVES_DELETE_PERMISSION = "libre.graph/driveItem/permissions/delete" + private const val DRIVES_MANAGER_ROLE = "manager" } } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/user/model/UserPermissions.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/user/model/UserPermissions.kt new file mode 100644 index 00000000000..e0f3aae7105 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/user/model/UserPermissions.kt @@ -0,0 +1,25 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2025 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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.owncloud.android.domain.user.model + +enum class UserPermissions { + CAN_EDIT_SPACES, CAN_DELETE_SPACES +}