diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 1e2bf1ab0f..ea4ea9fd12 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -295,6 +295,7 @@
+
: VectorEpoxy
var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var clickListener: View.OnClickListener? = null
+ @CallSuper
override fun bind(holder: T) {
super.bind(holder)
val bestName = matrixItem.getBestName()
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt
new file mode 100644
index 0000000000..b7fd597789
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevel.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package im.vector.app.core.epoxy.profiles
+
+import android.widget.TextView
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
+abstract class ProfileMatrixItemWithPowerLevel : BaseProfileMatrixItem() {
+
+ @EpoxyAttribute var powerLevelLabel: CharSequence? = null
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.editableView.isVisible = false
+ holder.powerLabel.setTextOrHide(powerLevelLabel)
+ }
+
+ class Holder : ProfileMatrixItem.Holder() {
+ val powerLabel by bind(R.id.matrixItemPowerLevelLabel)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/platform/GenericIdArgs.kt b/vector/src/main/java/im/vector/app/core/platform/GenericIdArgs.kt
new file mode 100644
index 0000000000..0f22ae9136
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/platform/GenericIdArgs.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.platform
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+/**
+ * Generic argument with one String. Can be an id (ex: roomId, spaceId, callId, etc.), or anything else
+ */
+@Parcelize
+data class GenericIdArgs(
+ val id: String
+) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 735a29afa4..27de04210a 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -75,6 +75,7 @@ import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.SpacePreviewActivity
import im.vector.app.features.spaces.manage.SpaceManageActivity
+import im.vector.app.features.spaces.people.SpacePeopleActivity
import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgsBuilder
@@ -283,7 +284,19 @@ class DefaultNavigator @Inject constructor(
}
override fun openCreateDirectRoom(context: Context) {
- val intent = CreateDirectRoomActivity.getIntent(context)
+ val intent = when (val currentGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
+ is RoomGroupingMethod.ByLegacyGroup -> {
+ CreateDirectRoomActivity.getIntent(context)
+ }
+ is RoomGroupingMethod.BySpace -> {
+ if (currentGroupingMethod.spaceSummary != null) {
+ SpacePeopleActivity.newIntent(context, currentGroupingMethod.spaceSummary.roomId)
+ } else {
+ CreateDirectRoomActivity.getIntent(context)
+ }
+ }
+ else -> null
+ } ?: return
context.startActivity(intent)
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
index 2ff89d6e54..591e3b84e0 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt
@@ -47,11 +47,16 @@ class RoomMemberListFragment @Inject constructor(
private val roomMemberListController: RoomMemberListController,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment(),
- RoomMemberListController.Callback {
+ RoomMemberListController.Callback,
+ RoomMemberListViewModel.Factory {
private val viewModel: RoomMemberListViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args()
+ override fun create(initialState: RoomMemberListViewState): RoomMemberListViewModel {
+ return viewModelFactory.create(initialState)
+ }
+
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomMemberListBinding {
return FragmentRoomMemberListBinding.inflate(inflater, container, false)
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
index 270b3cb408..5c6ff48403 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt
@@ -17,12 +17,13 @@
package im.vector.app.features.roomprofile.members
import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
@@ -62,8 +63,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomMemberListViewState): RoomMemberListViewModel? {
- val fragment: RoomMemberListFragment = (viewModelContext as FragmentViewModelContext).fragment()
- return fragment.viewModelFactory.create(state)
+ val factory = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+ is ActivityViewModelContext -> viewModelContext.activity as? Factory
+ }
+ return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
@@ -188,7 +192,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
override fun handle(action: RoomMemberListAction) {
when (action) {
is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action)
- is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action)
+ is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action)
}.exhaustive
}
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
index 6fe8df3d36..63d07cc4dd 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt
@@ -21,6 +21,7 @@ import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
+import im.vector.app.core.platform.GenericIdArgs
import im.vector.app.features.roomprofile.RoomProfileArgs
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.events.model.Event
@@ -38,6 +39,8 @@ data class RoomMemberListViewState(
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
+
+ constructor(args: GenericIdArgs) : this(roomId = args.id)
}
data class ActionPermissions(
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
index e2cc3f7b99..bd9fb7b941 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
@@ -29,9 +29,11 @@ class RoomMemberSummaryFilter @Inject constructor() : Predicate() {
+
+ override fun getBinding() = ActivitySimpleLoadingBinding.inflate(layoutInflater)
+
+ private lateinit var sharedActionViewModel: SpacePeopleSharedActionViewModel
+
+ override fun initUiAndData() {
+ super.initUiAndData()
+ waitingView = views.waitingView.waitingView
+ }
+
+ override fun showWaitingView(text: String?) {
+ hideKeyboard()
+ views.waitingView.waitingStatusText.isGone = views.waitingView.waitingStatusText.text.isNullOrBlank()
+ super.showWaitingView(text)
+ }
+
+ override fun hideWaitingView() {
+ views.waitingView.waitingStatusText.text = null
+ views.waitingView.waitingStatusText.isGone = true
+ views.waitingView.waitingHorizontalProgress.progress = 0
+ views.waitingView.waitingHorizontalProgress.isVisible = false
+ super.hideWaitingView()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val args = intent?.getParcelableExtra(MvRx.KEY_ARG)
+ if (isFirstCreation()) {
+ val simpleName = SpacePeopleFragment::class.java.simpleName
+ if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
+ supportFragmentManager.commitTransaction {
+ replace(R.id.simpleFragmentContainer,
+ SpacePeopleFragment::class.java,
+ Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) },
+ simpleName
+ )
+ }
+ }
+ }
+
+ sharedActionViewModel = viewModelProvider.get(SpacePeopleSharedActionViewModel::class.java)
+ sharedActionViewModel
+ .observe()
+ .subscribe { sharedAction ->
+ when (sharedAction) {
+ SpacePeopleSharedAction.Dismiss -> finish()
+ is SpacePeopleSharedAction.NavigateToRoom -> navigateToRooms(sharedAction)
+ SpacePeopleSharedAction.HideModalLoading -> hideWaitingView()
+ SpacePeopleSharedAction.ShowModalLoading -> {
+ showWaitingView()
+ }
+ is SpacePeopleSharedAction.NavigateToInvite -> {
+ ShareSpaceBottomSheet.show(supportFragmentManager, sharedAction.spaceId)
+ }
+ }
+ }.disposeOnDestroy()
+ }
+
+ private fun navigateToRooms(action: SpacePeopleSharedAction.NavigateToRoom) {
+ navigator.openRoom(this, action.roomId)
+ finish()
+ }
+
+ companion object {
+ fun newIntent(context: Context, spaceId: String): Intent {
+ return Intent(context, SpacePeopleActivity::class.java).apply {
+ putExtra(MvRx.KEY_ARG, GenericIdArgs(spaceId))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt
new file mode 100644
index 0000000000..bd7a941249
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import com.jakewharton.rxbinding3.appcompat.queryTextChanges
+import im.vector.app.R
+import im.vector.app.core.extensions.cleanup
+import im.vector.app.core.extensions.configureWith
+import im.vector.app.core.platform.OnBackPressed
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
+import im.vector.app.databinding.FragmentRecyclerviewWithSearchBinding
+import im.vector.app.features.roomprofile.members.RoomMemberListAction
+import im.vector.app.features.roomprofile.members.RoomMemberListViewModel
+import im.vector.app.features.roomprofile.members.RoomMemberListViewState
+import io.reactivex.rxkotlin.subscribeBy
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+class SpacePeopleFragment @Inject constructor(
+ private val viewModelFactory: SpacePeopleViewModel.Factory,
+ private val roomMemberModelFactory: RoomMemberListViewModel.Factory,
+ private val drawableProvider: DrawableProvider,
+ private val colorProvider: ColorProvider,
+ private val epoxyController: SpacePeopleListController
+) : VectorBaseFragment(),
+ SpacePeopleViewModel.Factory,
+ RoomMemberListViewModel.Factory,
+ OnBackPressed, SpacePeopleListController.InteractionListener {
+
+ private val viewModel by fragmentViewModel(SpacePeopleViewModel::class)
+ private val membersViewModel by fragmentViewModel(RoomMemberListViewModel::class)
+ private lateinit var sharedActionViewModel: SpacePeopleSharedActionViewModel
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ FragmentRecyclerviewWithSearchBinding.inflate(inflater, container, false)
+
+ override fun onBackPressed(toolbarButton: Boolean): Boolean {
+ sharedActionViewModel.post(SpacePeopleSharedAction.Dismiss)
+ return true
+ }
+
+ override fun create(initialState: SpacePeopleViewState): SpacePeopleViewModel {
+ return viewModelFactory.create(initialState)
+ }
+
+ override fun create(initialState: RoomMemberListViewState): RoomMemberListViewModel {
+ return roomMemberModelFactory.create(initialState)
+ }
+
+ override fun invalidate() = withState(membersViewModel) { memberListState ->
+ views.appBarTitle.text = getString(R.string.bottom_action_people)
+ val memberCount = (memberListState.roomSummary.invoke()?.otherMemberIds?.size ?: 0) + 1
+ views.appBarSpaceInfo.text = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)
+// views.listBuildingProgress.isVisible = true
+ epoxyController.setData(memberListState)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ sharedActionViewModel = activityViewModelProvider.get(SpacePeopleSharedActionViewModel::class.java)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupRecyclerView()
+ setupSearchView()
+
+ views.addRoomToSpaceToolbar.navigationIcon = drawableProvider.getDrawable(
+ R.drawable.ic_close_24dp,
+ colorProvider.getColorFromAttribute(R.attr.riot_primary_text_color)
+ )
+ views.addRoomToSpaceToolbar.setNavigationOnClickListener {
+ sharedActionViewModel.post(SpacePeopleSharedAction.Dismiss)
+ }
+
+ viewModel.observeViewEvents {
+ handleViewEvents(it)
+ }
+
+ viewModel.subscribe(this) {
+ when (it.createAndInviteState) {
+ is Loading -> sharedActionViewModel.post(SpacePeopleSharedAction.ShowModalLoading)
+ Uninitialized,
+ is Fail -> sharedActionViewModel.post(SpacePeopleSharedAction.HideModalLoading)
+ is Success -> {
+ // don't hide on success, it will navigate out. If not the loading goes out before navigation
+ }
+ }
+ }
+ }
+
+ override fun onDestroyView() {
+ epoxyController.listener = null
+ views.roomList.cleanup()
+ super.onDestroyView()
+ }
+
+ private fun setupRecyclerView() {
+ views.roomList.configureWith(epoxyController, hasFixedSize = false, disableItemAnimation = false)
+ epoxyController.listener = this
+ }
+
+ private fun setupSearchView() {
+ views.memberNameFilter.queryHint = getString(R.string.search_members_hint)
+ views.memberNameFilter.queryTextChanges()
+ .debounce(100, TimeUnit.MILLISECONDS)
+ .subscribeBy {
+ membersViewModel.handle(RoomMemberListAction.FilterMemberList(it.toString()))
+ }
+ .disposeOnDestroyView()
+ }
+
+ private fun handleViewEvents(events: SpacePeopleViewEvents) {
+ when (events) {
+ is SpacePeopleViewEvents.OpenRoom -> {
+ sharedActionViewModel.post(SpacePeopleSharedAction.NavigateToRoom(events.roomId))
+ }
+ is SpacePeopleViewEvents.InviteToSpace -> {
+ sharedActionViewModel.post(SpacePeopleSharedAction.NavigateToInvite(events.spaceId))
+ }
+ }
+ }
+
+ override fun onSpaceMemberClicked(roomMemberSummary: RoomMemberSummary) {
+ viewModel.handle(SpacePeopleViewAction.ChatWith(roomMemberSummary))
+ }
+
+ override fun onInviteToSpaceSelected() {
+ viewModel.handle(SpacePeopleViewAction.InviteToSpace)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt
new file mode 100644
index 0000000000..71be3690a1
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import com.airbnb.epoxy.TypedEpoxyController
+import im.vector.app.R
+import im.vector.app.core.epoxy.dividerItem
+import im.vector.app.core.epoxy.loadingItem
+import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPowerLevel
+import im.vector.app.core.extensions.join
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.GenericItem
+import im.vector.app.core.ui.list.genericItem
+import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.roomprofile.members.RoomMemberListCategories
+import im.vector.app.features.roomprofile.members.RoomMemberListViewState
+import im.vector.app.features.roomprofile.members.RoomMemberSummaryFilter
+import me.gujun.android.span.span
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import org.matrix.android.sdk.api.util.toMatrixItem
+import javax.inject.Inject
+
+class SpacePeopleListController @Inject constructor(
+ private val avatarRenderer: AvatarRenderer,
+ private val colorProvider: ColorProvider,
+ private val stringProvider: StringProvider,
+ private val dimensionConverter: DimensionConverter,
+ private val roomMemberSummaryFilter: RoomMemberSummaryFilter
+) : TypedEpoxyController() {
+
+ private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
+
+ interface InteractionListener {
+ fun onSpaceMemberClicked(roomMemberSummary: RoomMemberSummary)
+ fun onInviteToSpaceSelected()
+ }
+
+ var listener: InteractionListener? = null
+
+ init {
+ setData(null)
+ }
+
+ override fun buildModels(data: RoomMemberListViewState?) {
+ val memberSummaries = data?.roomMemberSummaries?.invoke()
+ if (memberSummaries == null) {
+ loadingItem { id("loading") }
+ return
+ }
+ roomMemberSummaryFilter.filter = data.filter
+ var foundCount = 0
+ memberSummaries.forEach { memberEntry ->
+
+ val filtered = memberEntry.second
+ .filter { roomMemberSummaryFilter.test(it) }
+ if (filtered.isNotEmpty()) {
+ dividerItem {
+ id("divider_type_${memberEntry.first.titleRes}")
+ color(dividerColor)
+ }
+ }
+ foundCount += filtered.size
+ filtered
+ .join(
+ each = { _, roomMember ->
+ profileMatrixItemWithPowerLevel {
+ id(roomMember.userId)
+ matrixItem(roomMember.toMatrixItem())
+ avatarRenderer(avatarRenderer)
+ userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId))
+ .apply {
+ val pl = memberEntry.first.toPowerLevelLabel()
+ if (memberEntry.first == RoomMemberListCategories.INVITE) {
+ powerLevelLabel(
+ span {
+ span(stringProvider.getString(R.string.invited)) {
+ textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
+ textStyle = "bold"
+ // fontFamily = "monospace"
+ }
+ }
+ )
+ } else if (pl != null) {
+ powerLevelLabel(
+ span {
+ span(" $pl ") {
+ backgroundColor = colorProvider.getColor(R.color.notification_accent_color)
+ paddingTop = dimensionConverter.dpToPx(2)
+ paddingBottom = dimensionConverter.dpToPx(2)
+ textColor = colorProvider.getColor(R.color.white)
+ textStyle = "bold"
+ // fontFamily = "monospace"
+ }
+ }
+ )
+ } else {
+ powerLevelLabel(null)
+ }
+ }
+
+ clickListener { _ ->
+ listener?.onSpaceMemberClicked(roomMember)
+ }
+ }
+ },
+ between = { _, roomMemberBefore ->
+ dividerItem {
+ id("divider_${roomMemberBefore.userId}")
+ color(dividerColor)
+ }
+ }
+ )
+ }
+
+ if (foundCount == 0 && data.filter.isNotEmpty()) {
+ // add the footer thing
+ genericItem {
+ id("not_found")
+ title(
+ span {
+ +"\n"
+ +stringProvider.getString(R.string.no_result_placeholder)
+ }
+ )
+ description(
+ span {
+ +stringProvider.getString(R.string.looking_for_someone_not_in_space, data.roomSummary.invoke()?.displayName ?: "")
+ +"\n"
+ span("Invite them") {
+ textColor = colorProvider.getColorFromAttribute(R.attr.colorAccent)
+ textStyle = "bold"
+ }
+ }
+ )
+ itemClickAction(GenericItem.Action("invite").apply {
+ perform = Runnable {
+ listener?.onInviteToSpaceSelected()
+ }
+ })
+ }
+ }
+ }
+
+ private fun RoomMemberListCategories.toPowerLevelLabel(): String? {
+ return when (this) {
+ RoomMemberListCategories.ADMIN -> stringProvider.getString(R.string.power_level_admin)
+ RoomMemberListCategories.MODERATOR -> stringProvider.getString(R.string.power_level_moderator)
+ else -> null
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedAction.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedAction.kt
new file mode 100644
index 0000000000..201196c865
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedAction.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import im.vector.app.core.platform.VectorSharedAction
+
+sealed class SpacePeopleSharedAction : VectorSharedAction {
+ object Dismiss : SpacePeopleSharedAction()
+ object ShowModalLoading : SpacePeopleSharedAction()
+ object HideModalLoading : SpacePeopleSharedAction()
+ data class NavigateToRoom(val roomId: String) : SpacePeopleSharedAction()
+ data class NavigateToInvite(val spaceId: String) : SpacePeopleSharedAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedActionViewModel.kt
new file mode 100644
index 0000000000..ace942cd63
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedActionViewModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import im.vector.app.core.platform.VectorSharedActionViewModel
+import javax.inject.Inject
+
+class SpacePeopleSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel()
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewAction.kt
new file mode 100644
index 0000000000..39d9dddc3a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewAction.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import im.vector.app.core.platform.VectorViewModelAction
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+
+sealed class SpacePeopleViewAction : VectorViewModelAction {
+ data class ChatWith(val member: RoomMemberSummary) : SpacePeopleViewAction()
+ object InviteToSpace : SpacePeopleViewAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewEvents.kt
new file mode 100644
index 0000000000..e2d93668f2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewEvents.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class SpacePeopleViewEvents : VectorViewEvents {
+ data class OpenRoom(val roomId: String) : SpacePeopleViewEvents()
+ data class InviteToSpace(val spaceId: String) : SpacePeopleViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
new file mode 100644
index 0000000000..13944adfc8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.ViewModelContext
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.raw.wellknown.getElementWellknown
+import im.vector.app.features.raw.wellknown.isE2EByDefault
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.raw.RawService
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+class SpacePeopleViewModel @AssistedInject constructor(
+ @Assisted val initialState: SpacePeopleViewState,
+ private val rawService: RawService,
+ private val session: Session
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(initialState: SpacePeopleViewState): SpacePeopleViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+ override fun create(viewModelContext: ViewModelContext, state: SpacePeopleViewState): SpacePeopleViewModel? {
+ val factory = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+ is ActivityViewModelContext -> viewModelContext.activity as? Factory
+ }
+ return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
+ }
+ }
+
+ override fun handle(action: SpacePeopleViewAction) {
+ when (action) {
+ is SpacePeopleViewAction.ChatWith -> handleChatWith(action)
+ SpacePeopleViewAction.InviteToSpace -> handleInviteToSpace()
+ }.exhaustive
+ }
+
+ private fun handleInviteToSpace() {
+ _viewEvents.post(SpacePeopleViewEvents.InviteToSpace(initialState.spaceId))
+ }
+
+ private fun handleChatWith(action: SpacePeopleViewAction.ChatWith) {
+ val otherUserId = action.member.userId
+ if (otherUserId == session.myUserId) return
+ val existingRoomId = session.getExistingDirectRoomWithUser(otherUserId)
+ if (existingRoomId != null) {
+ // just open it
+ _viewEvents.post(SpacePeopleViewEvents.OpenRoom(existingRoomId))
+ return
+ }
+ setState { copy(createAndInviteState = Loading()) }
+
+ viewModelScope.launch(Dispatchers.IO) {
+ val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+ ?.isE2EByDefault()
+ ?: true
+
+ val roomParams = CreateRoomParams()
+ .apply {
+ invitedUserIds.add(otherUserId)
+ setDirectMessage()
+ enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
+ }
+
+ try {
+ val roomId = session.createRoom(roomParams)
+ _viewEvents.post(SpacePeopleViewEvents.OpenRoom(roomId))
+ setState { copy(createAndInviteState = Success(roomId)) }
+ } catch (failure: Throwable) {
+ setState { copy(createAndInviteState = Fail(failure)) }
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt
new file mode 100644
index 0000000000..ea322e3fbd
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.spaces.people
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+import im.vector.app.core.platform.GenericIdArgs
+
+data class SpacePeopleViewState(
+ val spaceId: String,
+ val createAndInviteState: Async = Uninitialized
+) : MvRxState {
+ constructor(args: GenericIdArgs) : this(
+ spaceId = args.id
+ )
+}
diff --git a/vector/src/main/res/layout/activity_simple_loading.xml b/vector/src/main/res/layout/activity_simple_loading.xml
new file mode 100644
index 0000000000..b523d97c92
--- /dev/null
+++ b/vector/src/main/res/layout/activity_simple_loading.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_recyclerview_with_search.xml b/vector/src/main/res/layout/fragment_recyclerview_with_search.xml
new file mode 100644
index 0000000000..a76c786026
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_recyclerview_with_search.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml
index d2ea7c01f5..3d9aa0e876 100644
--- a/vector/src/main/res/layout/item_profile_matrix_item.xml
+++ b/vector/src/main/res/layout/item_profile_matrix_item.xml
@@ -37,44 +37,53 @@
+
+
-
diff --git a/vector/src/main/res/layout/item_profile_matrix_item_progress.xml b/vector/src/main/res/layout/item_profile_matrix_item_progress.xml
index 3376d58598..55e8cecade 100644
--- a/vector/src/main/res/layout/item_profile_matrix_item_progress.xml
+++ b/vector/src/main/res/layout/item_profile_matrix_item_progress.xml
@@ -37,11 +37,10 @@
+ tools:ignore="MissingPrefix" />
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index f8b04ef3a6..f5fd97be31 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3353,4 +3353,6 @@
Experimental Space - Restricted Room.
Warning requires server support and experimental room version
%s invites you
+
+ Looking for someone not in %s?