diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt index a4fb587d19a..14efaaac634 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RemoteDataSourceModule.kt @@ -4,7 +4,7 @@ * @author David González Verdugo * @author Jorge Aguado Recio * - * Copyright (C) 2025 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, @@ -33,6 +33,8 @@ import com.owncloud.android.data.capabilities.datasources.implementation.OCRemot import com.owncloud.android.data.capabilities.datasources.mapper.RemoteCapabilityMapper import com.owncloud.android.data.files.datasources.RemoteFileDataSource import com.owncloud.android.data.files.datasources.implementation.OCRemoteFileDataSource +import com.owncloud.android.data.members.datasources.RemoteMembersDataSource +import com.owncloud.android.data.members.datasources.implementation.OCRemoteMembersDataSource import com.owncloud.android.data.oauth.datasources.RemoteOAuthDataSource import com.owncloud.android.data.oauth.datasources.implementation.OCRemoteOAuthDataSource import com.owncloud.android.data.roles.datasources.RemoteRolesDataSource @@ -76,6 +78,7 @@ val remoteDataSourceModule = module { singleOf(::OCRemoteAuthenticationDataSource) bind RemoteAuthenticationDataSource::class singleOf(::OCRemoteCapabilitiesDataSource) bind RemoteCapabilitiesDataSource::class singleOf(::OCRemoteFileDataSource) bind RemoteFileDataSource::class + singleOf(::OCRemoteMembersDataSource) bind RemoteMembersDataSource::class singleOf(::OCRemoteOAuthDataSource) bind RemoteOAuthDataSource::class singleOf(::OCRemoteRolesDataSource) bind RemoteRolesDataSource::class singleOf(::OCRemoteServerInfoDataSource) bind RemoteServerInfoDataSource::class diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt index ae4ea9fcf79..44e1e762781 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt @@ -6,7 +6,7 @@ * @author Juan Carlos Garrote Gascón * @author Jorge Aguado Recio * - * Copyright (C) 2025 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, @@ -28,6 +28,7 @@ import com.owncloud.android.data.authentication.repository.OCAuthenticationRepos import com.owncloud.android.data.capabilities.repository.OCCapabilityRepository import com.owncloud.android.data.files.repository.OCFileRepository import com.owncloud.android.data.folderbackup.repository.OCFolderBackupRepository +import com.owncloud.android.data.members.repository.OCMembersRepository import com.owncloud.android.data.oauth.repository.OCOAuthRepository import com.owncloud.android.data.roles.repository.OCRolesRepository import com.owncloud.android.data.server.repository.OCServerInfoRepository @@ -43,6 +44,7 @@ import com.owncloud.android.domain.authentication.oauth.OAuthRepository import com.owncloud.android.domain.automaticuploads.FolderBackupRepository import com.owncloud.android.domain.capabilities.CapabilityRepository import com.owncloud.android.domain.files.FileRepository +import com.owncloud.android.domain.members.MembersRepository import com.owncloud.android.domain.roles.RolesRepository import com.owncloud.android.domain.server.ServerInfoRepository import com.owncloud.android.domain.sharing.sharees.ShareeRepository @@ -61,6 +63,7 @@ val repositoryModule = module { factoryOf(::OCCapabilityRepository) bind CapabilityRepository::class factoryOf(::OCFileRepository) bind FileRepository::class factoryOf(::OCFolderBackupRepository) bind FolderBackupRepository::class + factoryOf(::OCMembersRepository) bind MembersRepository::class factoryOf(::OCOAuthRepository) bind OAuthRepository::class factoryOf(::OCRolesRepository) bind RolesRepository::class factoryOf(::OCServerInfoRepository) bind ServerInfoRepository::class 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 90299a510ee..5758eeb2456 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -7,7 +7,7 @@ * @author Aitor Ballesteros Pavón * @author Jorge Aguado Recio * - * Copyright (C) 2025 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, @@ -107,6 +107,7 @@ import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAc import com.owncloud.android.domain.spaces.usecases.GetSpacesFromEveryAccountUseCaseAsStream import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase import com.owncloud.android.domain.spaces.usecases.GetSpaceMembersUseCase +import com.owncloud.android.domain.members.usecases.SearchUsersUseCase import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransferByIdUseCase import com.owncloud.android.domain.transfers.usecases.ClearSuccessfulTransfersUseCase import com.owncloud.android.domain.transfers.usecases.GetAllTransfersAsStreamUseCase @@ -306,4 +307,7 @@ val useCaseModule = module { // Roles factoryOf(::GetRolesAsyncUseCase) + + // Members + factoryOf(::SearchUsersUseCase) } 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 new file mode 100644 index 00000000000..efbd789933a --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/AddMemberFragment.kt @@ -0,0 +1,138 @@ +/** + * 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.spaces.members + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.owncloud.android.R +import com.owncloud.android.databinding.AddMemberFragmentBinding +import com.owncloud.android.domain.members.model.OCMember +import com.owncloud.android.domain.spaces.model.OCSpace +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 + +class AddMemberFragment: Fragment() { + private var _binding: AddMemberFragmentBinding? = null + private val binding get() = _binding!! + + private val spaceMembersViewModel: SpaceMembersViewModel by viewModel { + parametersOf( + requireArguments().getString(ARG_ACCOUNT_NAME), + requireArguments().getParcelable(ARG_CURRENT_SPACE) + ) + } + + private lateinit var searchMembersAdapter: SearchMembersAdapter + private lateinit var recyclerView: RecyclerView + + private var listOfUsers = emptyList() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = AddMemberFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + searchMembersAdapter = SearchMembersAdapter() + recyclerView = binding.membersRecyclerView + recyclerView.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = searchMembersAdapter + } + + collectLatestLifecycleFlow(spaceMembersViewModel.users) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { + uiResult.data?.let { + listOfUsers = it + spaceMembersViewModel.getSpaceMembers() + } + } + is UIResult.Loading -> { } + is UIResult.Error -> { + showErrorInSnackbar(R.string.search_members_failed, uiResult.error) + } + } + } + } + + collectLatestLifecycleFlow(spaceMembersViewModel.spaceMembers) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { + uiResult.data?.let { + listOfUsers = listOfUsers.filter { user -> !it.members.any { member -> member.id == "u:${user.id}" } } + searchMembersAdapter.addUserMembers(listOfUsers) + } + } + is UIResult.Loading -> { } + is UIResult.Error -> { } + } + } + } + + + binding.searchBar.apply { + requestFocus() + setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean = true + + override fun onQueryTextChange(newText: String): Boolean { + if (newText.length > 2) { spaceMembersViewModel.searchUsers(newText) } else { spaceMembersViewModel.clearSearch() } + return true + } + }) + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + requireActivity().setTitle(R.string.add_member) + } + + companion object { + private const val ARG_ACCOUNT_NAME = "ACCOUNT_NAME" + private const val ARG_CURRENT_SPACE = "CURRENT_SPACE" + + fun newInstance( + accountName: String, + currentSpace: OCSpace + ): AddMemberFragment { + val args = Bundle().apply { + putString(ARG_ACCOUNT_NAME, accountName) + putParcelable(ARG_CURRENT_SPACE, currentSpace) + } + return AddMemberFragment().apply { + arguments = args + } + } + } +} diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SearchMembersAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SearchMembersAdapter.kt new file mode 100644 index 00000000000..07b375f81fe --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/spaces/members/SearchMembersAdapter.kt @@ -0,0 +1,71 @@ +/** + * 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.spaces.members + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.owncloud.android.R +import com.owncloud.android.databinding.MemberItemBinding +import com.owncloud.android.domain.members.model.OCMember +import com.owncloud.android.utils.PreferenceUtils + +class SearchMembersAdapter: RecyclerView.Adapter() { + + private var members: List = emptyList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchMembersViewHolder { + val inflater = LayoutInflater.from(parent.context) + + val view = inflater.inflate(R.layout.member_item, parent, false) + view.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(parent.context) + + return SearchMembersViewHolder(view) + } + + override fun onBindViewHolder(holder: SearchMembersViewHolder, position: Int) { + val member = members[position] + + holder.binding.apply { + memberIcon.setImageResource(R.drawable.ic_user) + memberName.text = member.displayName + memberName.contentDescription = holder.itemView.context.getString(R.string.content_description_member_user, member.displayName) + memberRole.text = if (member.surname == USER_SURNAME) holder.itemView.context.getString(R.string.member_user) else member.surname + } + } + + override fun getItemCount(): Int = members.size + + fun addUserMembers(members: List) { + this.members = members + notifyDataSetChanged() + } + + class SearchMembersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val binding = MemberItemBinding.bind(itemView) + } + + companion object { + private const val USER_SURNAME = "User" + } + +} 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 1d96476ef63..a7ed783a306 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 @@ -3,7 +3,7 @@ * * @author Jorge Aguado Recio * - * Copyright (C) 2025 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, @@ -25,21 +25,42 @@ import android.view.Menu import android.view.MenuItem import androidx.fragment.app.transaction import com.owncloud.android.R +import com.owncloud.android.databinding.MembersActivityBinding import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.ui.activity.FileActivity +import com.owncloud.android.utils.DisplayUtils -class SpaceMembersActivity: FileActivity() { +class SpaceMembersActivity: FileActivity(), SpaceMembersFragment.SpaceMemberFragmentListener { + + private lateinit var binding: MembersActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.members_activity) + binding = MembersActivityBinding.inflate(layoutInflater) + setContentView(binding.root) setupStandardToolbar(title = null, displayHomeAsUpEnabled = true, homeButtonEnabled = true, displayShowTitleEnabled = true) supportActionBar?.setHomeActionContentDescription(R.string.common_back) - val currentSpace = intent.getParcelableExtra(EXTRA_SPACE) + val currentSpace = intent.getParcelableExtra(EXTRA_SPACE) ?: return + binding.apply { + itemName.text = currentSpace.name + currentSpace.quota?.let { quota -> + val usedQuota = quota.used + val totalQuota = quota.total + itemSize.text = when { + usedQuota == null -> getString(R.string.drawer_unavailable_used_storage) + totalQuota == 0L -> DisplayUtils.bytesToHumanReadable(usedQuota, baseContext, true) + else -> getString( + R.string.drawer_quota, + DisplayUtils.bytesToHumanReadable(usedQuota, baseContext, true), + DisplayUtils.bytesToHumanReadable(totalQuota, baseContext, true), + quota.getRelative().toString()) + } + } + } supportFragmentManager.transaction { if (savedInstanceState == null && currentSpace != null) { @@ -59,8 +80,17 @@ class SpaceMembersActivity: FileActivity() { super.onOptionsItemSelected(item) } + override fun addMember(space: OCSpace) { + supportFragmentManager.transaction { + val fragment = AddMemberFragment.newInstance(account.name, space) + replace(R.id.members_fragment_container, fragment, TAG_ADD_MEMBER_FRAGMENT) + addToBackStack(null) + } + } + companion object { private const val TAG_SPACE_MEMBERS_FRAGMENT = "SPACE_MEMBERS_FRAGMENT" + private const val TAG_ADD_MEMBER_FRAGMENT ="ADD_MEMBER_FRAGMENT" const val EXTRA_SPACE = "EXTRA_SPACE" } 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 00ed053774d..a57ed533693 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 @@ -3,7 +3,7 @@ * * @author Jorge Aguado Recio * - * Copyright (C) 2025 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, @@ -20,6 +20,7 @@ package com.owncloud.android.presentation.spaces.members +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -33,9 +34,9 @@ import com.owncloud.android.domain.roles.model.OCRole import com.owncloud.android.domain.spaces.model.OCSpace import com.owncloud.android.extensions.collectLatestLifecycleFlow import com.owncloud.android.presentation.common.UIResult -import com.owncloud.android.utils.DisplayUtils import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf +import timber.log.Timber class SpaceMembersFragment : Fragment() { private var _binding: MembersFragmentBinding? = null @@ -52,6 +53,7 @@ class SpaceMembersFragment : Fragment() { private lateinit var recyclerView: RecyclerView private var roles: List = emptyList() + private var listener: SpaceMemberFragmentListener? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = MembersFragmentBinding.inflate(inflater, container, false) @@ -96,28 +98,49 @@ class SpaceMembersFragment : Fragment() { } } - val currentSpace = requireArguments().getParcelable(ARG_CURRENT_SPACE) ?: return - binding.apply { - itemName.text = currentSpace.name - currentSpace.quota?.let { quota -> - val usedQuota = quota.used - val totalQuota = quota.total - itemSize.text = when { - usedQuota == null -> getString(R.string.drawer_unavailable_used_storage) - totalQuota == 0L -> DisplayUtils.bytesToHumanReadable(usedQuota, requireContext(), true) - else -> getString( - R.string.drawer_quota, - DisplayUtils.bytesToHumanReadable(usedQuota, requireContext(), true), - DisplayUtils.bytesToHumanReadable(totalQuota, requireContext(), true), - quota.getRelative().toString()) + collectLatestLifecycleFlow(spaceMembersViewModel.spacePermissions) { event -> + event?.let { + when (val uiResult = event.peekContent()) { + is UIResult.Success -> { + uiResult.data?.let { spacePermissions -> + if (DRIVES_CREATE_PERMISSION in spacePermissions) { binding.addMemberButton.visibility = View.VISIBLE } + } + } + is UIResult.Loading -> { } + is UIResult.Error -> { } } } } + + val currentSpace = requireArguments().getParcelable(ARG_CURRENT_SPACE) ?: return + binding.addMemberButton.setOnClickListener { + listener?.addMember(currentSpace) + } + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + requireActivity().setTitle(R.string.space_members_label) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + try { + listener = context as SpaceMemberFragmentListener? + } catch (e: ClassCastException) { + Timber.e(e, "The activity attached does not implement SpaceMemberFragmentListener") + throw ClassCastException(activity.toString() + " must implement SpaceMemberFragmentListener") + } + } + + interface SpaceMemberFragmentListener { + fun addMember(space: OCSpace) } companion object { private const val ARG_CURRENT_SPACE = "CURRENT_SPACE" private const val ARG_ACCOUNT_NAME = "ACCOUNT_NAME" + private const val DRIVES_CREATE_PERMISSION = "libre.graph/driveItem/permissions/create" fun newInstance( accountName: 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 72b4fa673fb..b6f6662a1fd 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 @@ -3,7 +3,7 @@ * * @author Jorge Aguado Recio * - * Copyright (C) 2025 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, @@ -21,11 +21,14 @@ package com.owncloud.android.presentation.spaces.members import androidx.lifecycle.ViewModel +import com.owncloud.android.domain.members.model.OCMember 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.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.SearchUsersUseCase import com.owncloud.android.domain.utils.Event import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult import com.owncloud.android.presentation.common.UIResult @@ -36,6 +39,8 @@ import kotlinx.coroutines.flow.StateFlow class SpaceMembersViewModel( private val getRolesAsyncUseCase: GetRolesAsyncUseCase, private val getSpaceMembersUseCase: GetSpaceMembersUseCase, + private val getSpacePermissionsAsyncUseCase: GetSpacePermissionsAsyncUseCase, + private val searchUsersUseCase: SearchUsersUseCase, private val accountName: String, private val space: OCSpace, private val coroutineDispatcherProvider: CoroutinesDispatcherProvider @@ -47,6 +52,12 @@ class SpaceMembersViewModel( private val _spaceMembers = MutableStateFlow>?>(null) val spaceMembers: StateFlow>?> = _spaceMembers + private val _spacePermissions = MutableStateFlow>>?>(null) + val spacePermissions: StateFlow>>?> = _spacePermissions + + private val _users = MutableStateFlow>>?>(null) + val users: StateFlow>>?> = _users + init { runUseCaseWithResult( coroutineDispatcher = coroutineDispatcherProvider.io, @@ -56,6 +67,16 @@ class SpaceMembersViewModel( showLoading = false, requiresConnection = true ) + + runUseCaseWithResult( + coroutineDispatcher = coroutineDispatcherProvider.io, + flow = _spacePermissions, + useCase = getSpacePermissionsAsyncUseCase, + useCaseParams = GetSpacePermissionsAsyncUseCase.Params(accountName = accountName, spaceId = space.id), + showLoading = false, + requiresConnection = true + ) + } fun getSpaceMembers() = runUseCaseWithResult( @@ -67,4 +88,17 @@ class SpaceMembersViewModel( requiresConnection = true ) + fun searchUsers(query: String) = runUseCaseWithResult( + coroutineDispatcher = coroutineDispatcherProvider.io, + flow = _users, + useCase = searchUsersUseCase, + useCaseParams = SearchUsersUseCase.Params(accountName = accountName, query = query), + showLoading = false, + requiresConnection = true + ) + + fun clearSearch() { + _users.value = Event(UIResult.Success(emptyList())) + } + } diff --git a/owncloudApp/src/main/res/layout/add_member_fragment.xml b/owncloudApp/src/main/res/layout/add_member_fragment.xml new file mode 100644 index 00000000000..fe412782e9c --- /dev/null +++ b/owncloudApp/src/main/res/layout/add_member_fragment.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/owncloudApp/src/main/res/layout/member_item.xml b/owncloudApp/src/main/res/layout/member_item.xml index ab5f52997ee..c23fa978ec3 100644 --- a/owncloudApp/src/main/res/layout/member_item.xml +++ b/owncloudApp/src/main/res/layout/member_item.xml @@ -1,7 +1,7 @@ - + + + + + + + + + +