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 @@
+
() {
+
+ @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/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 735a29afa4..c38dccd63b 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,8 +284,21 @@ class DefaultNavigator @Inject constructor(
}
override fun openCreateDirectRoom(context: Context) {
- val intent = CreateDirectRoomActivity.getIntent(context)
- context.startActivity(intent)
+ when (val currentGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
+ is RoomGroupingMethod.ByLegacyGroup -> {
+ val intent = CreateDirectRoomActivity.getIntent(context)
+ context.startActivity(intent)
+ }
+ is RoomGroupingMethod.BySpace -> {
+ if (currentGroupingMethod.spaceSummary != null) {
+ val intent = SpacePeopleActivity.newIntent(context, currentGroupingMethod.spaceSummary.roomId)
+ context.startActivity(intent)
+ } else {
+ val intent = CreateDirectRoomActivity.getIntent(context)
+ context.startActivity(intent)
+ }
+ }
+ }
}
override fun openInviteUsersToRoom(context: Context, roomId: String) {
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/RoomMemberSummaryFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryFilter.kt
index e2cc3f7b99..a9e55f91c3 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,12 @@ class RoomMemberSummaryFilter @Inject constructor() : Predicate
+ acc
+ && (roomMemberSummary.displayName?.contains(s, ignoreCase = true).orFalse()
+ // We should maybe exclude the domain from the userId
+ || roomMemberSummary.userId.contains(s, ignoreCase = true))
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt
new file mode 100644
index 0000000000..1cc2203042
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.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 android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.MvRx
+import im.vector.app.R
+import im.vector.app.core.extensions.commitTransaction
+import im.vector.app.core.extensions.hideKeyboard
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.databinding.ActivitySimpleLoadingBinding
+import im.vector.app.features.roomprofile.RoomProfileArgs
+import im.vector.app.features.spaces.ShareSpaceBottomSheet
+
+class SpacePeopleActivity : VectorBaseActivity() {
+
+ 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, RoomProfileArgs(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..7f5369a6ec
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt
@@ -0,0 +1,152 @@
+/*
+ * 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 androidx.core.content.ContextCompat
+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.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 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(viewModel, membersViewModel) { baseState, 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 = ContextCompat.getDrawable(requireContext(), R.drawable.ic_close_24dp)
+ 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..fc982862a7
--- /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/SpacePeopleSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedActionViewModel.kt
new file mode 100644
index 0000000000..649f241bf9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleSharedActionViewModel.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 im.vector.app.core.platform.VectorSharedAction
+import im.vector.app.core.platform.VectorSharedActionViewModel
+import javax.inject.Inject
+
+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()
+}
+
+class SpacePeopleSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel()
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..71c3bcdda7
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.Async
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+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.VectorViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.features.raw.wellknown.getElementWellknown
+import im.vector.app.features.raw.wellknown.isE2EByDefault
+import im.vector.app.features.roomprofile.RoomProfileArgs
+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.RoomMemberSummary
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+data class SpacePeopleViewState(
+ val spaceId: String,
+ val createAndInviteState: Async = Uninitialized
+) : MvRxState {
+ constructor(args: RoomProfileArgs) : this(
+ spaceId = args.roomId
+ )
+}
+
+sealed class SpacePeopleViewAction : VectorViewModelAction {
+ data class ChatWith(val member: RoomMemberSummary) : SpacePeopleViewAction()
+ object InviteToSpace : SpacePeopleViewAction()
+}
+
+sealed class SpacePeopleViewEvents : VectorViewEvents {
+ data class OpenRoom(val roomId: String) : SpacePeopleViewEvents()
+ data class InviteToSpace(val spaceId: String) : SpacePeopleViewEvents()
+}
+
+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/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..a9c26cff6f
--- /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..29f537c315 100644
--- a/vector/src/main/res/layout/item_profile_matrix_item.xml
+++ b/vector/src/main/res/layout/item_profile_matrix_item.xml
@@ -40,7 +40,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginEnd="8dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="1"
@@ -48,7 +48,7 @@
android:textSize="16sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle"
- app:layout_constraintEnd_toStartOf="@+id/matrixItemEditable"
+ app:layout_constraintEnd_toStartOf="@+id/matrixItemPowerLevelLabel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toTopOf="parent"
@@ -60,7 +60,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginEnd="8dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="1"
@@ -68,13 +68,27 @@
android:textSize="12sp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/matrixItemEditable"
+ app:layout_constraintEnd_toStartOf="@+id/matrixItemPowerLevelLabel"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
app:layout_constraintTop_toBottomOf="@id/matrixItemTitle"
app:layout_goneMarginStart="0dp"
tools:text="@sample/matrix.json/data/mxid" />
+
+
+
Experimental Space - Restricted Room.
Warning requires server support and experimental room version
%s invites you
+
+ Looking for someone not in %s?