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?