diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b746dd9f40..4a503351262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ ownCloud admins and users. * 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 space image: [#4691](https://github.com/owncloud/android/issues/4691) * Enhancement - Show space quota: [#4693](https://github.com/owncloud/android/issues/4693) * Enhancement - Add user role to spaces: [#4698](https://github.com/owncloud/android/pull/4698) @@ -134,6 +135,15 @@ ownCloud admins and users. https://github.com/owncloud/android/issues/4611 https://github.com/owncloud/android/pull/4696 +* Enhancement - Update space image: [#4691](https://github.com/owncloud/android/issues/4691) + + A new option to update the space image has 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/4691 + https://github.com/owncloud/android/pull/4705 + * Enhancement - Show space quota: [#4693](https://github.com/owncloud/android/issues/4693) The used and total values of the space quota have been added to the bottom sheet diff --git a/changelog/unreleased/4705 b/changelog/unreleased/4705 new file mode 100644 index 00000000000..84baad63235 --- /dev/null +++ b/changelog/unreleased/4705 @@ -0,0 +1,7 @@ +Enhancement: Update space image + +A new option to update the space image has 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/4691 +https://github.com/owncloud/android/pull/4705 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 692c3e48383..7ed6ecbafba 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -91,6 +91,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUs 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.EditSpaceImageUseCase import com.owncloud.android.domain.spaces.usecases.EditSpaceUseCase import com.owncloud.android.domain.spaces.usecases.EnableSpaceUseCase import com.owncloud.android.domain.spaces.usecases.FilterSpaceMenuOptionsUseCase @@ -229,6 +230,7 @@ val useCaseModule = module { // Spaces factoryOf(::CreateSpaceUseCase) factoryOf(::DisableSpaceUseCase) + factoryOf(::EditSpaceImageUseCase) factoryOf(::EditSpaceUseCase) factoryOf(::EnableSpaceUseCase) factoryOf(::FilterSpaceMenuOptionsUseCase) 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 926a829f6ec..1da2679251c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -102,7 +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(), get(), get(), accountName, + SpacesListViewModel(get(), 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 788031197e3..e5a305360b3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMenuOptionExt.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMenuOptionExt.kt @@ -26,6 +26,7 @@ import com.owncloud.android.domain.spaces.model.SpaceMenuOption fun SpaceMenuOption.toStringResId() = when (this) { SpaceMenuOption.EDIT -> R.string.edit_space + SpaceMenuOption.EDIT_IMAGE -> R.string.edit_space_image SpaceMenuOption.DISABLE -> R.string.disable_space SpaceMenuOption.ENABLE -> R.string.enable_space SpaceMenuOption.DELETE -> R.string.delete_space @@ -34,6 +35,7 @@ fun SpaceMenuOption.toStringResId() = fun SpaceMenuOption.toDrawableResId() = when (this) { SpaceMenuOption.EDIT -> R.drawable.ic_pencil + SpaceMenuOption.EDIT_IMAGE -> R.drawable.file_image 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 1b84fc621d8..3030155cfad 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,7 +22,9 @@ package com.owncloud.android.presentation.spaces +import android.app.Activity import android.content.DialogInterface +import android.content.Intent import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater @@ -30,10 +32,12 @@ import android.view.Menu import android.view.MenuInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.SearchView import androidx.core.content.res.ResourcesCompat import androidx.core.os.bundleOf import androidx.core.view.isVisible +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import androidx.fragment.app.setFragmentResult import androidx.recyclerview.widget.GridLayoutManager @@ -43,8 +47,15 @@ import com.owncloud.android.R import com.owncloud.android.databinding.FileOptionsBottomSheetFragmentBinding import com.owncloud.android.databinding.SpacesListFragmentBinding import com.owncloud.android.domain.files.model.FileListOption +import com.owncloud.android.domain.files.model.MIME_BMP +import com.owncloud.android.domain.files.model.MIME_GIF +import com.owncloud.android.domain.files.model.MIME_JPEG +import com.owncloud.android.domain.files.model.MIME_PNG +import com.owncloud.android.domain.files.model.MIME_PREFIX_IMAGE +import com.owncloud.android.domain.files.model.MIME_X_MS_BMP import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceMenuOption +import com.owncloud.android.domain.transfers.model.TransferStatus import com.owncloud.android.domain.user.model.UserPermissions import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.collectLatestLifecycleFlow @@ -61,6 +72,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 com.owncloud.android.presentation.transfers.TransfersViewModel import kotlinx.coroutines.flow.SharedFlow import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -78,6 +90,8 @@ class SpacesListFragment : private var isMultiPersonal = false private var userPermissions = mutableSetOf() private var editQuotaPermission = false + private var lastUpdatedRemotePath: String? = null + private var selectedImageName: String? = null private lateinit var currentSpace: OCSpace private val spacesListViewModel: SpacesListViewModel by viewModel { @@ -91,6 +105,24 @@ class SpacesListFragment : requireArguments().getString(BUNDLE_ACCOUNT_NAME), ) } + private val transfersViewModel: TransfersViewModel by viewModel() + + private val editSpaceImageLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult + + val selectedImageUri = result.data?.data ?: return@registerForActivityResult + val accountName = requireArguments().getString(BUNDLE_ACCOUNT_NAME) ?: return@registerForActivityResult + val documentFile = DocumentFile.fromSingleUri(requireContext(), selectedImageUri) ?: return@registerForActivityResult + selectedImageName = documentFile.name + + transfersViewModel.uploadFilesFromContentUri( + accountName = accountName, + listOfContentUris = listOf(selectedImageUri), + uploadFolderPath = SPACE_CONFIG_DIR, + spaceId = currentSpace.id + ) + } private lateinit var spacesListAdapter: SpacesListAdapter @@ -202,6 +234,7 @@ class SpacesListFragment : 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.editSpaceImageFlow, R.string.edit_space_image_correctly, R.string.edit_space_image_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) @@ -210,6 +243,24 @@ class SpacesListFragment : showSpaceMenuOptionsDialog(menuOptions) } + collectLatestLifecycleFlow(transfersViewModel.transfersWithSpaceStateFlow) { transfersWithSpace -> + val remotePath = SPACE_CONFIG_DIR + selectedImageName + val matchedTransfer = transfersWithSpace.map { it.first }.find { it.remotePath == remotePath } + + if (matchedTransfer != null && lastUpdatedRemotePath != matchedTransfer.remotePath) { + when(matchedTransfer.status) { + TransferStatus.TRANSFER_SUCCEEDED -> { + spacesListViewModel.editSpaceImage(currentSpace.id, matchedTransfer.remotePath) + lastUpdatedRemotePath = matchedTransfer.remotePath + } + TransferStatus.TRANSFER_FAILED -> { + showMessageInSnackbar(getString(R.string.edit_space_image_failed)) + } + else -> { } + } + } + } + } private fun collectSpaceOperationsFlow(flow: SharedFlow>?>, successMessage: Int, errorMessage: Int) { @@ -382,6 +433,14 @@ class SpacesListFragment : negativeButtonText = getString(R.string.common_no) ) } + SpaceMenuOption.EDIT_IMAGE -> { + val action = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = MIME_PREFIX_IMAGE + putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(MIME_JPEG, MIME_PNG, MIME_BMP, MIME_X_MS_BMP, MIME_GIF)) + } + editSpaceImageLauncher.launch(action) + } } } } @@ -397,6 +456,7 @@ class SpacesListFragment : 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" + const val SPACE_CONFIG_DIR = "/.space/" 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 278fe0b07bf..2fecf8f24e6 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 @@ -33,6 +33,7 @@ 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.EditSpaceImageUseCase import com.owncloud.android.domain.spaces.usecases.EditSpaceUseCase import com.owncloud.android.domain.spaces.usecases.EnableSpaceUseCase import com.owncloud.android.domain.spaces.usecases.FilterSpaceMenuOptionsUseCase @@ -66,6 +67,7 @@ class SpacesListViewModel( private val createSpaceUseCase: CreateSpaceUseCase, private val filterSpaceMenuOptionsUseCase: FilterSpaceMenuOptionsUseCase, private val editSpaceUseCase: EditSpaceUseCase, + private val editSpaceImageUseCase: EditSpaceImageUseCase, private val disableSpaceUseCase: DisableSpaceUseCase, private val enableSpaceUseCase: EnableSpaceUseCase, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider, @@ -92,6 +94,9 @@ class SpacesListViewModel( private val _editSpaceFlow = MutableSharedFlow>?>() val editSpaceFlow: SharedFlow>?> = _editSpaceFlow + private val _editSpaceImageFlow = MutableSharedFlow>?>() + val editSpaceImageFlow: SharedFlow>?> = _editSpaceImageFlow + private val _disableSpaceFlow = MutableSharedFlow>?>() val disableSpaceFlow: SharedFlow>?> = _disableSpaceFlow @@ -224,6 +229,14 @@ class SpacesListViewModel( ) } + fun editSpaceImage(spaceId: String, remotePath: String) { + runSpaceOperation( + flow = _editSpaceImageFlow, + useCase = editSpaceImageUseCase, + useCaseParams = EditSpaceImageUseCase.Params(accountName, spaceId, remotePath) + ) + } + private fun runSpaceOperation( flow: MutableSharedFlow>?>, useCase: BaseUseCaseWithResult, diff --git a/owncloudApp/src/main/res/values/strings.xml b/owncloudApp/src/main/res/values/strings.xml index 926d619bb10..ca7a59e2a71 100644 --- a/owncloudApp/src/main/res/values/strings.xml +++ b/owncloudApp/src/main/res/values/strings.xml @@ -851,6 +851,9 @@ Edit space Space updated correctly Space could not be updated + Edit image + Image updated correctly + Image 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. diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/EditRemoteSpaceImageOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/EditRemoteSpaceImageOperation.kt new file mode 100644 index 00000000000..54a18b77ec4 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/EditRemoteSpaceImageOperation.kt @@ -0,0 +1,104 @@ +/** + * 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.JSONArray +import org.json.JSONObject +import timber.log.Timber +import java.net.URL + +class EditRemoteSpaceImageOperation( + private val spaceId: String, + private val imageId: 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 specialFolder = JSONObject().apply { + put(SPACE_NAME_BODY_PARAM, SPACE_NAME_BODY_PARAM_VALUE) + } + + val specialEntry = JSONObject().apply { + put(SPACE_ID_BODY_PARAM, imageId) + put(SPACE_SPECIAL_FOLDER_BODY_PARAM, specialFolder) + } + + val requestBody = JSONObject().apply { + put(SPACE_SPECIAL_BODY_PARAM, JSONArray().apply { put(specialEntry) }) + }.toString().toRequestBody(CONTENT_TYPE_JSON.toMediaType()) + + + val patchMethod = PatchMethod(URL(uriBuilder.build().toString()), requestBody) + + 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("Update of space completed and parsed to ${result.data}") + } else { + result = RemoteOperationResult(patchMethod) + Timber.e("Failed response while updating the space; status code: $status, response: $response") + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Exception while updating the space $spaceId") + } + return result + } + + companion object { + private const val GRAPH_API_SPACES_PATH = "graph/v1.0/drives/" + private const val SPACE_SPECIAL_BODY_PARAM = "special" + private const val SPACE_ID_BODY_PARAM = "id" + private const val SPACE_SPECIAL_FOLDER_BODY_PARAM = "specialFolder" + private const val SPACE_NAME_BODY_PARAM = "name" + private const val SPACE_NAME_BODY_PARAM_VALUE = "image" + } + +} 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 a0d4466cc2b..ac89b42de24 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 @@ -26,6 +26,7 @@ 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.EditRemoteSpaceImageOperation import com.owncloud.android.lib.resources.spaces.EditRemoteSpaceOperation import com.owncloud.android.lib.resources.spaces.EnableRemoteSpaceOperation import com.owncloud.android.lib.resources.spaces.GetRemoteSpacePermissionsOperation @@ -45,6 +46,9 @@ 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 editSpaceImage(spaceId: String, imageId: String): RemoteOperationResult = + EditRemoteSpaceImageOperation(spaceId, imageId).execute(client) + override fun disableSpace(spaceId: String, deleteMode: Boolean): RemoteOperationResult = DisableRemoteSpaceOperation(spaceId, deleteMode).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 304184339e7..d46c33d60d9 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,6 +31,7 @@ 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 editSpaceImage(spaceId: String, imageId: String): 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 04e38744547..f5bccc7643d 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,6 +27,7 @@ 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 editSpaceImage(accountName: String, spaceId: String, imageId: String): 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 1624345727a..8745cd41f1c 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,13 @@ class OCRemoteSpacesDataSource( return spaceResponse.toModel(accountName) } + override fun editSpaceImage(accountName: String, spaceId: String, imageId: String): OCSpace { + val spaceResponse = executeRemoteOperation { + clientManager.getSpacesService(accountName).editSpaceImage(spaceId, imageId) + } + return spaceResponse.toModel(accountName) + } + override fun disableSpace(accountName: String, spaceId: String, deleteMode: Boolean) { executeRemoteOperation { clientManager.getSpacesService(accountName).disableSpace(spaceId, deleteMode) } } 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 0e38ad737f3..903f91ff911 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,6 +88,10 @@ class OCSpacesRepository( remoteSpacesDataSource.editSpace(accountName, spaceId, spaceName, spaceSubtitle, spaceQuota) } + override fun editSpaceImage(accountName: String, spaceId: String, imageId: String) { + remoteSpacesDataSource.editSpaceImage(accountName, spaceId, imageId) + } + override fun disableSpace(accountName: String, spaceId: String, deleteMode: Boolean) { remoteSpacesDataSource.disableSpace(accountName, spaceId, deleteMode) } 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 6e8d9c8ad00..dbb3842567a 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 @@ -27,6 +27,7 @@ import com.owncloud.android.data.spaces.datasources.implementation.OCRemoteSpace import com.owncloud.android.lib.resources.spaces.services.OCSpacesService import com.owncloud.android.testutil.OC_ACCOUNT_NAME import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE +import com.owncloud.android.testutil.OC_SPACE_SPECIAL_IMAGE import com.owncloud.android.testutil.OC_USER_GROUPS import com.owncloud.android.testutil.OC_USER_ID import com.owncloud.android.testutil.SPACE_PERMISSIONS @@ -150,6 +151,33 @@ class OCRemoteSpacesDataSourceTest { } } + @Test + fun `editSpaceImage updates the project space image correctly`() { + val editSpaceImageOperationResult = createRemoteOperationResultMock(SPACE_RESPONSE, isSuccess = true) + + every { + ocSpaceService.editSpaceImage( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + imageId = OC_SPACE_SPECIAL_IMAGE.id + ) + } returns editSpaceImageOperationResult + + val spaceResult = ocRemoteSpacesDataSource.editSpaceImage( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + imageId = OC_SPACE_SPECIAL_IMAGE.id + ) + assertEquals(SPACE_RESPONSE.toModel(OC_ACCOUNT_NAME), spaceResult) + + verify(exactly = 1) { + clientManager.getSpacesService(OC_ACCOUNT_NAME) + ocSpaceService.editSpaceImage( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + imageId = OC_SPACE_SPECIAL_IMAGE.id + ) + } + } + @Test fun `disableSpace disables a project space correctly when delete mode is false`() { val disableSpaceResult = createRemoteOperationResultMock(Unit, isSuccess = true) 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 d68200fc764..4b126b3ffab 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 @@ -31,6 +31,7 @@ import com.owncloud.android.testutil.OC_SPACE_PERSONAL import com.owncloud.android.testutil.OC_SPACE_PERSONAL_WITH_LIMITED_QUOTA import com.owncloud.android.testutil.OC_SPACE_PERSONAL_WITH_UNLIMITED_QUOTA import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE +import com.owncloud.android.testutil.OC_SPACE_SPECIAL_IMAGE import com.owncloud.android.testutil.OC_USER_GROUPS import com.owncloud.android.testutil.OC_USER_ID import com.owncloud.android.testutil.OC_USER_QUOTA_LIMITED @@ -350,6 +351,31 @@ class OCSpacesRepositoryTest { } } + @Test + fun `editSpaceImage updates the space image correctly`() { + every { + remoteSpacesDataSource.editSpaceImage( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + imageId = OC_SPACE_SPECIAL_IMAGE.id + ) + } returns OC_SPACE_PROJECT_WITH_IMAGE + + ocSpacesRepository.editSpaceImage( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + imageId = OC_SPACE_SPECIAL_IMAGE.id + ) + + verify(exactly = 1) { + remoteSpacesDataSource.editSpaceImage( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + imageId = OC_SPACE_SPECIAL_IMAGE.id + ) + } + } + @Test fun `disableSpace disables a space correctly when delete mode is false`() { ocSpacesRepository.disableSpace(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, false) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/MimeTypeConstants.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/MimeTypeConstants.kt index 34c63a2087d..4282449a4c5 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/MimeTypeConstants.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/MimeTypeConstants.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel GarcĂ­a de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @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, @@ -29,4 +31,9 @@ const val MIME_PREFIX_IMAGE = "image/" const val MIME_PREFIX_TEXT = "text/" const val MIME_SVG = "image/svg+xml" +const val MIME_JPEG = "image/jpeg" +const val MIME_PNG = "image/png" +const val MIME_BMP = "image/bmp" +const val MIME_X_MS_BMP = "image/x-ms-bmp" +const val MIME_GIF = "image/gif" 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 40ec29d1cbd..29b756676bd 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,6 +37,7 @@ 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 editSpaceImage(accountName: String, spaceId: String, imageId: String) 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 99a97f0bd9c..a0bc8d3f3c8 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, DISABLE, ENABLE, DELETE + EDIT, EDIT_IMAGE, DISABLE, ENABLE, DELETE } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/EditSpaceImageUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/EditSpaceImageUseCase.kt new file mode 100644 index 00000000000..d32bcb62805 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/EditSpaceImageUseCase.kt @@ -0,0 +1,44 @@ +/** + * 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.files.FileRepository +import com.owncloud.android.domain.spaces.SpacesRepository + +class EditSpaceImageUseCase( + private val spacesRepository: SpacesRepository, + private val fileRepository: FileRepository +) : BaseUseCaseWithResult() { + + override fun run(params: Params) { + val image = fileRepository.readFile(params.remotePath, params.accountName, params.spaceId ) + val imageRemoteId = image.remoteId ?: return + spacesRepository.editSpaceImage(params.accountName, params.spaceId, imageRemoteId) + } + + data class Params( + val accountName: String, + val spaceId: String, + val remotePath: 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 f79337989ca..37aac58c70e 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 @@ -43,10 +43,16 @@ class FilterSpaceMenuOptionsUseCase( val deletePermission = (UserPermissions.CAN_DELETE_SPACES in params.userPermissions || hasSpacePermission(spacePermissionsResult, DRIVES_DELETE_PERMISSION)) + val editImagePermission = hasSpacePermission(spacePermissionsResult, DRIVES_MANAGE_PERMISSION) + if (editPermission || isSpaceManager) { optionsToShow.add(SpaceMenuOption.EDIT) } + if (editImagePermission) { + optionsToShow.add(SpaceMenuOption.EDIT_IMAGE) + } + if (!currentSpace.isDisabled && deletePermission) { optionsToShow.add(SpaceMenuOption.DISABLE) }