diff --git a/changelog/unreleased/4945 b/changelog/unreleased/4945 new file mode 100644 index 00000000000..1a651433576 --- /dev/null +++ b/changelog/unreleased/4945 @@ -0,0 +1,7 @@ +Enhancement: List shares for oCIS accounts + +A new UI has been implemented to list and show information about the +shares of a file or folder on oCIS accounts (using Graph API) + +https://github.com/owncloud/android/issues/4876 +https://github.com/owncloud/android/pull/4945 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 8732933e9f5..8b1eae2e9e5 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -95,6 +95,7 @@ import com.owncloud.android.domain.sharing.shares.usecases.CreatePublicShareAsyn import com.owncloud.android.domain.sharing.shares.usecases.DeleteShareAsyncUseCase import com.owncloud.android.domain.sharing.shares.usecases.EditPrivateShareAsyncUseCase import com.owncloud.android.domain.sharing.shares.usecases.EditPublicShareAsyncUseCase +import com.owncloud.android.domain.sharing.shares.usecases.GetGraphSharesAsyncUseCase import com.owncloud.android.domain.sharing.shares.usecases.GetShareAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.GetSharesAsLiveDataUseCase import com.owncloud.android.domain.sharing.shares.usecases.RefreshSharesFromServerAsyncUseCase @@ -233,6 +234,7 @@ val useCaseModule = module { factoryOf(::DeleteShareAsyncUseCase) factoryOf(::EditPrivateShareAsyncUseCase) factoryOf(::EditPublicShareAsyncUseCase) + factoryOf(::GetGraphSharesAsyncUseCase) factoryOf(::GetShareAsLiveDataUseCase) factoryOf(::GetShareesAsyncUseCase) factoryOf(::GetSharesAsLiveDataUseCase) 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 c9dee872bcb..c22b569ea3d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -53,6 +53,7 @@ import com.owncloud.android.presentation.settings.automaticuploads.SettingsVideo import com.owncloud.android.presentation.settings.logging.SettingsLogsViewModel import com.owncloud.android.presentation.settings.more.SettingsMoreViewModel import com.owncloud.android.presentation.settings.security.SettingsSecurityViewModel +import com.owncloud.android.presentation.sharing.GraphShareViewModel import com.owncloud.android.presentation.sharing.ShareViewModel import com.owncloud.android.presentation.spaces.SpacesListViewModel import com.owncloud.android.presentation.spaces.links.SpaceLinksViewModel @@ -70,6 +71,7 @@ val viewModelModule = module { viewModelOf(::DrawerViewModel) viewModelOf(::FileDetailsViewModel) viewModelOf(::FileOperationsViewModel) + viewModelOf(::GraphShareViewModel) viewModelOf(::LogListViewModel) viewModelOf(::OAuthViewModel) viewModelOf(::PatternViewModel) diff --git a/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMemberExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMemberExt.kt index 93619de7848..79e2954a43f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMemberExt.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/SpaceMemberExt.kt @@ -22,12 +22,12 @@ package com.owncloud.android.extensions import com.owncloud.android.domain.members.model.OCMember import com.owncloud.android.domain.members.model.OCMemberType -import com.owncloud.android.domain.spaces.model.SpaceMember +import com.owncloud.android.domain.sharing.shares.model.MemberPermission private const val GROUP_PREFIX = "g:" private const val USER_PREFIX = "u:" -fun SpaceMember.toOCMember(): OCMember { +fun MemberPermission.toOCMember(): OCMember { val isGroup = id.startsWith(GROUP_PREFIX) val type = if (isGroup) OCMemberType.GROUP else OCMemberType.USER return OCMember( diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/releasenotes/ReleaseNotesViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/releasenotes/ReleaseNotesViewModel.kt index 9777ec03d6a..4869ed3fb9c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/releasenotes/ReleaseNotesViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/releasenotes/ReleaseNotesViewModel.kt @@ -43,6 +43,11 @@ class ReleaseNotesViewModel( companion object { val releaseNotesList = listOf( + ReleaseNote( + title = R.string.release_notes_4_9_0_sharing_ng_title, + subtitle = R.string.release_notes_4_9_0_sharing_ng_subtitle, + type = ReleaseNoteType.ENHANCEMENT + ), ReleaseNote( title = R.string.release_notes_bugfixes_title, subtitle = R.string.release_notes_bugfixes_subtitle, diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareFragment.kt new file mode 100644 index 00000000000..6f8d5f9623d --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareFragment.kt @@ -0,0 +1,147 @@ +/** + * 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.presentation.sharing + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.owncloud.android.R +import com.owncloud.android.databinding.MembersFragmentBinding +import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.roles.model.OCRole +import com.owncloud.android.extensions.collectLatestLifecycleFlow +import com.owncloud.android.extensions.showErrorInSnackbar +import com.owncloud.android.presentation.common.UIResult +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import timber.log.Timber +import kotlin.apply + +class GraphShareFragment : Fragment() { + private var _binding: MembersFragmentBinding? = null + private val binding get() = _binding!! + + private val graphShareViewModel by viewModel { + parametersOf( + requireArguments().getString(ARG_ACCOUNT_NAME), + requireArguments().getParcelable(ARG_FILE) + ) + } + + private lateinit var graphSharesAdapter: GraphSharesAdapter + + private var roles: List = emptyList() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = MembersFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.membersTitle.text = getString(R.string.share_with_people_title) + + graphSharesAdapter = GraphSharesAdapter() + binding.membersRecyclerView.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = graphSharesAdapter + } + + binding.swipeRefreshMembers.setOnRefreshListener { + graphShareViewModel.getGraphShares() + } + + subscribeToViewModels() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun subscribeToViewModels() { + observeRoles() + observeShares() + } + + private fun observeRoles() { + collectLatestLifecycleFlow(graphShareViewModel.roles) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { + uiResult.data?.let { + roles = it + graphShareViewModel.getGraphShares() + } + } + is UIResult.Loading -> { } + is UIResult.Error -> { + showErrorInSnackbar(R.string.share_sync_failed, uiResult.error) + Timber.e(uiResult.error, "Failed to retrieve platform roles") + } + } + } + } + } + + private fun observeShares() { + collectLatestLifecycleFlow(graphShareViewModel.shares) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { + uiResult.data?.let { + val hasMembers = it.members.isNotEmpty() + binding.membersRecyclerView.isVisible = hasMembers + binding.noSharesMessage.isVisible = !hasMembers + if (hasMembers) graphSharesAdapter.setShares(it.members, it.roles) + binding.swipeRefreshMembers.isRefreshing = false + } + } + is UIResult.Loading -> { binding.swipeRefreshMembers.isRefreshing = true } + is UIResult.Error -> { + binding.swipeRefreshMembers.isRefreshing = false + showErrorInSnackbar(R.string.share_sync_failed, uiResult.error) + Timber.e(uiResult.error, "Failed to retrieve shares") + } + } + } + } + } + + companion object { + private const val ARG_FILE = "FILE" + private const val ARG_ACCOUNT_NAME = "ACCOUNT_NAME" + + fun newInstance(file: OCFile, accountName: String): GraphShareFragment { + val args = Bundle().apply { + putParcelable(ARG_FILE, file) + putString(ARG_ACCOUNT_NAME, accountName) + } + return GraphShareFragment().apply { + arguments = args + } + } + } +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareViewModel.kt new file mode 100644 index 00000000000..3072111ce3e --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareViewModel.kt @@ -0,0 +1,70 @@ +/** + * 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.presentation.sharing + +import androidx.lifecycle.ViewModel +import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.roles.model.OCRole +import com.owncloud.android.domain.roles.usecases.GetRolesAsyncUseCase +import com.owncloud.android.domain.sharing.shares.usecases.GetGraphSharesAsyncUseCase +import com.owncloud.android.domain.sharing.shares.model.OCPermissions +import com.owncloud.android.domain.utils.Event +import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult +import com.owncloud.android.presentation.common.UIResult +import com.owncloud.android.providers.CoroutinesDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class GraphShareViewModel( + private val getRolesAsyncUseCase: GetRolesAsyncUseCase, + private val getGraphSharesAsyncUseCase: GetGraphSharesAsyncUseCase, + private val accountName: String, + private val file: OCFile, + private val coroutineDispatcherProvider: CoroutinesDispatcherProvider, +) : ViewModel() { + + private val _roles = MutableStateFlow>>?>(null) + val roles: StateFlow>>?> = _roles + + private val _shares = MutableStateFlow>?>(null) + val shares: StateFlow>?> = _shares + + init { + runUseCaseWithResult( + coroutineDispatcher = coroutineDispatcherProvider.io, + flow = _roles, + useCase = getRolesAsyncUseCase, + useCaseParams = GetRolesAsyncUseCase.Params(accountName = accountName), + ) + } + + fun getGraphShares() = runUseCaseWithResult( + coroutineDispatcher = coroutineDispatcherProvider.io, + showLoading = true, + flow = _shares, + useCase = getGraphSharesAsyncUseCase, + useCaseParams = GetGraphSharesAsyncUseCase.Params( + accountName = accountName, + spaceId = file.spaceId.orEmpty(), + itemId = file.remoteId.orEmpty() + ) + ) +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesAdapter.kt new file mode 100644 index 00000000000..062a18850b0 --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesAdapter.kt @@ -0,0 +1,88 @@ +/** + * 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.presentation.sharing + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.owncloud.android.R +import com.owncloud.android.databinding.MemberItemBinding +import com.owncloud.android.domain.roles.model.OCRole +import com.owncloud.android.domain.sharing.shares.model.MemberPermission +import com.owncloud.android.utils.DisplayUtils +import com.owncloud.android.utils.PreferenceUtils + +class GraphSharesAdapter : RecyclerView.Adapter() { + + private var shares: List = emptyList() + private var rolesMap: Map = emptyMap() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GraphShareViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.member_item, parent, false) + view.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(parent.context) + return GraphShareViewHolder(view) + } + + override fun onBindViewHolder(holder: GraphShareViewHolder, position: Int) { + val share = shares[position] + val roleNames = share.roles.mapNotNull { rolesMap[it] } + + holder.binding.apply { + memberIcon.setImageResource(if (share.isGroup) R.drawable.ic_group else R.drawable.ic_user) + memberName.text = share.displayName + memberName.contentDescription = holder.itemView.context.getString( + if (share.isGroup) R.string.content_description_member_group else R.string.content_description_member_user, + share.displayName + ) + memberRole.text = roleNames.joinToString(", ") + + val hasExpirationDate = share.expirationDateTime != null + expirationCalendarIcon.isVisible = hasExpirationDate + expirationDate.isVisible = hasExpirationDate + if (hasExpirationDate) { + expirationDate.text = DisplayUtils.displayDateToHumanReadable(share.expirationDateTime) + expirationDate.contentDescription = + holder.itemView.context.getString(R.string.content_description_member_expiration_date, expirationDate.text) + } + } + } + + override fun getItemCount(): Int = shares.size + + fun setShares(shares: List, roles: List) { + this.rolesMap = roles.associate { it.id to it.displayName } + val sortedShares = shares.sortedWith( + compareByDescending { share -> roles.indexOfFirst { it.id in share.roles } } + .thenBy { it.displayName } + ) + val diffResult = DiffUtil.calculateDiff(GraphSharesDiffUtil(this.shares, sortedShares)) + this.shares = sortedShares + diffResult.dispatchUpdatesTo(this) + } + + class GraphShareViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val binding = MemberItemBinding.bind(itemView) + } +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesDiffUtil.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesDiffUtil.kt new file mode 100644 index 00000000000..39bf62427ee --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphSharesDiffUtil.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.presentation.sharing + +import androidx.recyclerview.widget.DiffUtil +import com.owncloud.android.domain.sharing.shares.model.MemberPermission + +class GraphSharesDiffUtil( + private val oldList: List, + private val newList: List, +) : DiffUtil.Callback() { + + override fun getOldListSize(): Int = oldList.size + + override fun getNewListSize(): Int = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].id == newList[newItemPosition].id + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/ShareActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/ShareActivity.kt index 8aba893d508..72b8c5faec7 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/ShareActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/ShareActivity.kt @@ -93,6 +93,11 @@ class ShareActivity : FileActivity(), ShareFragmentListener { setContentView(binding.root) setupToolbar(binding.root) setupFileHeader(binding) + if (file != null && account != null) { + supportFragmentManager.transaction { + replace(R.id.members_fragment_container, GraphShareFragment.newInstance(file, account!!.name), TAG_GRAPH_SHARE_FRAGMENT) + } + } } private fun setupLegacyLayout(savedInstanceState: Bundle?) { @@ -355,7 +360,8 @@ class ShareActivity : FileActivity(), ShareFragmentListener { override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean = if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { - if (findViewById(R.id.owncloud_app_bar).hasFocus()) { + val appBar = findViewById(R.id.owncloud_app_bar) + if (appBar?.hasFocus() == true) { findViewById(R.id.share_fragment_container).requestFocus() } true @@ -365,6 +371,7 @@ class ShareActivity : FileActivity(), ShareFragmentListener { companion object { const val TAG_SHARE_FRAGMENT = "SHARE_FRAGMENT" + const val TAG_GRAPH_SHARE_FRAGMENT = "GRAPH_SHARE_FRAGMENT" const val TAG_SEARCH_FRAGMENT = "SEARCH_USER_AND_GROUPS_FRAGMENT" const val TAG_EDIT_SHARE_FRAGMENT = "EDIT_SHARE_FRAGMENT" const val TAG_PUBLIC_SHARE_DIALOG_FRAGMENT = "PUBLIC_SHARE_DIALOG_FRAGMENT" diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/AddMemberFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/AddMemberFragment.kt index 755b9c402c9..c4d6e530c55 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/AddMemberFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/AddMemberFragment.kt @@ -36,7 +36,7 @@ import com.owncloud.android.domain.members.model.OCMember import com.owncloud.android.domain.members.model.OCMemberType import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.model.SpaceMember +import com.owncloud.android.domain.sharing.shares.model.MemberPermission import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.showErrorInSnackbar import com.owncloud.android.presentation.common.UIResult @@ -91,7 +91,7 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi roles = requireArguments().getParcelableArrayList(ARG_ROLES) ?: arrayListOf() if (editMode) { - val selectedMember = requireArguments().getParcelable(ARG_SELECTED_MEMBER) + val selectedMember = requireArguments().getParcelable(ARG_SELECTED_MEMBER) selectedMember?.let { bindEditMode(it, roles) } @@ -141,7 +141,7 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi } private fun subscribeToViewModels() { - val spaceMembers = requireArguments().getParcelableArrayList(ARG_SPACE_MEMBERS) ?: arrayListOf() + val spaceMembers = requireArguments().getParcelableArrayList(ARG_SPACE_MEMBERS) ?: arrayListOf() searchMinLength = spaceMembersViewModel.capabilities?.filesSharingSearchMinLength ?: DEFAULT_SEARCH_MIN_LENGTH collectLatestLifecycleFlow(spaceMembersViewModel.members) { uiState -> @@ -299,7 +299,7 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi } } - private fun bindEditMode(member: SpaceMember, roles: List) { + private fun bindEditMode(member: MemberPermission, roles: List) { selectedMemberId = member.id spaceMembersViewModel.onMemberSelected(member) @@ -325,10 +325,10 @@ class AddMemberFragment: Fragment(), SearchMembersAdapter.SearchMembersAdapterLi fun newInstance( accountName: String, currentSpace: OCSpace, - spaceMembers: List, + spaceMembers: List, roles: List, editMode: Boolean, - selectedMember: SpaceMember? + selectedMember: MemberPermission? ): AddMemberFragment { val args = Bundle().apply { putString(ARG_ACCOUNT_NAME, accountName) 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 cffeda728d2..4f447463562 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 @@ -31,7 +31,7 @@ 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 +import com.owncloud.android.domain.sharing.shares.model.MemberPermission import com.owncloud.android.extensions.adaptInfiniteEdges import com.owncloud.android.presentation.common.ShareSheetHelper import com.owncloud.android.presentation.spaces.links.AddPublicLinkFragment @@ -95,7 +95,13 @@ class SpaceMembersActivity: FileActivity(), SpaceMembersFragment.SpaceMemberFrag super.onOptionsItemSelected(item) } - override fun addMember(space: OCSpace, spaceMembers: List, roles: List, editMode: Boolean, selectedMember: SpaceMember?) { + override fun addMember( + space: OCSpace, + spaceMembers: List, + roles: List, + editMode: Boolean, + selectedMember: MemberPermission? + ) { val addMemberFragment = AddMemberFragment.newInstance(account.name, space, spaceMembers, roles, editMode, selectedMember) val transaction = supportFragmentManager.beginTransaction() transaction.apply { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersAdapter.kt index b7190b5c33d..021b3b73bf0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersAdapter.kt @@ -30,7 +30,7 @@ import com.owncloud.android.R import com.owncloud.android.databinding.MemberItemBinding import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.roles.model.OCRoleType -import com.owncloud.android.domain.spaces.model.SpaceMember +import com.owncloud.android.domain.sharing.shares.model.MemberPermission import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.PreferenceUtils @@ -39,7 +39,7 @@ class SpaceMembersAdapter( private val accountId: String? ): RecyclerView.Adapter() { - private var members: List = emptyList() + private var members: List = emptyList() private var rolesMap: Map = emptyMap() private var canRemoveMembers = false private var canEditMembers = false @@ -105,7 +105,7 @@ class SpaceMembersAdapter( override fun getItemCount(): Int = members.size fun setSpaceMembers( - spaceMembers: List, + spaceMembers: List, roles: List, canRemoveMembers: Boolean, canEditMembers: Boolean, @@ -118,7 +118,7 @@ class SpaceMembersAdapter( this.canRemoveMembers = canRemoveMembers this.canEditMembers = canEditMembers this.rolesMap = roles.associate { it.id to it.displayName } - val listOfMembersFiltered = spaceMembers.sortedWith(compareByDescending { + val listOfMembersFiltered = spaceMembers.sortedWith(compareByDescending { member -> roles.indexOfFirst { it.id in member.roles } }.thenBy { member -> member.displayName }) val diffCallback = SpaceMembersDiffUtil(this.members, listOfMembersFiltered, numberOfManagersChanged, hasUserPermissionsChanged) val diffResult = DiffUtil.calculateDiff(diffCallback) @@ -132,8 +132,8 @@ class SpaceMembersAdapter( } interface SpaceMembersAdapterListener { - fun onRemoveMember(spaceMember: SpaceMember) - fun onEditMember(spaceMember: SpaceMember) + fun onRemoveMember(spaceMember: MemberPermission) + fun onEditMember(spaceMember: MemberPermission) } companion object { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersDiffUtil.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersDiffUtil.kt index 637319c56a1..581c8864811 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersDiffUtil.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersDiffUtil.kt @@ -21,11 +21,11 @@ package com.owncloud.android.presentation.spaces.members import androidx.recyclerview.widget.DiffUtil -import com.owncloud.android.domain.spaces.model.SpaceMember +import com.owncloud.android.domain.sharing.shares.model.MemberPermission class SpaceMembersDiffUtil( - private val oldList: List, - private val newList: List, + private val oldList: List, + private val newList: List, private val hasNumberOfManagersChanged: Boolean, private val hasUserPermissionsChanged: Boolean ) : DiffUtil.Callback() { 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 66da33336ca..97ce41306cb 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 @@ -36,7 +36,7 @@ import com.owncloud.android.domain.links.model.OCLink import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.roles.model.OCRoleType import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.model.SpaceMember +import com.owncloud.android.domain.sharing.shares.model.MemberPermission import com.owncloud.android.extensions.avoidScreenshotsIfNeeded import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.extensions.showAlertDialog @@ -77,7 +77,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter private var roles: List = emptyList() private var addMemberRoles: List = emptyList() - private var spaceMembers: List = emptyList() + private var spaceMembers: List = emptyList() private var spaceLinks: List = emptyList() private var listener: SpaceMemberFragmentListener? = null private var canRemoveMembersAndLinks = false @@ -172,7 +172,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter outState.putBoolean(CAN_READ_MEMBERS, canReadMembersAndLinks) } - override fun onRemoveMember(spaceMember: SpaceMember) { + override fun onRemoveMember(spaceMember: MemberPermission) { AlertDialog.Builder(requireContext()) .setMessage(getString(R.string.members_remove_dialog_message, spaceMember.displayName)) .setPositiveButton(getString(R.string.common_yes)) { _, _ -> spaceMembersViewModel.removeMember(spaceMember.id) } @@ -181,7 +181,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter .avoidScreenshotsIfNeeded() } - override fun onEditMember(spaceMember: SpaceMember) { + override fun onEditMember(spaceMember: MemberPermission) { spaceMembersViewModel.resetViewModel() val currentSpace = requireArguments().getParcelable(ARG_CURRENT_SPACE) ?: return listener?.addMember( @@ -458,7 +458,7 @@ class SpaceMembersFragment : Fragment(), SpaceMembersAdapter.SpaceMembersAdapter } interface SpaceMemberFragmentListener { - fun addMember(space: OCSpace, spaceMembers: List, roles: List, editMode: Boolean, selectedMember: SpaceMember?) + fun addMember(space: OCSpace, spaceMembers: List, roles: List, editMode: Boolean, selectedMember: MemberPermission?) fun addPublicLink(space: OCSpace, editMode: Boolean, selectedPublicLink: OCLink?) fun copyOrSendPublicLink(publicLinkUrl: String, spaceName: String) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersViewModel.kt index 87c3129b238..978dcf0bb72 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SpaceMembersViewModel.kt @@ -31,12 +31,12 @@ import com.owncloud.android.domain.members.usecases.EditMemberUseCase import com.owncloud.android.domain.members.usecases.RemoveMemberUseCase import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import com.owncloud.android.domain.spaces.usecases.GetSpaceMembersUseCase import com.owncloud.android.domain.roles.usecases.GetRolesAsyncUseCase import com.owncloud.android.domain.spaces.usecases.GetSpacePermissionsAsyncUseCase import com.owncloud.android.domain.members.usecases.SearchMembersUseCase -import com.owncloud.android.domain.spaces.model.SpaceMember +import com.owncloud.android.domain.sharing.shares.model.MemberPermission import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult import com.owncloud.android.extensions.toOCMember @@ -67,8 +67,8 @@ class SpaceMembersViewModel( private val _roles = MutableStateFlow>>?>(null) val roles: StateFlow>>?> = _roles - private val _spaceMembers = MutableStateFlow>?>(null) - val spaceMembers: StateFlow>?> = _spaceMembers + private val _spaceMembers = MutableStateFlow>?>(null) + val spaceMembers: StateFlow>?> = _spaceMembers private val _spacePermissions = MutableStateFlow>>?>(null) val spacePermissions: StateFlow>>?> = _spacePermissions @@ -146,7 +146,7 @@ class SpaceMembersViewModel( _addMemberUIState.value = AddMemberUIState(selectedMember = member) } - fun onMemberSelected(member: SpaceMember) { + fun onMemberSelected(member: MemberPermission) { onMemberSelected(member.toOCMember()) } diff --git a/owncloudApp/src/main/res/layout/members_fragment.xml b/owncloudApp/src/main/res/layout/members_fragment.xml index 858cfe858ac..ea134cf8381 100644 --- a/owncloudApp/src/main/res/layout/members_fragment.xml +++ b/owncloudApp/src/main/res/layout/members_fragment.xml @@ -83,6 +83,18 @@ android:layout_height="wrap_content" android:scrollbars="vertical"/> + + Share Users and Groups No data shared with users yet + Shares could not be refreshed Public links Create link share Edit link share @@ -506,6 +507,7 @@ Download/View/Upload Upload Only (File Drop) Share with + Share with people Share with %1$s Search @@ -623,6 +625,8 @@ Release note icon Minor bugfixes Some bugs were fixed to improve experience in the app + New sharing implementation for Infinite Scale + New interface and operations have been added to share files and folders on Infinite Scale accounts Open in %1$s (web) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteGraphSharesForFileOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteGraphSharesForFileOperation.kt new file mode 100644 index 00000000000..bda20f0ca78 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/GetRemoteGraphSharesForFileOperation.kt @@ -0,0 +1,82 @@ +/** + * 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.shares + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod +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.PermissionsResponse +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import timber.log.Timber +import java.net.URL + +class GetRemoteGraphSharesForFileOperation( + private val spaceId: String, + private val itemId: String, +) : RemoteOperation() { + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult + try { + val requestUri = client.baseUri.buildUpon().apply { + appendEncodedPath(GRAPH_API_DRIVES_PATH) + appendEncodedPath(spaceId) + appendEncodedPath(ITEMS_PATH) + appendEncodedPath(itemId) + appendEncodedPath(PERMISSIONS_ENDPOINT) + build() + } + val getMethod = GetMethod(URL(requestUri.toString())) + + val status = client.executeHttpMethod(getMethod) + + val response = getMethod.getResponseBodyAsString() + + if (status == HttpConstants.HTTP_OK) { + Timber.d("Successful response: $response") + + val moshi: Moshi = Moshi.Builder().build() + val adapter: JsonAdapter = moshi.adapter(PermissionsResponse::class.java) + + result = RemoteOperationResult(ResultCode.OK) + result.data = getMethod.getResponseBodyAsString().let { adapter.fromJson(it) } + + Timber.d("Get graph shares completed and parsed to ${result.data}") + } else { + result = RemoteOperationResult(getMethod) + Timber.e("Failed response while getting graph shares; status code: $status, response: $response") + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Timber.e(e, "Exception while getting graph shares") + } + return result + } + + companion object { + private const val GRAPH_API_DRIVES_PATH = "graph/v1beta1/drives/" + private const val ITEMS_PATH = "items" + private const val PERMISSIONS_ENDPOINT = "permissions" + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/ShareService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/ShareService.kt index 53458c59186..b81cb8fcab5 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/ShareService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/ShareService.kt @@ -30,6 +30,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.Service import com.owncloud.android.lib.resources.shares.ShareResponse import com.owncloud.android.lib.resources.shares.ShareType +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse interface ShareService : Service { fun getShares( @@ -38,6 +39,8 @@ interface ShareService : Service { subfiles: Boolean ): RemoteOperationResult + fun getGraphShares(spaceId: String, itemId: String): RemoteOperationResult + fun insertShare( remoteFilePath: String, shareType: ShareType, diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/implementation/OCShareService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/implementation/OCShareService.kt index af2845293b4..fa9fc9ebf71 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/implementation/OCShareService.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/shares/services/implementation/OCShareService.kt @@ -29,12 +29,14 @@ package com.owncloud.android.lib.resources.shares.services.implementation import com.owncloud.android.lib.common.OwnCloudClient import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.shares.CreateRemoteShareOperation +import com.owncloud.android.lib.resources.shares.GetRemoteGraphSharesForFileOperation import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation import com.owncloud.android.lib.resources.shares.RemoveRemoteShareOperation import com.owncloud.android.lib.resources.shares.ShareResponse import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.lib.resources.shares.UpdateRemoteShareOperation import com.owncloud.android.lib.resources.shares.services.ShareService +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse class OCShareService(override val client: OwnCloudClient) : ShareService { override fun getShares( @@ -47,6 +49,9 @@ class OCShareService(override val client: OwnCloudClient) : ShareService { subfiles ).execute(client) + override fun getGraphShares(spaceId: String, itemId: String): RemoteOperationResult = + GetRemoteGraphSharesForFileOperation(spaceId, itemId).execute(client) + override fun insertShare( remoteFilePath: String, shareType: ShareType, diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/GetRemoteSpacePermissionsOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/GetRemoteSpacePermissionsOperation.kt index fa41aea6d32..dbdaf5cbabb 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/GetRemoteSpacePermissionsOperation.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/GetRemoteSpacePermissionsOperation.kt @@ -26,7 +26,7 @@ import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod 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.SpacePermissionsResponse +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import timber.log.Timber @@ -34,9 +34,9 @@ import java.net.URL class GetRemoteSpacePermissionsOperation( private val spaceId: String -): RemoteOperation() { - override fun run(client: OwnCloudClient): RemoteOperationResult { - var result: RemoteOperationResult +): RemoteOperation() { + override fun run(client: OwnCloudClient): RemoteOperationResult { + var result: RemoteOperationResult try { val requestUri = client.baseUri.buildUpon().apply { appendEncodedPath(GRAPH_API_SPACES_PATH) @@ -54,7 +54,7 @@ class GetRemoteSpacePermissionsOperation( Timber.d("Successful response: $response") val moshi: Moshi = Moshi.Builder().build() - val adapter: JsonAdapter = moshi.adapter(SpacePermissionsResponse::class.java) + val adapter: JsonAdapter = moshi.adapter(PermissionsResponse::class.java) result = RemoteOperationResult(ResultCode.OK) result.data = getMethod.getResponseBodyAsString().let { adapter.fromJson(it) } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/responses/SpacesResponse.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/responses/SpacesResponse.kt index 4f351f18a0a..d03b80af775 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/responses/SpacesResponse.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/spaces/responses/SpacesResponse.kt @@ -66,7 +66,7 @@ data class RootResponse( val id: String, val webDavUrl: String, val deleted: DeleteResponse?, - val permissions: List? + val permissions: List? ) @JsonClass(generateAdapter = true) @@ -103,7 +103,7 @@ data class SpecialFolderResponse( ) @JsonClass(generateAdapter = true) -data class PermissionsResponse( +data class MemberPermissionResponse( val id: String?, val expirationDateTime: String?, @@ -130,13 +130,13 @@ data class GroupResponse( ) @JsonClass(generateAdapter = true) -data class SpacePermissionsResponse( +data class PermissionsResponse( @Json(name = "@libre.graph.permissions.actions.allowedValues") val actions: List, @Json(name = "@libre.graph.permissions.roles.allowedValues") val roles: List, @Json(name = "value") - val members: List + val members: List? ) @JsonClass(generateAdapter = true) 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 ea71a2173d9..af65670bac7 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 @@ -31,7 +31,7 @@ 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.SpacePermissionsResponse +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse class OCSpacesService(override val client: OwnCloudClient) : SpacesService { @@ -41,7 +41,7 @@ class OCSpacesService(override val client: OwnCloudClient) : SpacesService { override fun createSpace(spaceName: String, spaceSubtitle: String, spaceQuota: Long): RemoteOperationResult = CreateRemoteSpaceOperation(spaceName, spaceSubtitle, spaceQuota).execute(client) - override fun getSpacePermissions(spaceId: String): RemoteOperationResult = + override fun getSpacePermissions(spaceId: String): RemoteOperationResult = GetRemoteSpacePermissionsOperation(spaceId).execute(client) override fun editSpace(spaceId: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long?): RemoteOperationResult = 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 a8eb59e798f..2ecb765c67e 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 @@ -24,13 +24,13 @@ package com.owncloud.android.lib.resources.spaces.services import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.Service -import com.owncloud.android.lib.resources.spaces.responses.SpacePermissionsResponse +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse interface SpacesService : Service { fun getSpaces(): RemoteOperationResult> fun createSpace(spaceName: String, spaceSubtitle: String, spaceQuota: Long): RemoteOperationResult - fun getSpacePermissions(spaceId: String): 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 diff --git a/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/RemoteShareDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/RemoteShareDataSource.kt index 58834dcd13a..e193d8c3f92 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/RemoteShareDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/RemoteShareDataSource.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. + * @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, @@ -21,6 +23,7 @@ package com.owncloud.android.data.sharing.shares.datasources import com.owncloud.android.domain.sharing.shares.model.OCShare import com.owncloud.android.domain.sharing.shares.model.ShareType +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import com.owncloud.android.lib.resources.shares.RemoteShare.Companion.INIT_EXPIRATION_DATE_IN_MILLIS interface RemoteShareDataSource { @@ -31,6 +34,12 @@ interface RemoteShareDataSource { accountName: String ): List + fun getGraphShares( + accountName: String, + spaceId: String, + itemId: String + ): OCPermissions + fun insert( remoteFilePath: String, shareType: ShareType, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSource.kt index 175cc46491f..427ba766347 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSource.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. + * @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, @@ -23,8 +25,14 @@ import com.owncloud.android.data.ClientManager import com.owncloud.android.data.executeRemoteOperation import com.owncloud.android.data.sharing.shares.datasources.RemoteShareDataSource import com.owncloud.android.data.sharing.shares.datasources.mapper.RemoteShareMapper +import com.owncloud.android.domain.links.model.OCLink +import com.owncloud.android.domain.links.model.OCLinkType +import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.sharing.shares.model.OCShare import com.owncloud.android.domain.sharing.shares.model.ShareType +import com.owncloud.android.domain.sharing.shares.model.MemberPermission +import com.owncloud.android.domain.sharing.shares.model.OCPermissions +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse class OCRemoteShareDataSource( private val clientManager: ClientManager, @@ -48,6 +56,17 @@ class OCRemoteShareDataSource( } } + override fun getGraphShares( + accountName: String, + spaceId: String, + itemId: String + ): OCPermissions { + val response = executeRemoteOperation { + clientManager.getShareService(accountName).getGraphShares(spaceId, itemId) + } + return response.toModel() + } + override fun insert( remoteFilePath: String, shareType: ShareType, @@ -103,4 +122,40 @@ class OCRemoteShareDataSource( clientManager.getShareService(accountName).deleteShare(remoteId) } } + + companion object { + fun PermissionsResponse.toModel(): OCPermissions { + val membersResponse = members.orEmpty() + return OCPermissions( + roles = roles.map { spaceRoleResponse -> + OCRole( + id = spaceRoleResponse.id, + displayName = spaceRoleResponse.displayName, + description = spaceRoleResponse.description + ) + }, + members = membersResponse.filter { it.grantedToV2 != null }.map { spaceMemberResponse -> + MemberPermission( + id = spaceMemberResponse.id ?: "", + expirationDateTime = spaceMemberResponse.expirationDateTime, + displayName = spaceMemberResponse.grantedToV2?.user?.displayName + ?: spaceMemberResponse.grantedToV2?.group?.displayName ?: "", + roles = spaceMemberResponse.roles ?: emptyList(), + isGroup = spaceMemberResponse.grantedToV2?.group != null + ) + }, + links = membersResponse.filter { it.grantedToV2 == null }.map { spaceLinkResponse -> + OCLink( + id = spaceLinkResponse.id.orEmpty(), + createdDateTime = spaceLinkResponse.createdDateTime.orEmpty(), + expirationDateTime = spaceLinkResponse.expirationDateTime, + hasPassword = spaceLinkResponse.hasPassword ?: false, + displayName = spaceLinkResponse.link?.displayName.orEmpty(), + type = OCLinkType.parseFromString(spaceLinkResponse.link?.type.orEmpty()), + webUrl = spaceLinkResponse.link?.webUrl.orEmpty() + ) + } + ) + } + } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepository.kt index 1bcf69c74f6..0f33d4f3dab 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepository.kt @@ -3,8 +3,9 @@ * * @author David González Verdugo * @author Juan Carlos Garrote Gascón + * @author Jorge Aguado Recio * - * Copyright (C) 2022 ownCloud GmbH. + * 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, @@ -27,6 +28,7 @@ import com.owncloud.android.data.sharing.shares.datasources.RemoteShareDataSourc import com.owncloud.android.domain.sharing.shares.ShareRepository import com.owncloud.android.domain.sharing.shares.model.OCShare import com.owncloud.android.domain.sharing.shares.model.ShareType +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import com.owncloud.android.lib.resources.shares.RemoteShare class OCShareRepository( @@ -118,6 +120,9 @@ class OCShareRepository( override fun getShareAsLiveData(remoteId: String): LiveData = localShareDataSource.getShareAsLiveData(remoteId) + override fun getGraphShares(accountName: String, spaceId: String, itemId: String): OCPermissions = + remoteShareDataSource.getGraphShares(accountName, spaceId, itemId) + override fun refreshSharesFromNetwork( filePath: String, accountName: String 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 776e7ca5d1d..c89dfb44863 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 @@ -21,12 +21,12 @@ package com.owncloud.android.data.spaces.datasources import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.OCPermissions interface RemoteSpacesDataSource { fun refreshSpacesForAccount(accountName: String, userId: String, userGroups: List): List fun createSpace(accountName: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long): OCSpace - fun getSpaceMembers(accountName: String, spaceId: String): SpaceMembers + fun getSpaceMembers(accountName: String, spaceId: String): OCPermissions 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 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 6029b2cc9fb..c5751a11c0e 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 @@ -30,8 +30,8 @@ import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.SpaceDeleted import com.owncloud.android.domain.spaces.model.SpaceFile -import com.owncloud.android.domain.spaces.model.SpaceMember -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.MemberPermission +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import com.owncloud.android.domain.spaces.model.SpaceOwner import com.owncloud.android.domain.spaces.model.SpaceQuota import com.owncloud.android.domain.spaces.model.SpaceRoot @@ -39,7 +39,7 @@ import com.owncloud.android.domain.spaces.model.SpaceSpecial import com.owncloud.android.domain.spaces.model.SpaceSpecialFolder import com.owncloud.android.domain.spaces.model.SpaceUser import com.owncloud.android.lib.resources.spaces.responses.RootResponse -import com.owncloud.android.lib.resources.spaces.responses.SpacePermissionsResponse +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse class OCRemoteSpacesDataSource( @@ -60,7 +60,7 @@ class OCRemoteSpacesDataSource( return spaceResponse.toModel(accountName) } - override fun getSpaceMembers(accountName: String, spaceId: String): SpaceMembers { + override fun getSpaceMembers(accountName: String, spaceId: String): OCPermissions { val spacePermissionsResponse = executeRemoteOperation { clientManager.getSpacesService(accountName).getSpacePermissions(spaceId) } @@ -196,8 +196,9 @@ class OCRemoteSpacesDataSource( ) @VisibleForTesting - fun SpacePermissionsResponse.toModel(): SpaceMembers = - SpaceMembers( + fun PermissionsResponse.toModel(): OCPermissions { + val membersResponse = members.orEmpty() + return OCPermissions( roles = roles.map { spaceRoleResponse -> OCRole( id = spaceRoleResponse.id, @@ -205,16 +206,17 @@ class OCRemoteSpacesDataSource( description = spaceRoleResponse.description ) }, - members = members.filter { it.grantedToV2 != null }.map { spaceMemberResponse -> - SpaceMember ( + members = membersResponse.filter { it.grantedToV2 != null }.map { spaceMemberResponse -> + MemberPermission( id = spaceMemberResponse.id ?: "", expirationDateTime = spaceMemberResponse.expirationDateTime, displayName = spaceMemberResponse.grantedToV2?.user?.displayName ?: spaceMemberResponse.grantedToV2?.group?.displayName ?: "", - roles = spaceMemberResponse.roles ?: emptyList() + roles = spaceMemberResponse.roles ?: emptyList(), + isGroup = spaceMemberResponse.grantedToV2?.group != null ) }, - links = members.filter { it.grantedToV2 == null }.map { spaceLinkResponse -> + links = membersResponse.filter { it.grantedToV2 == null }.map { spaceLinkResponse -> OCLink( id = spaceLinkResponse.id.orEmpty(), createdDateTime = spaceLinkResponse.createdDateTime.orEmpty(), @@ -226,6 +228,7 @@ class OCRemoteSpacesDataSource( ) } ) + } private fun getRoleForUser(root: RootResponse, userId: String, userGroups: List): String? { 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 e7553caed01..a41ec4ca0e0 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 @@ -28,7 +28,7 @@ import com.owncloud.android.data.spaces.datasources.RemoteSpacesDataSource import com.owncloud.android.data.user.datasources.LocalUserDataSource import com.owncloud.android.domain.spaces.SpacesRepository import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import com.owncloud.android.domain.user.model.UserQuotaState import com.owncloud.android.domain.user.model.UserQuota @@ -75,7 +75,7 @@ class OCSpacesRepository( override fun getSpaceByIdForAccount(spaceId: String?, accountName: String): OCSpace? = localSpacesDataSource.getSpaceByIdForAccount(spaceId = spaceId, accountName = accountName) - override fun getSpaceMembers(accountName: String, spaceId: String): SpaceMembers = + override fun getSpaceMembers(accountName: String, spaceId: String): OCPermissions = remoteSpacesDataSource.getSpaceMembers(accountName, spaceId) override fun getSpacePermissions(accountName: String, spaceId: String): List = diff --git a/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSourceTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSourceTest.kt index 44334c0b58f..a5ed7c3c4d1 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSourceTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/datasources/implementation/OCRemoteShareDataSourceTest.kt @@ -4,8 +4,9 @@ * @author David González Verdugo * @author Jesús Recio * @author Aitor Ballesteros Pavón + * @author Jorge Aguado Recio * - * Copyright (C) 2023 ownCloud GmbH. + * 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, @@ -24,6 +25,7 @@ package com.owncloud.android.data.sharing.shares.datasources.implementation import com.owncloud.android.data.ClientManager import com.owncloud.android.data.sharing.shares.datasources.mapper.RemoteShareMapper +import com.owncloud.android.data.spaces.datasources.implementation.OCRemoteSpacesDataSource.Companion.toModel import com.owncloud.android.domain.exceptions.ShareForbiddenException import com.owncloud.android.domain.exceptions.ShareNotFoundException import com.owncloud.android.domain.sharing.shares.model.ShareType @@ -31,7 +33,10 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.shares.ShareResponse import com.owncloud.android.lib.resources.shares.services.implementation.OCShareService import com.owncloud.android.testutil.OC_ACCOUNT_NAME +import com.owncloud.android.testutil.OC_FILE import com.owncloud.android.testutil.OC_SHARE +import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE +import com.owncloud.android.testutil.SPACE_PERMISSIONS_RESPONSE import com.owncloud.android.utils.createRemoteOperationResultMock import io.mockk.every import io.mockk.mockk @@ -386,6 +391,23 @@ class OCRemoteShareDataSourceTest { } } + @Test + fun `getGraphShares returns a OCPermissions`() { + val getSpaceMembersResult = createRemoteOperationResultMock(SPACE_PERMISSIONS_RESPONSE, isSuccess = true) + + every { + ocShareService.getGraphShares(OC_SPACE_PROJECT_WITH_IMAGE.id, OC_FILE.remoteId.orEmpty()) + } returns getSpaceMembersResult + + val ocisShares = ocRemoteShareDataSource.getGraphShares(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, OC_FILE.remoteId.orEmpty()) + assertEquals(SPACE_PERMISSIONS_RESPONSE.toModel(), ocisShares) + + verify(exactly = 1) { + clientManager.getShareService(OC_ACCOUNT_NAME) + ocShareService.getGraphShares(OC_SPACE_PROJECT_WITH_IMAGE.id, OC_FILE.remoteId.orEmpty()) + } + } + @Test(expected = ShareNotFoundException::class) fun `insert throws a ShareNotFoundException when share is not found`() { insertShareOperationWithError(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepositoryTest.kt b/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepositoryTest.kt index 50fdb95c897..d5c999a9e65 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepositoryTest.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/sharing/shares/repository/OCShareRepositoryTest.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. + * @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, @@ -28,10 +30,13 @@ import com.owncloud.android.domain.sharing.shares.model.OCShare import com.owncloud.android.domain.sharing.shares.model.ShareType import com.owncloud.android.lib.resources.shares.RemoteShare import com.owncloud.android.testutil.OC_ACCOUNT_NAME +import com.owncloud.android.testutil.OC_FILE import com.owncloud.android.testutil.OC_PRIVATE_SHARE import com.owncloud.android.testutil.OC_PUBLIC_SHARE import com.owncloud.android.testutil.OC_SHARE import com.owncloud.android.testutil.OC_SHAREE +import com.owncloud.android.testutil.OC_SPACE_PROJECT_WITH_IMAGE +import com.owncloud.android.testutil.SPACE_MEMBERS import com.owncloud.android.testutil.livedata.getLastEmittedValue import io.mockk.every import io.mockk.mockk @@ -237,6 +242,20 @@ class OCShareRepositoryTest { } } + @Test + fun `getGraphShares returns a OCPermissions`() { + every { + remoteShareDataSource.getGraphShares(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, OC_FILE.remoteId.orEmpty()) + } returns SPACE_MEMBERS + + val sharesResult = ocShareRepository.getGraphShares(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, OC_FILE.remoteId.orEmpty()) + assertEquals(SPACE_MEMBERS, sharesResult) + + verify(exactly = 1) { + remoteShareDataSource.getGraphShares(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id, OC_FILE.remoteId.orEmpty()) + } + } + @Test fun `refreshSharesFromNetwork refreshes shares correctly when the list of shares received is not empty`() { every { 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 98acddf19b6..7b39bf1e0a8 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 @@ -102,7 +102,7 @@ class OCRemoteSpacesDataSourceTest { } @Test - fun `getSpaceMembers returns a SpaceMembers for a project space`() { + fun `getSpaceMembers returns a OCPermissions for a project space`() { val getSpaceMembersResult = createRemoteOperationResultMock(SPACE_PERMISSIONS_RESPONSE, isSuccess = true) every { 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 6be5378fa2b..df1a96d7565 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 @@ -252,7 +252,7 @@ class OCSpacesRepositoryTest { } @Test - fun `getSpaceMembers returns a SpaceMembers`() { + fun `getSpaceMembers returns a OCPermissions`() { every { remoteSpacesDataSource.getSpaceMembers(OC_ACCOUNT_NAME, OC_SPACE_PROJECT_WITH_IMAGE.id) } returns SPACE_MEMBERS diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/ShareRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/ShareRepository.kt index cbe4be07a07..4f8f4acfae5 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/ShareRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/ShareRepository.kt @@ -3,8 +3,9 @@ * * @author David González Verdugo * @author Juan Carlos Garrote Gascón + * @author Jorge Aguado Recio * - * Copyright (C) 2022 ownCloud GmbH. + * 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, @@ -24,6 +25,7 @@ package com.owncloud.android.domain.sharing.shares import androidx.lifecycle.LiveData import com.owncloud.android.domain.sharing.shares.model.OCShare import com.owncloud.android.domain.sharing.shares.model.ShareType +import com.owncloud.android.domain.sharing.shares.model.OCPermissions interface ShareRepository { @@ -75,6 +77,8 @@ interface ShareRepository { fun getShareAsLiveData(remoteId: String): LiveData + fun getGraphShares(accountName: String, spaceId: String, itemId: String): OCPermissions + fun refreshSharesFromNetwork(filePath: String, accountName: String) fun deleteShare(remoteId: String, accountName: String) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/model/OCPermissions.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/model/OCPermissions.kt new file mode 100644 index 00000000000..55f43a5708b --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/model/OCPermissions.kt @@ -0,0 +1,42 @@ +/** + * 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.sharing.shares.model + +import android.os.Parcelable +import com.owncloud.android.domain.links.model.OCLink +import com.owncloud.android.domain.roles.model.OCRole +import kotlinx.parcelize.Parcelize + +@Parcelize +data class OCPermissions( + val roles: List, + val members: List, + val links: List +) : Parcelable + +@Parcelize +data class MemberPermission( + val id: String, + val expirationDateTime: String?, + val displayName: String, + val roles: List, + val isGroup: Boolean = false +) : Parcelable diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/usecases/GetGraphSharesAsyncUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/usecases/GetGraphSharesAsyncUseCase.kt new file mode 100644 index 00000000000..7e41b64f7ce --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/sharing/shares/usecases/GetGraphSharesAsyncUseCase.kt @@ -0,0 +1,35 @@ +/** + * 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.sharing.shares.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.sharing.shares.ShareRepository +import com.owncloud.android.domain.sharing.shares.model.OCPermissions + +class GetGraphSharesAsyncUseCase( + private val shareRepository: ShareRepository +) : BaseUseCaseWithResult() { + + override fun run(params: Params) = + shareRepository.getGraphShares(params.accountName, params.spaceId, params.itemId) + + data class Params(val accountName: String, val spaceId: String, val itemId: String) +} 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 c3a4855ad40..a0bdffe899c 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 @@ -23,7 +23,7 @@ package com.owncloud.android.domain.spaces import com.owncloud.android.domain.spaces.model.OCSpace -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import kotlinx.coroutines.flow.Flow interface SpacesRepository { @@ -34,7 +34,7 @@ interface SpacesRepository { fun getPersonalAndProjectSpacesForAccount(accountName: String): List fun getSpaceWithSpecialsByIdForAccount(spaceId: String?, accountName: String): OCSpace fun getSpaceByIdForAccount(spaceId: String?, accountName: String): OCSpace? - fun getSpaceMembers(accountName: String, spaceId: String): SpaceMembers + fun getSpaceMembers(accountName: String, spaceId: String): OCPermissions fun getSpacePermissions(accountName: String, spaceId: String): List fun getWebDavUrlForSpace(accountName: String, spaceId: String?): String? fun createSpace(accountName: String, spaceName: String, spaceSubtitle: String, spaceQuota: Long) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt index 1b0811718fc..2220c7bae79 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/model/OCSpace.kt @@ -23,8 +23,6 @@ package com.owncloud.android.domain.spaces.model import android.os.Parcelable -import com.owncloud.android.domain.links.model.OCLink -import com.owncloud.android.domain.roles.model.OCRole import kotlinx.parcelize.Parcelize import kotlin.math.roundToLong @@ -123,18 +121,3 @@ data class SpaceFile( data class SpaceSpecialFolder( val name: String ) : Parcelable - -@Parcelize -data class SpaceMembers( - val roles: List, - val members: List, - val links: List -) : Parcelable - -@Parcelize -data class SpaceMember( - val id: String, - val expirationDateTime: String?, - val displayName: String, - val roles: List -) : Parcelable diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceMembersUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceMembersUseCase.kt index ed40fed5f0d..d175c1b4d2c 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceMembersUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/spaces/usecases/GetSpaceMembersUseCase.kt @@ -22,11 +22,11 @@ package com.owncloud.android.domain.spaces.usecases import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.spaces.SpacesRepository -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.OCPermissions class GetSpaceMembersUseCase( private val spacesRepository: SpacesRepository -): BaseUseCaseWithResult() { +): BaseUseCaseWithResult() { override fun run(params: Params) = spacesRepository.getSpaceMembers(params.accountName, params.spaceId) diff --git a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt index f03d5cc97dc..935fe24aada 100644 --- a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt +++ b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCSpace.kt @@ -33,8 +33,8 @@ import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.domain.spaces.model.OCSpace.Companion.SPACE_ID_SHARES import com.owncloud.android.domain.spaces.model.SpaceDeleted import com.owncloud.android.domain.spaces.model.SpaceFile -import com.owncloud.android.domain.spaces.model.SpaceMember -import com.owncloud.android.domain.spaces.model.SpaceMembers +import com.owncloud.android.domain.sharing.shares.model.MemberPermission +import com.owncloud.android.domain.sharing.shares.model.OCPermissions import com.owncloud.android.domain.spaces.model.SpaceOwner import com.owncloud.android.domain.spaces.model.SpaceQuota import com.owncloud.android.domain.spaces.model.SpaceRoot @@ -43,10 +43,10 @@ import com.owncloud.android.domain.spaces.model.SpaceSpecialFolder import com.owncloud.android.domain.spaces.model.SpaceUser import com.owncloud.android.lib.resources.roles.responses.RoleResponse import com.owncloud.android.lib.resources.spaces.responses.GrantedToV2Response -import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse +import com.owncloud.android.lib.resources.spaces.responses.MemberPermissionResponse import com.owncloud.android.lib.resources.spaces.responses.QuotaResponse import com.owncloud.android.lib.resources.spaces.responses.RootResponse -import com.owncloud.android.lib.resources.spaces.responses.SpacePermissionsResponse +import com.owncloud.android.lib.resources.spaces.responses.PermissionsResponse import com.owncloud.android.lib.resources.spaces.responses.SpaceResponse import com.owncloud.android.lib.resources.spaces.responses.UserResponse @@ -320,7 +320,7 @@ val SPACE_RESPONSE = webDavUrl = "https://server.url/dav/spaces/8871f4f3-fc6f-4a66-8bed-62f175f76f3805bca744-d89f-4e9c-a990-25a0d7f03fe9", deleted = null, permissions = listOf( - PermissionsResponse( + MemberPermissionResponse( expirationDateTime = null, grantedToV2 = GrantedToV2Response(UserResponse(id = OC_USER_ID, displayName = OC_USER_INFO.displayName), null), id = null, @@ -340,7 +340,7 @@ val SPACE_RESPONSE = special = null, ) -val SPACE_PERMISSIONS_RESPONSE = SpacePermissionsResponse( +val SPACE_PERMISSIONS_RESPONSE = PermissionsResponse( actions = listOf("libre.graph/driveItem/permissions/delete", "libre.graph/driveItem/permissions/update"), roles = listOf( RoleResponse( @@ -360,7 +360,7 @@ val SPACE_PERMISSIONS_RESPONSE = SpacePermissionsResponse( ) ), members = listOf( - PermissionsResponse( + MemberPermissionResponse( expirationDateTime = null, grantedToV2 = GrantedToV2Response(UserResponse(id = OC_USER_ID, displayName = OC_USER_INFO.displayName), null), id = "u:$OC_USER_ID", @@ -372,7 +372,7 @@ val SPACE_PERMISSIONS_RESPONSE = SpacePermissionsResponse( ) ) -val SPACE_MEMBERS = SpaceMembers( +val SPACE_MEMBERS = OCPermissions( roles = listOf( OCRole( displayName = "Can view", @@ -391,13 +391,13 @@ val SPACE_MEMBERS = SpaceMembers( ) ), members = listOf( - SpaceMember( + MemberPermission( id = "u:$OC_USER_ID", expirationDateTime = null, displayName = OC_USER_INFO.displayName, roles = listOf("312c0871-5ef7-4b3a-85b6-0e4074c64049") ), - SpaceMember( + MemberPermission( id = "g:${OC_USER_GROUPS[0]}", expirationDateTime = "2025-07-03T12:09:43.364Z", displayName = "group-1",