diff --git a/changelog/unreleased/4817 b/changelog/unreleased/4817 new file mode 100644 index 00000000000..be6ed5b3e0a --- /dev/null +++ b/changelog/unreleased/4817 @@ -0,0 +1,6 @@ +Enhancement: Edit space link + +A new option to edit space public links has been added. It will be only visible for users with proper permissions. + +https://github.com/owncloud/android/issues/4756 +https://github.com/owncloud/android/pull/4817 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 a86015d5fe6..8732933e9f5 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -80,6 +80,8 @@ import com.owncloud.android.domain.files.usecases.SortFilesUseCase import com.owncloud.android.domain.files.usecases.SortFilesWithSyncInfoUseCase import com.owncloud.android.domain.files.usecases.UpdateAlreadyDownloadedFilesPathUseCase import com.owncloud.android.domain.links.usecases.AddLinkUseCase +import com.owncloud.android.domain.links.usecases.EditLinkUseCase +import com.owncloud.android.domain.links.usecases.EditPasswordLinkUseCase import com.owncloud.android.domain.links.usecases.RemoveLinkUseCase import com.owncloud.android.domain.members.usecases.AddMemberUseCase import com.owncloud.android.domain.members.usecases.EditMemberUseCase @@ -321,5 +323,7 @@ val useCaseModule = module { // Links factoryOf(::AddLinkUseCase) + factoryOf(::EditLinkUseCase) + factoryOf(::EditPasswordLinkUseCase) factoryOf(::RemoveLinkUseCase) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/capabilities/CapabilityViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/capabilities/CapabilityViewModel.kt index 4c61b140cfd..3fbfa17daa3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/capabilities/CapabilityViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/capabilities/CapabilityViewModel.kt @@ -25,12 +25,10 @@ package com.owncloud.android.presentation.capabilities import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.ViewModel -import com.owncloud.android.domain.capabilities.model.CapabilityBooleanType import com.owncloud.android.domain.capabilities.model.OCCapability import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase -import com.owncloud.android.domain.links.model.OCLinkType import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResultAndUseCachedData import com.owncloud.android.presentation.common.UIResult @@ -83,11 +81,4 @@ class CapabilityViewModel( capabilities?.spaces?.hasMultiplePersonalSpaces == true } - fun checkPasswordEnforced(selectedPermission: OCLinkType, capabilities: OCCapability?) = - when(selectedPermission) { - OCLinkType.CAN_VIEW -> capabilities?.filesSharingPublicPasswordEnforcedReadOnly == CapabilityBooleanType.TRUE - OCLinkType.CAN_EDIT -> capabilities?.filesSharingPublicPasswordEnforcedReadWrite == CapabilityBooleanType.TRUE - OCLinkType.CREATE_ONLY -> capabilities?.filesSharingPublicPasswordEnforcedUploadOnly == CapabilityBooleanType.TRUE - else -> true - } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt index 79d09f147c4..19c90dcb25f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/AddPublicLinkFragment.kt @@ -31,19 +31,16 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.owncloud.android.R import com.owncloud.android.databinding.AddPublicLinkFragmentBinding -import com.owncloud.android.domain.capabilities.model.OCCapability +import com.owncloud.android.domain.links.model.OCLink import com.owncloud.android.domain.links.model.OCLinkType import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.hideSoftKeyboard import com.owncloud.android.extensions.showErrorInSnackbar -import com.owncloud.android.presentation.capabilities.CapabilityViewModel import com.owncloud.android.presentation.common.UIResult import com.owncloud.android.utils.DisplayUtils import org.koin.androidx.viewmodel.ext.android.activityViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf -import timber.log.Timber import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone @@ -60,15 +57,11 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi requireArguments().getParcelable(ARG_CURRENT_SPACE) ) } - private val capabilityViewModel: CapabilityViewModel by viewModel { - parametersOf( - accountName - ) - } - private var capabilities: OCCapability? = null private var isPasswordEnforced = true private var hasPassword = false + private var editMode = false + private var selectedPublicLink: OCLink? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { _binding = AddPublicLinkFragmentBinding.inflate(inflater, container, false) @@ -77,7 +70,9 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - requireActivity().setTitle(R.string.public_link_create_title) + editMode = requireArguments().getBoolean(ARG_EDIT_MODE) + selectedPublicLink = requireArguments().getParcelable(ARG_SELECTED_PUBLIC_LINK) + requireActivity().setTitle(if (editMode) R.string.public_link_edit_title else R.string.public_link_create_title) binding.publicLinkPermissions.apply { canViewPublicLinkRadioButton.tag = OCLinkType.CAN_VIEW @@ -94,7 +89,7 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi } } - hasPassword = it.selectedPassword != null + hasPassword = it.hasPassword it.selectedPermission?.let { binding.optionsLayout.isVisible = true binding.passwordLayout.apply { @@ -115,6 +110,7 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi openDatePickerDialog(uiState.selectedExpirationDate) } else { expirationDateSwitch.isChecked = true + openDatePickerDialog(null) } } } @@ -130,28 +126,28 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi } } - capabilityViewModel.capabilities.observe(viewLifecycleOwner) { event-> - when (val uiResult = event.peekContent()) { - is UIResult.Success -> { - capabilities = uiResult.data - } - is UIResult.Loading -> { } - is UIResult.Error -> { - Timber.e(uiResult.error, "Failed to retrieve server capabilities") + collectLatestLifecycleFlow(spaceLinksViewModel.addLinkResultFlow) { event -> + event?.peekContent()?.let { uiResult -> + when (uiResult) { + is UIResult.Loading -> { } + is UIResult.Success -> parentFragmentManager.popBackStack() + is UIResult.Error -> showErrorInSnackbar(R.string.public_link_add_failed, uiResult.error) } } } - collectLatestLifecycleFlow(spaceLinksViewModel.addLinkResultFlow) { event -> + collectLatestLifecycleFlow(spaceLinksViewModel.editLinkResultFlow) { event -> event?.peekContent()?.let { uiResult -> when (uiResult) { is UIResult.Loading -> { } is UIResult.Success -> parentFragmentManager.popBackStack() - is UIResult.Error -> showErrorInSnackbar(R.string.public_link_add_failed, uiResult.error) + is UIResult.Error -> showErrorInSnackbar(R.string.public_link_edit_failed, uiResult.error) } } } + if (editMode) { bindEditMode() } + binding.publicLinkPermissions.apply { canViewPublicLinkRadioButton.setOnClickListener { selectRadioButton(canViewPublicLinkRadioButton) } canViewPublicLinkLayout.setOnClickListener { selectRadioButton(canViewPublicLinkRadioButton) } @@ -174,9 +170,12 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi } binding.createPublicLinkButton.setOnClickListener { - spaceLinksViewModel.createPublicLink( - binding.publicLinkNameEditText.text.toString().ifEmpty { getString(R.string.public_link_default_display_name) } - ) + val displayName = binding.publicLinkNameEditText.text.toString().ifEmpty { getString(R.string.public_link_default_display_name) } + if (editMode) { + selectedPublicLink?.let { spaceLinksViewModel.editPublicLink(it.id, displayName) } + } else { + spaceLinksViewModel.createPublicLink(displayName) + } } } @@ -188,10 +187,11 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi override fun onSetPassword(password: String) { val normalizedPassword = password.ifBlank { null } - if (!isPasswordEnforced && normalizedPassword == null) { + val hasPassword = normalizedPassword != null + if (!isPasswordEnforced && !hasPassword) { binding.passwordLayout.setPasswordSwitch.isChecked = false } - spaceLinksViewModel.onPasswordSelected(normalizedPassword) + spaceLinksViewModel.onPasswordSelected(normalizedPassword, hasPassword) } private fun selectRadioButton(selectedRadioButton: RadioButton) { @@ -203,14 +203,14 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi selectedRadioButton.isChecked = true } val selectedPermission = selectedRadioButton.tag as OCLinkType - isPasswordEnforced = capabilityViewModel.checkPasswordEnforced(selectedPermission, capabilities) + isPasswordEnforced = spaceLinksViewModel.checkPasswordEnforced(selectedPermission) spaceLinksViewModel.onPermissionSelected(selectedPermission) } private fun bindDatePickerDialog(expirationDate: String?) { - binding.expirationDateLayout.expirationDateSwitch.setOnCheckedChangeListener { _, isChecked -> + binding.expirationDateLayout.expirationDateSwitch.setOnClickListener { hideKeyboardAndClearFocus() - if (isChecked) { + if (binding.expirationDateLayout.expirationDateSwitch.isChecked) { openDatePickerDialog(expirationDate) } else { binding.expirationDateLayout.expirationDateValue.visibility = View.GONE @@ -263,7 +263,7 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi private fun removePassword() { hideKeyboardAndClearFocus() - spaceLinksViewModel.onPasswordSelected(null) + spaceLinksViewModel.onPasswordSelected(null, false) } private fun hideKeyboardAndClearFocus() { @@ -271,18 +271,53 @@ class AddPublicLinkFragment: Fragment(), SetPasswordDialogFragment.SetPasswordLi binding.publicLinkNameEditText.clearFocus() } + private fun bindEditMode() { + selectedPublicLink?.let { + binding.publicLinkNameEditText.setText(it.displayName) + binding.createPublicLinkButton.apply { + setText(R.string.share_confirm_public_link_button) + contentDescription = getString(R.string.share_confirm_public_link_button) + } + + // Do not recreate the edit view after the first iteration + if (spaceLinksViewModel.addPublicLinkUIState.value?.selectedPermission != null) return + + when (it.type) { + OCLinkType.CAN_VIEW -> selectRadioButton(binding.publicLinkPermissions.canViewPublicLinkRadioButton) + OCLinkType.CAN_EDIT -> selectRadioButton(binding.publicLinkPermissions.canEditPublicLinkRadioButton) + OCLinkType.CREATE_ONLY -> selectRadioButton(binding.publicLinkPermissions.secretFileDropPublicLinkRadioButton) + else -> {} + } + + if (it.hasPassword) { + spaceLinksViewModel.onPasswordSelected(password = null, hasPassword = true, wasPasswordChanged = false) + } + + it.expirationDateTime?.let { expirationDate -> + spaceLinksViewModel.onExpirationDateSelected(expirationDate) + binding.expirationDateLayout.expirationDateSwitch.isChecked = true + } + } + } + companion object { private const val DIALOG_SET_PASSWORD = "DIALOG_SET_PASSWORD" private const val ARG_ACCOUNT_NAME = "ARG_ACCOUNT_NAME" private const val ARG_CURRENT_SPACE = "ARG_CURRENT_SPACE" + private const val ARG_EDIT_MODE = "ARG_EDIT_MODE" + private const val ARG_SELECTED_PUBLIC_LINK = "ARG_SELECTED_PUBLIC_LINK" fun newInstance( accountName: String, - currentSpace: OCSpace + currentSpace: OCSpace, + editMode: Boolean, + selectedPublicLink: OCLink? ): AddPublicLinkFragment { val args = Bundle().apply { putString(ARG_ACCOUNT_NAME, accountName) putParcelable(ARG_CURRENT_SPACE, currentSpace) + putBoolean(ARG_EDIT_MODE, editMode) + putParcelable(ARG_SELECTED_PUBLIC_LINK, selectedPublicLink) } return AddPublicLinkFragment().apply { arguments = args diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksAdapter.kt index 9bf99387c66..210b26350c9 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksAdapter.kt @@ -39,6 +39,7 @@ class SpaceLinksAdapter( private var spaceLinks: List = emptyList() private var canRemoveLinks = false + private var canEditLinks = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SpaceLinksViewHolder { val inflater = LayoutInflater.from(parent.context) @@ -78,13 +79,22 @@ class SpaceLinksAdapter( listener.onRemovePublicLink(spaceLink.id, spaceLink.displayName) } } + + editPublicLinkButton.apply { + contentDescription = holder.itemView.context.getString(R.string.content_description_edit_public_link, spaceLink.displayName) + isVisible = canEditLinks + setOnClickListener { + listener.onEditPublicLink(spaceLink) + } + } } } override fun getItemCount(): Int = spaceLinks.size - fun setSpaceLinks(spaceLinks: List, canRemoveLinks: Boolean) { + fun setSpaceLinks(spaceLinks: List, canRemoveLinks: Boolean, canEditLinks: Boolean) { this.canRemoveLinks = canRemoveLinks + this.canEditLinks = canEditLinks val diffCallback = SpaceLinksDiffUtil(this.spaceLinks, spaceLinks) val diffResult = DiffUtil.calculateDiff(diffCallback) this.spaceLinks = spaceLinks @@ -98,5 +108,6 @@ class SpaceLinksAdapter( interface SpaceLinksAdapterListener { fun onCopyOrSendPublicLink(publicLinkUrl: String) fun onRemovePublicLink(publicLinkId: String, publicLinkDisplayName: String) + fun onEditPublicLink(publicLink: OCLink) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt index 8fc26d98bda..055b1e4879d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/links/SpaceLinksViewModel.kt @@ -21,8 +21,14 @@ package com.owncloud.android.presentation.spaces.links import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.owncloud.android.domain.capabilities.model.CapabilityBooleanType +import com.owncloud.android.domain.capabilities.model.OCCapability +import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import com.owncloud.android.domain.links.model.OCLinkType import com.owncloud.android.domain.links.usecases.AddLinkUseCase +import com.owncloud.android.domain.links.usecases.EditLinkUseCase +import com.owncloud.android.domain.links.usecases.EditPasswordLinkUseCase import com.owncloud.android.domain.links.usecases.RemoveLinkUseCase import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.utils.Event @@ -34,9 +40,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch class SpaceLinksViewModel( private val addLinkUseCase: AddLinkUseCase, + private val editLinkUseCase: EditLinkUseCase, + private val editPasswordLinkUseCase: EditPasswordLinkUseCase, + private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase, private val removeLinkUseCase: RemoveLinkUseCase, private val accountName: String, private val space: OCSpace, @@ -52,8 +62,16 @@ class SpaceLinksViewModel( private val _removeLinkResultFlow = MutableSharedFlow>() val removeLinkResultFlow: SharedFlow> = _removeLinkResultFlow + private val _editLinkResultFlow = MutableStateFlow>?>(null) + val editLinkResultFlow: StateFlow>?> = _editLinkResultFlow + + private var capabilities: OCCapability? = null + init { _addPublicLinkUIState.value = AddPublicLinkUIState() + viewModelScope.launch(coroutineDispatcherProvider.io) { + capabilities = getStoredCapabilitiesUseCase(GetStoredCapabilitiesUseCase.Params(accountName)) + } } fun onPermissionSelected(permission: OCLinkType) { @@ -64,8 +82,8 @@ class SpaceLinksViewModel( _addPublicLinkUIState.update { it?.copy(selectedExpirationDate = expirationDate) } } - fun onPasswordSelected(password: String?) { - _addPublicLinkUIState.update { it?.copy(selectedPassword = password) } + fun onPasswordSelected(password: String?, hasPassword: Boolean, wasPasswordChanged: Boolean = true) { + _addPublicLinkUIState.update { it?.copy(selectedPassword = password, hasPassword = hasPassword, wasPasswordChanged = wasPasswordChanged) } } fun createPublicLink(displayName: String) { @@ -99,14 +117,53 @@ class SpaceLinksViewModel( ) } + fun editPublicLink(linkId: String, displayName: String) { + _addPublicLinkUIState.value?.selectedPermission?.let { + runUseCaseWithResult( + coroutineDispatcher = coroutineDispatcherProvider.io, + flow = _editLinkResultFlow, + useCase = editLinkUseCase, + useCaseParams = EditLinkUseCase.Params( + accountName = accountName, + spaceId = space.id, + linkId = linkId, + displayName = displayName, + type = it, + expirationDate = _addPublicLinkUIState.value?.selectedExpirationDate, + ) + ) + if (_addPublicLinkUIState.value?.wasPasswordChanged == true) { + viewModelScope.launch(coroutineDispatcherProvider.io) { + editPasswordLinkUseCase(EditPasswordLinkUseCase.Params( + accountName = accountName, + spaceId = space.id, + linkId = linkId, + password = _addPublicLinkUIState.value?.selectedPassword + )) + } + } + } + } + + fun checkPasswordEnforced(selectedPermission: OCLinkType) = + when(selectedPermission) { + OCLinkType.CAN_VIEW -> capabilities?.filesSharingPublicPasswordEnforcedReadOnly == CapabilityBooleanType.TRUE + OCLinkType.CAN_EDIT -> capabilities?.filesSharingPublicPasswordEnforcedReadWrite == CapabilityBooleanType.TRUE + OCLinkType.CREATE_ONLY -> capabilities?.filesSharingPublicPasswordEnforcedUploadOnly == CapabilityBooleanType.TRUE + else -> true + } + fun resetViewModel() { _addLinkResultFlow.value = null _addPublicLinkUIState.value = AddPublicLinkUIState() + _editLinkResultFlow.value = null } data class AddPublicLinkUIState( val selectedPermission: OCLinkType? = null, val selectedExpirationDate: String? = null, - val selectedPassword: String? = null + val selectedPassword: String? = null, + val hasPassword: Boolean = false, + val wasPasswordChanged: Boolean = false ) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt index fb2fe90acbc..cccd7150255 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersActivity.kt @@ -28,6 +28,7 @@ import android.view.MenuItem import androidx.fragment.app.transaction import com.owncloud.android.R import com.owncloud.android.databinding.MembersActivityBinding +import com.owncloud.android.domain.links.model.OCLink import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceMember @@ -101,8 +102,8 @@ class SpaceMembersActivity: FileActivity(), SpaceMembersFragment.SpaceMemberFrag } } - override fun addPublicLink(space: OCSpace) { - val addPublicLinkFragment = AddPublicLinkFragment.newInstance(account.name, space) + override fun addPublicLink(space: OCSpace, editMode: Boolean, selectedPublicLink: OCLink?) { + val addPublicLinkFragment = AddPublicLinkFragment.newInstance(account.name, space, editMode, selectedPublicLink) val transaction = supportFragmentManager.beginTransaction() transaction.apply { replace(R.id.members_fragment_container, addPublicLinkFragment, TAG_ADD_PUBLIC_LINK_FRAGMENT) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt index 206305fd1ae..809acd22fc3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersFragment.kt @@ -80,7 +80,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter private var spaceMembers: List = emptyList() private var listener: SpaceMemberFragmentListener? = null private var canRemoveMembersAndLinks = false - private var canEditMembers = false + private var canEditMembersAndLinks = false private var canReadMembers = false private var numberOfManagers = 1 @@ -112,7 +112,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter currentSpace = requireArguments().getParcelable(ARG_CURRENT_SPACE) ?: return savedInstanceState?.let { canRemoveMembersAndLinks = it.getBoolean(CAN_REMOVE_MEMBERS, false) - canEditMembers = it.getBoolean(CAN_EDIT_MEMBERS, false) + canEditMembersAndLinks = it.getBoolean(CAN_EDIT_MEMBERS, false) canReadMembers = it.getBoolean(CAN_READ_MEMBERS, false) } @@ -131,7 +131,11 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter binding.addPublicLinkButton.setOnClickListener { spaceLinksViewModel.resetViewModel() - listener?.addPublicLink(currentSpace) + listener?.addPublicLink( + space = currentSpace, + editMode = false, + selectedPublicLink = null + ) } } @@ -158,7 +162,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(CAN_REMOVE_MEMBERS, canRemoveMembersAndLinks) - outState.putBoolean(CAN_EDIT_MEMBERS, canEditMembers) + outState.putBoolean(CAN_EDIT_MEMBERS, canEditMembersAndLinks) outState.putBoolean(CAN_READ_MEMBERS, canReadMembers) } @@ -197,6 +201,15 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter ) } + override fun onEditPublicLink(publicLink: OCLink) { + spaceLinksViewModel.resetViewModel() + listener?.addPublicLink( + space = currentSpace, + editMode = true, + selectedPublicLink = publicLink + ) + } + private fun subscribeToViewModels() { observeRoles() observeSpaceMembers() @@ -206,6 +219,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter observeEditMemberResult() observeAddLinkResult() observeRemoveLinkResult() + observeEditLinkResult() } private fun observeRoles() { @@ -356,10 +370,25 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter } } + private fun observeEditLinkResult() { + collectLatestLifecycleFlow(spaceLinksViewModel.editLinkResultFlow) { event -> + event?.peekContent()?.let { uiResult -> + when (uiResult) { + is UIResult.Loading -> { } + is UIResult.Success -> { + showMessageInSnackbar(getString(R.string.public_link_edit_correctly)) + spaceLinksViewModel.resetViewModel() + } + is UIResult.Error -> { } + } + } + } + } + private fun checkPermissions(spacePermissions: List) { val hasCreatePermission = DRIVES_CREATE_PERMISSION in spacePermissions canRemoveMembersAndLinks = DRIVES_DELETE_PERMISSION in spacePermissions - canEditMembers = DRIVES_UPDATE_PERMISSION in spacePermissions + canEditMembersAndLinks = DRIVES_UPDATE_PERMISSION in spacePermissions canReadMembers = DRIVES_READ_PERMISSION in spacePermissions binding.apply { addMemberButton.isVisible = hasCreatePermission @@ -381,7 +410,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter spaceMembers = spaceMembers, roles = roles, canRemoveMembers = canRemoveMembersAndLinks, - canEditMembers = canEditMembers, + canEditMembers = canEditMembersAndLinks, numberOfManagers = numberOfManagers ) } @@ -398,13 +427,14 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter Date(0) } }, - canRemoveLinks = canRemoveMembersAndLinks + canRemoveLinks = canRemoveMembersAndLinks, + canEditLinks = canEditMembersAndLinks ) } interface SpaceMemberFragmentListener { fun addMember(space: OCSpace, spaceMembers: List, roles: List, editMode: Boolean, selectedMember: SpaceMember?) - fun addPublicLink(space: OCSpace) + fun addPublicLink(space: OCSpace, editMode: Boolean, selectedPublicLink: OCLink?) fun copyOrSendPublicLink(publicLinkUrl: String, spaceName: String) } diff --git a/owncloudApp/src/main/res/layout/public_link_item.xml b/owncloudApp/src/main/res/layout/public_link_item.xml index 06c4402c69c..7c8746de313 100644 --- a/owncloudApp/src/main/res/layout/public_link_item.xml +++ b/owncloudApp/src/main/res/layout/public_link_item.xml @@ -88,6 +88,7 @@ android:textSize="13sp" android:textColor="@color/textColor" android:ellipsize="middle" + android:maxLines="1" android:visibility="gone" tools:visibility="visible"/> @@ -109,6 +110,19 @@ android:src="@drawable/copy_link" android:visibility="visible"/> + + Recreating the same link again is not possible Public link removed correctly Public link could not be removed + Edit public link + Public link edited correctly + Public link could not be edited forum or contribute in our GitHub repo]]> diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/EditRemoteLinkOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/EditRemoteLinkOperation.kt new file mode 100644 index 00000000000..cf14959d717 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/EditRemoteLinkOperation.kt @@ -0,0 +1,90 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2026 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.links + +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 okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import timber.log.Timber +import java.net.URL + +class EditRemoteLinkOperation( + private val spaceId: String, + private val linkId: String, + private val displayName: String, + private val type: String, + private val expirationDate: String? +): RemoteOperation() { + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val uriBuilder = client.baseUri.buildUpon().apply { + appendEncodedPath(GRAPH_API_DRIVES_PATH) + appendEncodedPath(spaceId) + appendEncodedPath(GRAPH_API_ROOT_PERMISSIONS_PATH) + appendEncodedPath(linkId) + } + + val requestBody = JSONObject().apply { + put(EXPIRATION_DATE_BODY_PARAM, expirationDate ?: JSONObject.NULL) + put(LINK_BODY_PARAM, JSONObject().apply { + put(DISPLAY_NAME_BODY_PARAM, displayName) + put(TYPE_BODY_PARAM, type) + }) + }.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") + result = RemoteOperationResult(ResultCode.OK) + Timber.d("Edit public link operation completed and parsed to ${result.data}") + } else { + result = RemoteOperationResult(patchMethod) + Timber.e("Failed response while editing a public link; status code: $status, response: $response") + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Exception while editing a public link") + } + return result + } + + companion object { + private const val GRAPH_API_DRIVES_PATH = "graph/v1beta1/drives/" + private const val GRAPH_API_ROOT_PERMISSIONS_PATH = "root/permissions" + private const val EXPIRATION_DATE_BODY_PARAM = "expirationDateTime" + private const val LINK_BODY_PARAM = "link" + private const val DISPLAY_NAME_BODY_PARAM = "@libre.graph.displayName" + private const val TYPE_BODY_PARAM = "type" + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/EditRemotePasswordLinkOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/EditRemotePasswordLinkOperation.kt new file mode 100644 index 00000000000..42e3a240178 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/EditRemotePasswordLinkOperation.kt @@ -0,0 +1,83 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2026 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.links + +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.PostMethod +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 okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import timber.log.Timber +import java.net.URL + +class EditRemotePasswordLinkOperation( + private val spaceId: String, + private val linkId: String, + private val password: String? +): RemoteOperation() { + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val uriBuilder = client.baseUri.buildUpon().apply { + appendEncodedPath(GRAPH_API_DRIVES_PATH) + appendEncodedPath(spaceId) + appendEncodedPath(GRAPH_API_ROOT_PERMISSIONS_PATH) + appendEncodedPath(linkId) + appendEncodedPath(GRAPH_API_SET_PASSWORD_PATH) + } + + val requestBody = JSONObject().apply { + put(PASSWORD_BODY_PARAM, password) + }.toString().toRequestBody(CONTENT_TYPE_JSON.toMediaType()) + + val postMethod = PostMethod(URL(uriBuilder.build().toString()), requestBody) + + val status = client.executeHttpMethod(postMethod) + + val response = postMethod.getResponseBodyAsString() + + if (status == HttpConstants.HTTP_OK) { + Timber.d("Successful response: $response") + result = RemoteOperationResult(ResultCode.OK) + Timber.d("Edit password public link operation completed and parsed to ${result.data}") + } else { + result = RemoteOperationResult(postMethod) + Timber.e("Failed response while editing password public link; status code: $status, response: $response") + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Exception while editing password public link") + } + return result + } + + companion object { + private const val GRAPH_API_DRIVES_PATH = "graph/v1beta1/drives/" + private const val GRAPH_API_ROOT_PERMISSIONS_PATH = "root/permissions" + private const val GRAPH_API_SET_PASSWORD_PATH = "setPassword" + private const val PASSWORD_BODY_PARAM = "password" + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt index 211cd11cb39..278a326881e 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/LinksService.kt @@ -25,5 +25,7 @@ import com.owncloud.android.lib.resources.Service interface LinksService: Service { fun addLink(spaceId: String, displayName: String, type: String, expirationDate: String?, password: String?): RemoteOperationResult + fun editLink(spaceId: String, linkId: String, displayName: String, type: String, expirationDate: String?): RemoteOperationResult + fun editPasswordLink(spaceId: String, linkId: String, password: String?): RemoteOperationResult fun removeLink(spaceId: String, linkId: String): RemoteOperationResult } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt index 955ac2d6176..85d1d89bdaa 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/links/services/OCLinksService.kt @@ -23,6 +23,8 @@ package com.owncloud.android.lib.resources.links.services import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.links.AddRemoteLinkOperation +import com.owncloud.android.lib.resources.links.EditRemoteLinkOperation +import com.owncloud.android.lib.resources.links.EditRemotePasswordLinkOperation import com.owncloud.android.lib.resources.links.RemoveRemoteLinkOperation class OCLinksService(override val client: OwnCloudClient) : LinksService { @@ -42,6 +44,28 @@ class OCLinksService(override val client: OwnCloudClient) : LinksService { password = password ).execute(client) + override fun editLink( + spaceId: String, + linkId: String, + displayName: String, + type: String, + expirationDate: String? + ): RemoteOperationResult = + EditRemoteLinkOperation( + spaceId = spaceId, + linkId = linkId, + displayName = displayName, + type = type, + expirationDate = expirationDate + ).execute(client) + + override fun editPasswordLink(spaceId: String, linkId: String, password: String?): RemoteOperationResult = + EditRemotePasswordLinkOperation( + spaceId = spaceId, + linkId = linkId, + password = password + ).execute(client) + override fun removeLink(spaceId: String, linkId: String): RemoteOperationResult = RemoveRemoteLinkOperation( spaceId = spaceId, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt index 64ca776647d..43b32e72f7f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/RemoteLinksDataSource.kt @@ -24,5 +24,7 @@ import com.owncloud.android.domain.links.model.OCLinkType interface RemoteLinksDataSource { fun addLink(accountName: String, spaceId: String, displayName: String, type: OCLinkType, expirationDate: String?, password: String?) + fun editLink(accountName: String, spaceId: String, linkId: String, displayName: String, type: OCLinkType, expirationDate: String?) + fun editPasswordLink(accountName: String, spaceId: String, linkId: String, password: String?) fun removeLink(accountName: String, spaceId: String, linkId: String) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt index ea6fc7a275e..627e7e8b493 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSource.kt @@ -35,6 +35,18 @@ class OCRemoteLinksDataSource( } } + override fun editLink(accountName: String, spaceId: String, linkId: String, displayName: String, type: OCLinkType, expirationDate: String?) { + executeRemoteOperation { + clientManager.getLinksService(accountName).editLink(spaceId, linkId, displayName, OCLinkType.toString(type), expirationDate) + } + } + + override fun editPasswordLink(accountName: String, spaceId: String, linkId: String, password: String?) { + executeRemoteOperation { + clientManager.getLinksService(accountName).editPasswordLink(spaceId, linkId, password) + } + } + override fun removeLink(accountName: String, spaceId: String, linkId: String) { executeRemoteOperation { clientManager.getLinksService(accountName).removeLink(spaceId, linkId) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt index 20be0168676..beff9f05772 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/links/repository/OCLinksRepository.kt @@ -32,6 +32,14 @@ class OCLinksRepository( remoteLinksDataSource.addLink(accountName, spaceId, displayName, type, expirationDate, password) } + override fun editLink(accountName: String, spaceId: String, linkId: String, displayName: String, type: OCLinkType, expirationDate: String?) { + remoteLinksDataSource.editLink(accountName, spaceId, linkId, displayName, type, expirationDate) + } + + override fun editPasswordLink(accountName: String, spaceId: String, linkId: String, password: String?) { + remoteLinksDataSource.editPasswordLink(accountName, spaceId, linkId, password) + } + override fun removeLink(accountName: String, spaceId: String, linkId: String) { remoteLinksDataSource.removeLink(accountName, spaceId, linkId) } diff --git a/owncloudData/src/test/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSourceTest.kt index c1e88db4ff9..f8b6a0cda96 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/links/datasources/implementation/OCRemoteLinksDataSourceTest.kt @@ -83,6 +83,70 @@ class OCRemoteLinksDataSourceTest { } } + @Test + fun `editLink edits a public link from a project space correctly`() { + val editLinkResult = createRemoteOperationResultMock(Unit, isSuccess = true) + + every { + ocLinksService.editLink( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + displayName = SPACE_MEMBERS.links[0].displayName, + type = OCLinkType.toString(SPACE_MEMBERS.links[0].type), + expirationDate = SPACE_MEMBERS.links[0].expirationDateTime + ) + } returns editLinkResult + + ocRemoteLinksDataSource.editLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + displayName = SPACE_MEMBERS.links[0].displayName, + type = SPACE_MEMBERS.links[0].type, + expirationDate = SPACE_MEMBERS.links[0].expirationDateTime + ) + + verify(exactly = 1) { + clientManager.getLinksService(OC_ACCOUNT_NAME) + ocLinksService.editLink( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + displayName = SPACE_MEMBERS.links[0].displayName, + type = OCLinkType.toString(SPACE_MEMBERS.links[0].type), + expirationDate = SPACE_MEMBERS.links[0].expirationDateTime + ) + } + } + + @Test + fun `editPasswordLink edits the password of a public link from a project space correctly`() { + val editPasswordLinkResult = createRemoteOperationResultMock(Unit, isSuccess = true) + + every { + ocLinksService.editPasswordLink( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + password = password + ) + } returns editPasswordLinkResult + + ocRemoteLinksDataSource.editPasswordLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + password = password + ) + + verify(exactly = 1) { + clientManager.getLinksService(OC_ACCOUNT_NAME) + ocLinksService.editPasswordLink( + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + password = password + ) + } + } + @Test fun `removeLink removes a public link from a project space correctly`() { val removeLinkResult = createRemoteOperationResultMock(Unit, isSuccess = true) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/links/repository/OCLinksRepositoryTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/links/repository/OCLinksRepositoryTest.kt index 11ca72aac84..78937211634 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/links/repository/OCLinksRepositoryTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/links/repository/OCLinksRepositoryTest.kt @@ -70,6 +70,68 @@ class OCLinksRepositoryTest { } } + @Test + fun `editLink edits a public link from a space correctly`() { + every { + remoteLinksDataSource.editLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + displayName = SPACE_MEMBERS.links[0].displayName, + type = SPACE_MEMBERS.links[0].type, + expirationDate = SPACE_MEMBERS.links[0].expirationDateTime + ) + } returns Unit + + ocLinksRepository.editLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + displayName = SPACE_MEMBERS.links[0].displayName, + type = SPACE_MEMBERS.links[0].type, + expirationDate = SPACE_MEMBERS.links[0].expirationDateTime + ) + + verify(exactly = 1) { + remoteLinksDataSource.editLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + displayName = SPACE_MEMBERS.links[0].displayName, + type = SPACE_MEMBERS.links[0].type, + expirationDate = SPACE_MEMBERS.links[0].expirationDateTime + ) + } + } + + @Test + fun `editPasswordLink edits the password of a public link from a space correctly`() { + every { + remoteLinksDataSource.editPasswordLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + password = password + ) + } returns Unit + + ocLinksRepository.editPasswordLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + password = password + ) + + verify(exactly = 1) { + remoteLinksDataSource.editPasswordLink( + accountName = OC_ACCOUNT_NAME, + spaceId = OC_SPACE_PROJECT_WITH_IMAGE.id, + linkId = SPACE_MEMBERS.links[0].id, + password = password + ) + } + } + @Test fun `removeLink removes a public link from a space correctly`() { every { diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt index 3ffc549ecc1..7e6ba93700f 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/LinksRepository.kt @@ -24,5 +24,7 @@ import com.owncloud.android.domain.links.model.OCLinkType interface LinksRepository { fun addLink(accountName: String, spaceId: String, displayName: String, type: OCLinkType, expirationDate: String?, password: String?) + fun editLink(accountName: String, spaceId: String, linkId: String, displayName: String, type: OCLinkType, expirationDate: String?) + fun editPasswordLink(accountName: String, spaceId: String, linkId: String, password: String?) fun removeLink(accountName: String, spaceId: String, linkId: String) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/EditLinkUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/EditLinkUseCase.kt new file mode 100644 index 00000000000..979d859c7c8 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/EditLinkUseCase.kt @@ -0,0 +1,43 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2026 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.links.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.links.LinksRepository +import com.owncloud.android.domain.links.model.OCLinkType + +class EditLinkUseCase( + private val linksRepository: LinksRepository +): BaseUseCaseWithResult() { + + override fun run(params: Params) { + linksRepository.editLink(params.accountName, params.spaceId, params.linkId, params.displayName, params.type, params.expirationDate) + } + + data class Params( + val accountName: String, + val spaceId: String, + val linkId: String, + val displayName: String, + val type: OCLinkType, + val expirationDate: String?, + ) +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/EditPasswordLinkUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/EditPasswordLinkUseCase.kt new file mode 100644 index 00000000000..b931719751e --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/links/usecases/EditPasswordLinkUseCase.kt @@ -0,0 +1,40 @@ +/** + * ownCloud Android client application + * + * @author Jorge Aguado Recio + * + * Copyright (C) 2026 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.links.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.links.LinksRepository + +class EditPasswordLinkUseCase( + private val linksRepository: LinksRepository +): BaseUseCaseWithResult() { + + override fun run(params: Params) { + linksRepository.editPasswordLink(params.accountName, params.spaceId, params.linkId, params.password) + } + + data class Params( + val accountName: String, + val spaceId: String, + val linkId: String, + val password: String? + ) +}