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/SpacesListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/SpacesListFragment.kt index 1f1af976183..2f819527fee 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,9 @@ 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.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 @@ -71,7 +74,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 @@ -181,8 +184,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,6 +198,15 @@ class SpacesListFragment : } } + observeSpaceOperationsFlows() + + collectLatestLifecycleFlow(spacesListViewModel.menuOptions) { menuOptions -> + showSpaceMenuOptionsDialog(menuOptions) + } + + } + + private fun observeSpaceOperationsFlows() { collectLatestLifecycleFlow(spacesListViewModel.createSpaceFlow) { event -> event?.let { when (val uiResult = event.peekContent()) { @@ -214,10 +227,35 @@ class SpacesListFragment : } } - collectLatestLifecycleFlow(spacesListViewModel.menuOptions) { menuOptions -> - showSpaceMenuOptionsDialog(menuOptions) + collectLatestLifecycleFlow(spacesListViewModel.disableSpaceFlow) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { showMessageInSnackbar(getString(R.string.disable_space_correctly)) } + is UIResult.Loading -> { } + is UIResult.Error -> { showErrorInSnackbar(R.string.disable_space_failed, uiResult.error) } + } + } } + collectLatestLifecycleFlow(spacesListViewModel.enableSpaceFlow) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { showMessageInSnackbar(getString(R.string.enable_space_correctly)) } + is UIResult.Loading -> { } + is UIResult.Error -> { showErrorInSnackbar(R.string.enable_space_failed, uiResult.error) } + } + } + } + + collectLatestLifecycleFlow(spacesListViewModel.deleteSpaceFlow) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { showMessageInSnackbar(getString(R.string.delete_space_correctly)) } + is UIResult.Loading -> { } + is UIResult.Error -> { showErrorInSnackbar(R.string.delete_space_failed, uiResult.error) } + } + } + } } private fun showOrHideEmptyView(spacesList: List) { @@ -243,7 +281,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 +375,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 +416,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..183ea37dbd1 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 @@ -31,12 +31,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 +65,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 +91,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() @@ -176,19 +190,49 @@ class SpacesListViewModel( } } - 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){ + viewModelScope.launch(coroutinesDispatcherProvider.io) { + when (val result = disableSpaceUseCase(DisableSpaceUseCase.Params(accountName, spaceId, false))) { + is UseCaseResult.Success -> _disableSpaceFlow.emit(Event(UIResult.Success(result.getDataOrNull()))) + is UseCaseResult.Error -> _disableSpaceFlow.emit(Event(UIResult.Error(error = result.getThrowableOrNull()))) + } + refreshSpacesFromServerAsyncUseCase(RefreshSpacesFromServerAsyncUseCase.Params(accountName)) + } + } + + fun enableSpace(spaceId: String){ + viewModelScope.launch(coroutinesDispatcherProvider.io) { + when (val result = enableSpaceUseCase(EnableSpaceUseCase.Params(accountName, spaceId))) { + is UseCaseResult.Success -> _enableSpaceFlow.emit(Event(UIResult.Success(result.getDataOrNull()))) + is UseCaseResult.Error -> _enableSpaceFlow.emit(Event(UIResult.Error(error = result.getThrowableOrNull()))) + } + refreshSpacesFromServerAsyncUseCase(RefreshSpacesFromServerAsyncUseCase.Params(accountName)) + } + } + + fun deleteSpace(spaceId: String){ + viewModelScope.launch(coroutinesDispatcherProvider.io) { + when (val result = disableSpaceUseCase(DisableSpaceUseCase.Params(accountName, spaceId, true))) { + is UseCaseResult.Success -> _deleteSpaceFlow.emit(Event(UIResult.Success(result.getDataOrNull()))) + is UseCaseResult.Error -> _deleteSpaceFlow.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/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/values/strings.xml b/owncloudApp/src/main/res/values/strings.xml index 03fdbafe26d..319ee04aa97 100644 --- a/owncloudApp/src/main/res/values/strings.xml +++ b/owncloudApp/src/main/res/values/strings.xml @@ -853,6 +853,21 @@ 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 + 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/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..63b666529b8 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,46 @@ class FilterSpaceMenuOptionsUseCase( override fun run(params: Params): MutableList { val optionsToShow = mutableListOf() + val currentSpace = params.space - 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) { 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" } } 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 +}