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/extensions/ThrowableExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt
index d79a4f68baa..5b7dd781266 100644
--- a/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt
+++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt
@@ -33,6 +33,7 @@ import com.owncloud.android.domain.exceptions.CopyIntoSameFolderException
import com.owncloud.android.domain.exceptions.FileAlreadyExistsException
import com.owncloud.android.domain.exceptions.FileNotFoundException
import com.owncloud.android.domain.exceptions.ForbiddenException
+import com.owncloud.android.domain.exceptions.IncompleteFileDataException
import com.owncloud.android.domain.exceptions.IncorrectAddressException
import com.owncloud.android.domain.exceptions.InstanceNotConfiguredException
import com.owncloud.android.domain.exceptions.InvalidOverwriteException
@@ -84,6 +85,7 @@ fun Throwable.parseError(
})
is FileNotFoundException -> resources.getString(R.string.common_not_found)
is ForbiddenException -> resources.getString(R.string.uploads_view_upload_status_failed_permission_error)
+ is IncompleteFileDataException -> resources.getString(R.string.get_graph_shares_incomplete_data_error)
is IncorrectAddressException -> resources.getString(R.string.auth_incorrect_address_title)
is InstanceNotConfiguredException -> resources.getString(R.string.auth_not_configured_title)
is InvalidOverwriteException -> resources.getString(R.string.file_already_exists)
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..792f7f50004
--- /dev/null
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareFragment.kt
@@ -0,0 +1,146 @@
+/**
+ * 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
+
+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
+ 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..d332f781caf
--- /dev/null
+++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/sharing/GraphShareViewModel.kt
@@ -0,0 +1,81 @@
+/**
+ * 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.exceptions.IncompleteFileDataException
+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
+import kotlinx.coroutines.flow.update
+
+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() {
+ val spaceId = file.spaceId
+ val itemId = file.remoteId
+ if (spaceId == null || itemId == null) {
+ _shares.update { Event(UIResult.Error(error = IncompleteFileDataException())) }
+ return
+ }
+
+ runUseCaseWithResult(
+ coroutineDispatcher = coroutineDispatcherProvider.io,
+ showLoading = true,
+ flow = _shares,
+ useCase = getGraphSharesAsyncUseCase,
+ useCaseParams = GetGraphSharesAsyncUseCase.Params(
+ accountName = accountName,
+ spaceId = spaceId,
+ itemId = itemId
+ )
+ )
+ }
+}
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..bc765fe678c
--- /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(
+ compareBy { it.isGroup }
+ .thenBy { it.displayName.lowercase() }
+ )
+ 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"/>
+
+
%1$s could not be copied to %2$s local folder.
It was not possible to retrieve the shares from the server
+ Some data could not be retrieved
It was not possible to retrieve users and groups from the server
It was not possible to retrieve the capabilities from the server
It was not possible to share this file or folder
@@ -492,6 +493,7 @@
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 +508,7 @@
Download/View/Upload
Upload Only (File Drop)
Share with
+ Share with people
Share with %1$s
Search
@@ -623,6 +626,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..52ee3ab4747 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,
@@ -23,6 +24,7 @@
package com.owncloud.android.data.sharing.shares.datasources.implementation
import com.owncloud.android.data.ClientManager
+import com.owncloud.android.data.sharing.shares.datasources.implementation.OCRemoteShareDataSource.Companion.toModel
import com.owncloud.android.data.sharing.shares.datasources.mapper.RemoteShareMapper
import com.owncloud.android.domain.exceptions.ShareForbiddenException
import com.owncloud.android.domain.exceptions.ShareNotFoundException
@@ -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/exceptions/IncompleteFileDataException.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/exceptions/IncompleteFileDataException.kt
new file mode 100644
index 00000000000..4fccfae1d5c
--- /dev/null
+++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/exceptions/IncompleteFileDataException.kt
@@ -0,0 +1,23 @@
+/**
+ * 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.exceptions
+
+class IncompleteFileDataException : Exception()
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",