Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -306,4 +307,7 @@ val useCaseModule = module {

// Roles
factoryOf(::GetRolesAsyncUseCase)

// Members
factoryOf(::SearchUsersUseCase)
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<OCSpace>(ARG_CURRENT_SPACE)
)
}

private lateinit var searchMembersAdapter: SearchMembersAdapter
private lateinit var recyclerView: RecyclerView

private var listOfUsers = emptyList<OCMember>()

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
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<SearchMembersAdapter.SearchMembersViewHolder>() {

private var members: List<OCMember> = 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<OCMember>) {
this.members = members
notifyDataSetChanged()
}

class SearchMembersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = MemberItemBinding.bind(itemView)
}

companion object {
private const val USER_SURNAME = "User"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<OCSpace>(EXTRA_SPACE)
val currentSpace = intent.getParcelableExtra<OCSpace>(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) {
Expand All @@ -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"
}

Expand Down
Loading
Loading