From 150d44aafd3afa6639c6d8dc834b6626ab490b95 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 10 Jul 2020 20:08:51 +0200 Subject: [PATCH] Improve a bit how joining/leaving are handled --- .../java/im/vector/matrix/rx/RxSession.kt | 5 ++ .../api/session/room/RoomDirectoryService.kt | 7 -- .../android/api/session/room/RoomService.kt | 9 +++ .../session/room/RoomSummaryQueryParams.kt | 3 + .../room/members/ChangeMembershipState.kt | 34 +++++++++ .../room/DefaultRoomDirectoryService.kt | 8 -- .../session/room/DefaultRoomService.kt | 7 ++ .../RoomChangeMembershipStateDataSource.kt | 67 +++++++++++++++++ .../room/membership/joining/JoinRoomTask.kt | 13 +++- .../room/membership/leaving/LeaveRoomTask.kt | 32 ++++++-- .../room/summary/RoomSummaryDataSource.kt | 1 + .../internal/session/sync/RoomSyncHandler.kt | 6 ++ .../home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 19 ++++- .../home/room/detail/RoomDetailViewState.kt | 2 + .../home/room/list/RoomInvitationItem.kt | 68 +++++++---------- .../home/room/list/RoomListViewModel.kt | 62 +++++----------- .../home/room/list/RoomListViewState.kt | 10 +-- .../home/room/list/RoomSummaryController.kt | 21 ++---- .../home/room/list/RoomSummaryItemFactory.kt | 23 +++--- .../invite/InviteButtonStateBinder.kt | 48 ++++++++++++ .../riotx/features/invite/VectorInviteView.kt | 26 ++++++- .../features/navigation/DefaultNavigator.kt | 5 +- .../riotx/features/navigation/Navigator.kt | 3 +- .../roomdirectory/PublicRoomsController.kt | 12 +-- .../roomdirectory/PublicRoomsFragment.kt | 24 +++--- .../roomdirectory/PublicRoomsViewState.kt | 10 +-- .../roomdirectory/RoomDirectoryAction.kt | 2 +- .../roomdirectory/RoomDirectoryViewModel.kt | 73 +++++++------------ .../roompreview/RoomPreviewAction.kt | 2 +- .../roompreview/RoomPreviewActivity.kt | 9 ++- .../RoomPreviewNoPreviewFragment.kt | 2 +- .../roompreview/RoomPreviewViewModel.kt | 69 +++++++++--------- .../roompreview/RoomPreviewViewState.kt | 12 ++- .../roomprofile/RoomProfileFragment.kt | 1 - .../roomprofile/RoomProfileViewEvents.kt | 1 - .../roomprofile/RoomProfileViewModel.kt | 2 +- .../main/res/layout/vector_invite_view.xml | 37 +++++----- .../src/main/res/layout/view_button_state.xml | 1 + 39 files changed, 454 insertions(+), 284 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index e8fef1361d..54dacb5614 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.sync.SyncState @@ -165,6 +166,10 @@ class RxSession(private val session: Session) { session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes) } } + + fun liveRoomChangeMembershipState(): Observable> { + return session.getChangeMembershipsLive().asObservable() + } } fun Session.rx(): RxSession { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt index 0273c789dd..7014fbff37 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomDirectoryService.kt @@ -34,13 +34,6 @@ interface RoomDirectoryService { publicRoomsParams: PublicRoomsParams, callback: MatrixCallback): Cancelable - /** - * Join a room by id, or room alias - */ - fun joinRoom(roomIdOrAlias: String, - reason: String? = null, - callback: MatrixCallback): Cancelable - /** * Fetches the overall metadata about protocols supported by the homeserver. * Includes both the available protocols and all fields required for queries against each protocol. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index bc6c17a130..3093fb8ec5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.util.Cancelable @@ -104,5 +105,13 @@ interface RoomService { searchOnServer: Boolean, callback: MatrixCallback>): Cancelable + /** + * Return a live data of all local changes membership who happened since the session has been opened. + * It allows you to track this in your client to known what is currently being processed by the SDK. + * It won't know anything about change being done in other client. + * Keys are roomId or roomAlias, depending of what you used as parameter for the join/leave action + */ + fun getChangeMembershipsLive(): LiveData> + fun getExistingDirectRoomWithUser(otherUserId: String) : Room? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt index 6983bda225..51df30ad75 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomSummaryQueryParams.kt @@ -28,6 +28,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = { * [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService] */ data class RoomSummaryQueryParams( + val roomId: QueryStringValue, val displayName: QueryStringValue, val canonicalAlias: QueryStringValue, val memberships: List @@ -35,11 +36,13 @@ data class RoomSummaryQueryParams( class Builder { + var roomId: QueryStringValue = QueryStringValue.IsNotEmpty var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var memberships: List = Membership.all() fun build() = RoomSummaryQueryParams( + roomId = roomId, displayName = displayName, canonicalAlias = canonicalAlias, memberships = memberships diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt new file mode 100644 index 0000000000..8f42e310bf --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/ChangeMembershipState.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 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.matrix.android.api.session.room.members + +sealed class ChangeMembershipState() { + object Unknown : ChangeMembershipState() + object Joining : ChangeMembershipState() + data class FailedJoining(val throwable: Throwable) : ChangeMembershipState() + object Joined : ChangeMembershipState() + object Leaving : ChangeMembershipState() + data class FailedLeaving(val throwable: Throwable) : ChangeMembershipState() + object Left : ChangeMembershipState() + + fun isInProgress() = this is Joining || this is Leaving + + fun isSuccessful() = this is Joined || this is Left + + fun isFailed() = this is FailedJoining || this is FailedLeaving + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt index ef55702de6..e89fae647f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomDirectoryService.kt @@ -44,14 +44,6 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu .executeBy(taskExecutor) } - override fun joinRoom(roomIdOrAlias: String, reason: String?, callback: MatrixCallback): Cancelable { - return joinRoomTask - .configureWith(JoinRoomTask.Params(roomIdOrAlias, reason)) { - this.callback = callback - } - .executeBy(taskExecutor) - } - override fun getThirdPartyProtocol(callback: MatrixCallback>): Cancelable { return getThirdPartyProtocolsTask .configureWith { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index c773682c0f..b8b4c968b1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -21,12 +21,14 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask +import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.summary.RoomSummaryDataSource @@ -43,6 +45,7 @@ internal class DefaultRoomService @Inject constructor( private val roomIdByAliasTask: GetRoomIdByAliasTask, private val roomGetter: RoomGetter, private val roomSummaryDataSource: RoomSummaryDataSource, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val taskExecutor: TaskExecutor ) : RoomService { @@ -111,4 +114,8 @@ internal class DefaultRoomService @Inject constructor( } .executeBy(taskExecutor) } + + override fun getChangeMembershipsLive(): LiveData> { + return roomChangeMembershipStateDataSource.getLiveStates() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt new file mode 100644 index 0000000000..5cf75c3bbd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 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.matrix.android.internal.session.room.membership + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.internal.session.SessionScope +import javax.inject.Inject + +/** + * This class holds information about rooms that current user is joining or leaving. + */ +@SessionScope +internal class RoomChangeMembershipStateDataSource @Inject constructor() { + + private val mutableLiveStates = MutableLiveData>(emptyMap()) + private val states = HashMap() + + /** + * This will update local states to be synced with the server. + */ + fun setMembershipFromSync(roomId: String, membership: Membership) { + if (states.containsKey(roomId)) { + val newState = membership.toMembershipChangeState() + updateState(roomId, newState) + } + } + + fun updateState(roomId: String, state: ChangeMembershipState) { + states[roomId] = state + mutableLiveStates.postValue(states.toMap()) + } + + fun getLiveStates(): LiveData> { + return mutableLiveStates + } + + fun getState(roomId: String): ChangeMembershipState { + return states.getOrElse(roomId) { + ChangeMembershipState.Unknown + } + } + + private fun Membership.toMembershipChangeState(): ChangeMembershipState { + return when { + this == Membership.JOIN -> ChangeMembershipState.Joined + this.isLeft() -> ChangeMembershipState.Left + else -> ChangeMembershipState.Unknown + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index 635f3955c2..7467a595bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.session.room.membership.joining import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse import im.vector.matrix.android.internal.database.awaitNotEmptyResult import im.vector.matrix.android.internal.database.model.RoomEntity @@ -24,6 +25,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntityFields import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.task.Task import io.realm.RealmConfiguration @@ -45,12 +47,19 @@ internal class DefaultJoinRoomTask @Inject constructor( private val readMarkersTask: SetReadMarkersTask, @SessionDatabase private val realmConfiguration: RealmConfiguration, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val eventBus: EventBus ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { - val joinRoomResponse = executeRequest(eventBus) { - apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason)) + roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) + val joinRoomResponse = try { + executeRequest(eventBus) { + apiCall = roomAPI.join(params.roomIdOrAlias, params.viaServers, mapOf("reason" to params.reason)) + } + } catch (failure: Throwable) { + roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.FailedJoining(failure)) + throw failure } // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) val roomId = joinRoomResponse.roomId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt index 2d56e5bd47..94645f3d98 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -19,12 +19,16 @@ package im.vector.matrix.android.internal.session.room.membership.leaving import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource import im.vector.matrix.android.internal.session.room.state.StateEventDataSource +import im.vector.matrix.android.internal.session.room.summary.RoomSummaryDataSource import im.vector.matrix.android.internal.task.Task import org.greenrobot.eventbus.EventBus +import timber.log.Timber import javax.inject.Inject internal interface LeaveRoomTask : Task { @@ -37,7 +41,9 @@ internal interface LeaveRoomTask : Task { internal class DefaultLeaveRoomTask @Inject constructor( private val roomAPI: RoomAPI, private val eventBus: EventBus, - private val stateEventDataSource: StateEventDataSource + private val stateEventDataSource: StateEventDataSource, + private val roomSummaryDataSource: RoomSummaryDataSource, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource ) : LeaveRoomTask { override suspend fun execute(params: LeaveRoomTask.Params) { @@ -45,13 +51,29 @@ internal class DefaultLeaveRoomTask @Inject constructor( } private suspend fun leaveRoom(roomId: String, reason: String?) { - val roomCreateStateEvent = stateEventDataSource.getStateEvent(roomId, eventType = EventType.STATE_ROOM_CREATE, stateKey = QueryStringValue.NoCondition) - val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel()?.predecessor?.roomId - executeRequest(eventBus) { - apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + val roomSummary = roomSummaryDataSource.getRoomSummary(roomId) + if (roomSummary?.membership?.isActive() == false) { + Timber.v("Room $roomId is not joined so can't be left") + return } + roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.Leaving) + val roomCreateStateEvent = stateEventDataSource.getStateEvent( + roomId = roomId, + eventType = EventType.STATE_ROOM_CREATE, + stateKey = QueryStringValue.NoCondition + ) + // Server is not cleaning predecessor rooms, so we also try to left them + val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel()?.predecessor?.roomId if (predecessorRoomId != null) { leaveRoom(predecessorRoomId, reason) } + try { + executeRequest(eventBus) { + apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + } + } catch (failure: Throwable) { + roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.FailedLeaving(failure)) + throw failure + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt index 7c579a2719..b1518b085d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/summary/RoomSummaryDataSource.kt @@ -100,6 +100,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { val query = RoomSummaryEntity.where(realm) + query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index f3af24001d..de2c3cda57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -48,6 +48,7 @@ import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.mapWithProgress +import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource import im.vector.matrix.android.internal.session.room.membership.RoomMemberEventHandler import im.vector.matrix.android.internal.session.room.read.FullyReadContent import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater @@ -73,6 +74,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private val cryptoService: DefaultCryptoService, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomTypingUsersHandler: RoomTypingUsersHandler, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, @UserId private val userId: String, private val eventBus: EventBus, private val timelineEventDecryptor: TimelineEventDecryptor) { @@ -185,6 +187,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } != null roomTypingUsersHandler.handle(realm, roomId, ephemeralResult) + roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.JOIN) roomSummaryUpdater.update( realm, roomId, @@ -221,6 +224,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val inviterEvent = roomSync.inviteState?.events?.lastOrNull { it.type == EventType.STATE_ROOM_MEMBER } + roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.INVITE) roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = true, inviterId = inviterEvent?.senderId) return roomEntity } @@ -263,6 +267,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val membership = leftMember?.membership ?: Membership.LEAVE roomEntity.membership = membership roomEntity.chunks.deleteAllFromRealm() + roomTypingUsersHandler.handle(realm, roomId, null) + roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, roomSync.unreadNotifications) return roomEntity } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 7483d978a3..2f8c72a996 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -883,7 +883,7 @@ class RoomDetailFragment @Inject constructor( } else if (summary?.membership == Membership.INVITE && inviter != null) { roomToolbarContentView.isClickable = false inviteView.visibility = View.VISIBLE - inviteView.render(inviter, VectorInviteView.Mode.LARGE) + inviteView.render(inviter, VectorInviteView.Mode.LARGE, state.changeMembershipState) // Intercept click event inviteView.setOnClickListener { } } else if (state.asyncInviter.complete) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index bed8b4867d..fb1cb8e666 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -40,6 +40,7 @@ import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.members.roomMemberQueryParams import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.PowerLevelsContent @@ -166,6 +167,7 @@ class RoomDetailViewModel @AssistedInject constructor( timeline.start() timeline.addListener(this) observeRoomSummary() + observeMembershipChanges() observeSummaryState() getUnreadState() observeSyncState() @@ -406,7 +408,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled() fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state -> - if(state.asyncRoomSummary()?.membership != Membership.JOIN){ + if (state.asyncRoomSummary()?.membership != Membership.JOIN) { return@withState false } when (itemId) { @@ -629,7 +631,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { - session.joinRoom(command.roomAlias, command.reason, object : MatrixCallback { + session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback { override fun onSuccess(data: Unit) { session.getRoomSummary(command.roomAlias) ?.roomId @@ -1147,6 +1149,19 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .map { + it[initialState.roomId] ?: ChangeMembershipState.Unknown + } + .distinctUntilChanged() + .subscribe { + setState { copy(changeMembershipState = it) } + } + .disposeOnClear() + } + private fun observeSummaryState() { asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> roomSummaryHolder.set(summary) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt index 224dd61b65..6800850c48 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewState.kt @@ -20,6 +20,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -64,6 +65,7 @@ data class RoomDetailViewState( val highlightedEventId: String? = null, val unreadState: UnreadState = UnreadState.Unknown, val canShowJumpToReadMarker: Boolean = true, + val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown, val canSendMessage: Boolean = true ) : MvRxState { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt index 4e4e758aa2..7338a46d8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomInvitationItem.kt @@ -19,9 +19,9 @@ package im.vector.riotx.features.home.room.list import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView -import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder @@ -29,6 +29,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.ButtonStateView import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.invite.InviteButtonStateBinder @EpoxyModelClass(layout = R.layout.item_room_invitation) abstract class RoomInvitationItem : VectorEpoxyModel() { @@ -37,53 +38,36 @@ abstract class RoomInvitationItem : VectorEpoxyModel( @EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute var secondLine: CharSequence? = null @EpoxyAttribute var listener: (() -> Unit)? = null - @EpoxyAttribute var invitationAcceptInProgress: Boolean = false - @EpoxyAttribute var invitationAcceptInError: Boolean = false - @EpoxyAttribute var invitationRejectInProgress: Boolean = false - @EpoxyAttribute var invitationRejectInError: Boolean = false + @EpoxyAttribute lateinit var changeMembershipState: ChangeMembershipState @EpoxyAttribute var acceptListener: (() -> Unit)? = null @EpoxyAttribute var rejectListener: (() -> Unit)? = null + private val acceptCallback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + acceptListener?.invoke() + } + + override fun onRetryClicked() { + acceptListener?.invoke() + } + } + + private val rejectCallback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + rejectListener?.invoke() + } + + override fun onRetryClicked() { + rejectListener?.invoke() + } + } + override fun bind(holder: Holder) { super.bind(holder) holder.rootView.setOnClickListener { listener?.invoke() } - - // When a request is in progress (accept or reject), we only use the accept State button - val requestInProgress = invitationAcceptInProgress || invitationRejectInProgress - - when { - requestInProgress -> holder.acceptView.render(ButtonStateView.State.Loading) - invitationAcceptInError -> holder.acceptView.render(ButtonStateView.State.Error) - else -> holder.acceptView.render(ButtonStateView.State.Button) - } - // ButtonStateView.State.Loaded not used because roomSummary will not be displayed as a room invitation anymore - - holder.acceptView.callback = object : ButtonStateView.Callback { - override fun onButtonClicked() { - acceptListener?.invoke() - } - - override fun onRetryClicked() { - acceptListener?.invoke() - } - } - - holder.rejectView.isVisible = !requestInProgress - - when { - invitationRejectInError -> holder.rejectView.render(ButtonStateView.State.Error) - else -> holder.rejectView.render(ButtonStateView.State.Button) - } - - holder.rejectView.callback = object : ButtonStateView.Callback { - override fun onButtonClicked() { - rejectListener?.invoke() - } - - override fun onRetryClicked() { - rejectListener?.invoke() - } - } + holder.acceptView.callback = acceptCallback + holder.rejectView.callback = rejectCallback + InviteButtonStateBinder.bind(holder.acceptView, holder.rejectView, changeMembershipState) holder.titleView.text = matrixItem.getBestName() holder.subtitleView.setTextOrHide(secondLine) avatarRenderer.render(matrixItem, holder.avatarImageView) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index a2de7c79a0..cfc76b61a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -21,10 +21,12 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag +import im.vector.matrix.rx.rx import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.utils.DataSource @@ -55,6 +57,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, init { observeRoomSummaries() + observeMembershipChanges() } override fun handle(action: RoomListAction) { @@ -102,37 +105,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, .observeOn(Schedulers.computation()) .map { buildRoomSummaries(it) } .execute { async -> - val invitedRooms = async()?.get(RoomCategory.INVITE)?.map { it.roomId }.orEmpty() - val remainingJoining = joiningRoomsIds.intersect(invitedRooms) - val remainingJoinErrors = joiningErrorRoomsIds.intersect(invitedRooms) - val remainingRejecting = rejectingRoomsIds.intersect(invitedRooms) - val remainingRejectErrors = rejectingErrorRoomsIds.intersect(invitedRooms) - copy( - asyncFilteredRooms = async, - joiningRoomsIds = remainingJoining, - joiningErrorRoomsIds = remainingJoinErrors, - rejectingRoomsIds = remainingRejecting, - rejectingErrorRoomsIds = remainingRejectErrors - ) + copy(asyncFilteredRooms = async) } } private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state -> val roomId = action.roomSummary.roomId - - if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) { + val roomMembershipChange = state.roomMembershipChanges[roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { // Request already sent, should not happen Timber.w("Try to join an already joining room. Should not happen") return@withState } - setState { - copy( - joiningRoomsIds = joiningRoomsIds + roomId, - rejectingErrorRoomsIds = rejectingErrorRoomsIds - roomId - ) - } - session.getRoom(roomId)?.join(callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. @@ -142,32 +127,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, override fun onFailure(failure: Throwable) { // Notify the user _viewEvents.post(RoomListViewEvents.Failure(failure)) - setState { - copy( - joiningRoomsIds = joiningRoomsIds - roomId, - joiningErrorRoomsIds = joiningErrorRoomsIds + roomId - ) - } } }) } private fun handleRejectInvitation(action: RoomListAction.RejectInvitation) = withState { state -> val roomId = action.roomSummary.roomId - - if (state.joiningRoomsIds.contains(roomId) || state.rejectingRoomsIds.contains(roomId)) { + val roomMembershipChange = state.roomMembershipChanges[roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { // Request already sent, should not happen - Timber.w("Try to reject an already rejecting room. Should not happen") + Timber.w("Try to left an already leaving or joining room. Should not happen") return@withState } - setState { - copy( - rejectingRoomsIds = rejectingRoomsIds + roomId, - joiningErrorRoomsIds = joiningErrorRoomsIds - roomId - ) - } - session.getRoom(roomId)?.leave(null, object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. @@ -179,12 +151,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, override fun onFailure(failure: Throwable) { // Notify the user _viewEvents.post(RoomListViewEvents.Failure(failure)) - setState { - copy( - rejectingRoomsIds = rejectingRoomsIds - roomId, - rejectingErrorRoomsIds = rejectingErrorRoomsIds + roomId - ) - } } }) } @@ -235,6 +201,16 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, }) } + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .subscribe { + Timber.v("ChangeMembership states: $it") + setState { copy(roomMembershipChanges = it) } + } + .disposeOnClear() + } + private fun buildRoomSummaries(rooms: List): RoomSummaries { // Set up init size on directChats and groupRooms as they are the biggest ones val invites = ArrayList() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt index b41b4b9eeb..63f0cf2a1a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt @@ -20,6 +20,7 @@ import androidx.annotation.StringRes import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.R @@ -30,14 +31,7 @@ data class RoomListViewState( val asyncRooms: Async> = Uninitialized, val roomFilter: String = "", val asyncFilteredRooms: Async = Uninitialized, - // List of roomIds that the user wants to join - val joiningRoomsIds: Set = emptySet(), - // List of roomIds that the user wants to join, but an error occurred - val joiningErrorRoomsIds: Set = emptySet(), - // List of roomIds that the user wants to join - val rejectingRoomsIds: Set = emptySet(), - // List of roomIds that the user wants to reject, but an error occurred - val rejectingErrorRoomsIds: Set = emptySet(), + val roomMembershipChanges: Map = emptyMap(), val isInviteExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt index b06cb8a4bb..efa19d012b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.list import androidx.annotation.StringRes import com.airbnb.epoxy.EpoxyController +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.R @@ -72,10 +73,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri .filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) } buildRoomModels(filteredSummaries, - viewState.joiningRoomsIds, - viewState.joiningErrorRoomsIds, - viewState.rejectingRoomsIds, - viewState.rejectingErrorRoomsIds, + viewState.roomMembershipChanges, emptySet()) addFilterFooter(viewState) @@ -94,10 +92,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri } if (isExpanded) { buildRoomModels(summaries, - viewState.joiningRoomsIds, - viewState.joiningErrorRoomsIds, - viewState.rejectingRoomsIds, - viewState.rejectingErrorRoomsIds, + viewState.roomMembershipChanges, emptySet()) // Never set showHelp to true for invitation if (category != RoomCategory.INVITE) { @@ -153,18 +148,12 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri } private fun buildRoomModels(summaries: List, - joiningRoomsIds: Set, - joiningErrorRoomsIds: Set, - rejectingRoomsIds: Set, - rejectingErrorRoomsIds: Set, + roomChangedMembershipStates: Map, selectedRoomIds: Set) { summaries.forEach { roomSummary -> roomSummaryItemFactory .create(roomSummary, - joiningRoomsIds, - joiningErrorRoomsIds, - rejectingRoomsIds, - rejectingErrorRoomsIds, + roomChangedMembershipStates, selectedRoomIds, listener) .addTo(this) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index 1830899d80..f33166504d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.home.room.list import android.view.View +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.util.toMatrixItem @@ -39,23 +40,20 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor private val avatarRenderer: AvatarRenderer) { fun create(roomSummary: RoomSummary, - joiningRoomsIds: Set, - joiningErrorRoomsIds: Set, - rejectingRoomsIds: Set, - rejectingErrorRoomsIds: Set, + roomChangeMembershipStates: Map, selectedRoomIds: Set, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { return when (roomSummary.membership) { - Membership.INVITE -> createInvitationItem(roomSummary, joiningRoomsIds, joiningErrorRoomsIds, rejectingRoomsIds, rejectingErrorRoomsIds, listener) + Membership.INVITE -> { + val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown + createInvitationItem(roomSummary, changeMembershipState, listener) + } else -> createRoomItem(roomSummary, selectedRoomIds, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }) } } - fun createInvitationItem(roomSummary: RoomSummary, - joiningRoomsIds: Set, - joiningErrorRoomsIds: Set, - rejectingRoomsIds: Set, - rejectingErrorRoomsIds: Set, + private fun createInvitationItem(roomSummary: RoomSummary, + changeMembershipState: ChangeMembershipState, listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { val secondLine = if (roomSummary.isDirect) { roomSummary.inviterId @@ -70,10 +68,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor .avatarRenderer(avatarRenderer) .matrixItem(roomSummary.toMatrixItem()) .secondLine(secondLine) - .invitationAcceptInProgress(joiningRoomsIds.contains(roomSummary.roomId)) - .invitationAcceptInError(joiningErrorRoomsIds.contains(roomSummary.roomId)) - .invitationRejectInProgress(rejectingRoomsIds.contains(roomSummary.roomId)) - .invitationRejectInError(rejectingErrorRoomsIds.contains(roomSummary.roomId)) + .changeMembershipState(changeMembershipState) .acceptListener { listener?.onAcceptRoomInvitation(roomSummary) } .rejectListener { listener?.onRejectRoomInvitation(roomSummary) } .listener { listener?.onRoomClicked(roomSummary) } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt new file mode 100644 index 0000000000..88abf28888 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteButtonStateBinder.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 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.riotx.features.invite + +import androidx.core.view.isInvisible +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState +import im.vector.riotx.core.platform.ButtonStateView + +object InviteButtonStateBinder { + + fun bind( + acceptView: ButtonStateView, + rejectView: ButtonStateView, + changeMembershipState: ChangeMembershipState + ) { + // When a request is in progress (accept or reject), we only use the accept State button + // We check for isSuccessful, otherwise we get a glitch the time room summaries get rebuilt + + val requestInProgress = changeMembershipState.isInProgress() || changeMembershipState.isSuccessful() + when { + requestInProgress -> acceptView.render(ButtonStateView.State.Loading) + changeMembershipState is ChangeMembershipState.FailedJoining -> acceptView.render(ButtonStateView.State.Error) + else -> acceptView.render(ButtonStateView.State.Button) + } + // ButtonStateView.State.Loaded not used because roomSummary will not be displayed as a room invitation anymore + + rejectView.isInvisible = requestInProgress + + when { + changeMembershipState is ChangeMembershipState.FailedLeaving -> rejectView.render(ButtonStateView.State.Error) + else -> rejectView.render(ButtonStateView.State.Button) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt b/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt index b9bd9b0e1e..42f440fc30 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/VectorInviteView.kt @@ -21,10 +21,12 @@ import android.util.AttributeSet import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.updateLayoutParams +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.di.HasScreenInjector +import im.vector.riotx.core.platform.ButtonStateView import im.vector.riotx.features.home.AvatarRenderer import kotlinx.android.synthetic.main.vector_invite_view.view.* import javax.inject.Inject @@ -50,11 +52,28 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib context.injector().inject(this) } View.inflate(context, R.layout.vector_invite_view, this) - inviteRejectView.setOnClickListener { callback?.onRejectInvite() } - inviteAcceptView.setOnClickListener { callback?.onAcceptInvite() } + inviteAcceptView.callback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + callback?.onAcceptInvite() + } + + override fun onRetryClicked() { + callback?.onAcceptInvite() + } + } + + inviteRejectView.callback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + callback?.onRejectInvite() + } + + override fun onRetryClicked() { + callback?.onRejectInvite() + } + } } - fun render(sender: User, mode: Mode = Mode.LARGE) { + fun render(sender: User, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) { if (mode == Mode.LARGE) { updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT } avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView) @@ -68,5 +87,6 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib inviteNameView.visibility = View.GONE inviteLabelView.text = context.getString(R.string.invited_by, sender.userId) } + InviteButtonStateBinder.bind(inviteAcceptView, inviteRejectView, changeMembershipState) } } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 0b89ab8ec4..7b3eedc71f 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -29,6 +29,7 @@ import androidx.core.view.ViewCompat import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom +import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.session.widgets.model.Widget import im.vector.matrix.android.api.util.MatrixItem @@ -159,8 +160,8 @@ class DefaultNavigator @Inject constructor( activity.finish() } - override fun openRoomPreview(publicRoom: PublicRoom, context: Context) { - val intent = RoomPreviewActivity.getIntent(context, publicRoom) + override fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData) { + val intent = RoomPreviewActivity.getIntent(context, publicRoom, roomDirectoryData) context.startActivity(intent) } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index ce4d5ef3ea..08fd63b93c 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -22,6 +22,7 @@ import android.view.View import androidx.core.util.Pair import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom +import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData import im.vector.matrix.android.api.session.terms.TermsService import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.session.widgets.model.Widget @@ -48,7 +49,7 @@ interface Navigator { fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String? = null, buildTask: Boolean = false) - fun openRoomPreview(publicRoom: PublicRoom, context: Context) + fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData) fun openCreateRoom(context: Context, initialName: String = "") diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt index aaacb2a170..04ddccdd8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsController.kt @@ -21,6 +21,7 @@ import com.airbnb.epoxy.VisibilityState import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R @@ -89,13 +90,14 @@ class PublicRoomsController @Inject constructor(private val stringProvider: Stri roomTopic(publicRoom.topic) nbOfMembers(publicRoom.numJoinedMembers) + val roomChangeMembership = viewState.changeMembershipStates[publicRoom.roomId] ?: ChangeMembershipState.Unknown + val isJoined = viewState.joinedRoomsIds.contains(publicRoom.roomId) || roomChangeMembership is ChangeMembershipState.Joined val joinState = when { - viewState.joinedRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINED - viewState.joiningRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINING - viewState.joiningErrorRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINING_ERROR - else -> JoinState.NOT_JOINED + isJoined -> JoinState.JOINED + roomChangeMembership is ChangeMembershipState.Joining -> JoinState.JOINING + roomChangeMembership is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR + else -> JoinState.NOT_JOINED } - joinState(joinState) joinListener { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index 869ee85337..dcccd33cf6 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -114,26 +114,22 @@ class PublicRoomsFragment @Inject constructor( override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) { Timber.v("PublicRoomClicked: $publicRoom") - - when (joinState) { - JoinState.JOINED -> { - navigator.openRoom(requireActivity(), publicRoom.roomId) - } - JoinState.NOT_JOINED, - JoinState.JOINING_ERROR -> { - // ROOM PREVIEW - navigator.openRoomPreview(publicRoom, requireActivity()) - } - else -> { - Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT) - .show() + withState(viewModel) { state -> + when (joinState) { + JoinState.JOINED -> { + navigator.openRoom(requireActivity(), publicRoom.roomId) + } + else -> { + // ROOM PREVIEW + navigator.openRoomPreview(requireActivity(), publicRoom, state.roomDirectoryData) + } } } } override fun onPublicRoomJoin(publicRoom: PublicRoom) { Timber.v("PublicRoomJoinClicked: $publicRoom") - viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom.getPrimaryAlias(), publicRoom.roomId)) + viewModel.handle(RoomDirectoryAction.JoinRoom(publicRoom.roomId)) } override fun loadMore() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt index 665e37dcbd..67b17ea34e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt @@ -19,7 +19,9 @@ package im.vector.riotx.features.roomdirectory import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom +import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData data class PublicRoomsViewState( // The current filter @@ -30,11 +32,9 @@ data class PublicRoomsViewState( val asyncPublicRoomsRequest: Async> = Uninitialized, // True if more result are available server side val hasMore: Boolean = false, - // Set of roomIds that the user wants to join - val joiningRoomsIds: Set = emptySet(), - // Set of roomIds that the user wants to join, but an error occurred - val joiningErrorRoomsIds: Set = emptySet(), // Set of joined roomId, val joinedRoomsIds: Set = emptySet(), - val roomDirectoryDisplayName: String? = null + // keys are room alias or roomId + val changeMembershipStates: Map = emptyMap(), + val roomDirectoryData: RoomDirectoryData = RoomDirectoryData() ) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt index 598f26fc3b..8b32726370 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryAction.kt @@ -23,5 +23,5 @@ sealed class RoomDirectoryAction : VectorViewModelAction { data class SetRoomDirectoryData(val roomDirectoryData: RoomDirectoryData) : RoomDirectoryAction() data class FilterWith(val filter: String) : RoomDirectoryAction() object LoadMore : RoomDirectoryAction() - data class JoinRoom(val roomAlias: String?, val roomId: String) : RoomDirectoryAction() + data class JoinRoom(val roomId: String) : RoomDirectoryAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index 53661b075a..96de55a5b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -26,6 +26,7 @@ import com.airbnb.mvrx.appendAt import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.Membership @@ -63,18 +64,10 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: private var currentTask: Cancelable? = null - // Default RoomDirectoryData - private var roomDirectoryData = RoomDirectoryData() - init { - setState { - copy( - roomDirectoryDisplayName = roomDirectoryData.displayName - ) - } - // Observe joined room (from the sync) observeJoinedRooms() + observeMembershipChanges() } private fun observeJoinedRooms() { @@ -91,18 +84,21 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: ?: emptySet() setState { - copy( - joinedRoomsIds = joinedRoomIds, - // Remove (newly) joined room id from the joining room list - joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) }, - // Remove (newly) joined room id from the joining room list in error - joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { removeAll(joinedRoomIds) } - ) + copy(joinedRoomsIds = joinedRoomIds) } } .disposeOnClear() } + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .subscribe { + setState { copy(changeMembershipStates = it) } + } + .disposeOnClear() + } + override fun handle(action: RoomDirectoryAction) { when (action) { is RoomDirectoryAction.SetRoomDirectoryData -> setRoomDirectoryData(action) @@ -112,15 +108,15 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: } } - private fun setRoomDirectoryData(action: RoomDirectoryAction.SetRoomDirectoryData) { - if (this.roomDirectoryData == action.roomDirectoryData) { - return + private fun setRoomDirectoryData(action: RoomDirectoryAction.SetRoomDirectoryData) = withState { + if (it.roomDirectoryData == action.roomDirectoryData) { + return@withState + } + setState{ + copy(roomDirectoryData = action.roomDirectoryData) } - - this.roomDirectoryData = action.roomDirectoryData - reset("") - load("") + load("", action.roomDirectoryData) } private fun filterWith(action: RoomDirectoryAction.FilterWith) = withState { state -> @@ -128,7 +124,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: currentTask?.cancel() reset(action.filter) - load(action.filter) + load(action.filter, state.roomDirectoryData) } } @@ -141,7 +137,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: publicRooms = emptyList(), asyncPublicRoomsRequest = Loading(), hasMore = false, - roomDirectoryDisplayName = roomDirectoryData.displayName, currentFilter = newFilter ) } @@ -154,12 +149,11 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: asyncPublicRoomsRequest = Loading() ) } - - load(state.currentFilter) + load(state.currentFilter, state.roomDirectoryData) } } - private fun load(filter: String) { + private fun load(filter: String, roomDirectoryData: RoomDirectoryData) { currentTask = session.getPublicRooms(roomDirectoryData.homeServer, PublicRoomsParams( limit = PUBLIC_ROOMS_LIMIT, @@ -204,19 +198,16 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: } private fun joinRoom(action: RoomDirectoryAction.JoinRoom) = withState { state -> - if (state.joiningRoomsIds.contains(action.roomId)) { + val roomMembershipChange = state.changeMembershipStates[action.roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { // Request already sent, should not happen Timber.w("Try to join an already joining room. Should not happen") return@withState } - - setState { - copy( - joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(action.roomId) } - ) - } - - session.joinRoom(action.roomAlias ?: action.roomId, callback = object : MatrixCallback { + val viaServers = state.roomDirectoryData.homeServer?.let { + listOf(it) + } ?: emptyList() + session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined @@ -225,20 +216,12 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: override fun onFailure(failure: Throwable) { // Notify the user _viewEvents.post(RoomDirectoryViewEvents.Failure(failure)) - - setState { - copy( - joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(action.roomId) }, - joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(action.roomId) } - ) - } } }) } override fun onCleared() { super.onCleared() - currentTask?.cancel() } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt index 6b83ada90e..426078fa3d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewAction.kt @@ -19,5 +19,5 @@ package im.vector.riotx.features.roomdirectory.roompreview import im.vector.riotx.core.platform.VectorViewModelAction sealed class RoomPreviewAction : VectorViewModelAction { - data class Join(val roomAlias: String?) : RoomPreviewAction() + object Join : RoomPreviewAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt index 3cb442127f..063cf3b8ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Parcelable import androidx.appcompat.widget.Toolbar import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom +import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.extensions.addFragment @@ -35,7 +36,8 @@ data class RoomPreviewData( val roomAlias: String?, val topic: String?, val worldReadable: Boolean, - val avatarUrl: String? + val avatarUrl: String?, + val homeServer: String? ) : Parcelable { val matrixItem: MatrixItem get() = MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl) @@ -46,7 +48,7 @@ class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable { companion object { private const val ARG = "ARG" - fun getIntent(context: Context, publicRoom: PublicRoom): Intent { + fun getIntent(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData): Intent { return Intent(context, RoomPreviewActivity::class.java).apply { putExtra(ARG, RoomPreviewData( roomId = publicRoom.roomId, @@ -54,7 +56,8 @@ class RoomPreviewActivity : VectorBaseActivity(), ToolbarConfigurable { roomAlias = publicRoom.getPrimaryAlias(), topic = publicRoom.topic, worldReadable = publicRoom.worldReadable, - avatarUrl = publicRoom.avatarUrl + avatarUrl = publicRoom.avatarUrl, + homeServer = roomDirectoryData.homeServer )) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 04ecdb2305..ee01e8f7fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -65,7 +65,7 @@ class RoomPreviewNoPreviewFragment @Inject constructor( roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback { override fun onButtonClicked() { - roomPreviewViewModel.handle(RoomPreviewAction.Join(roomPreviewData.roomAlias)) + roomPreviewViewModel.handle(RoomPreviewAction.Join) } override fun onRetryClicked() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 3f8ae03029..c5e79832fc 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -22,7 +22,9 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.rx.rx @@ -32,7 +34,7 @@ import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.roomdirectory.JoinState import timber.log.Timber -class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: RoomPreviewViewState, +class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState, private val session: Session) : VectorViewModel(initialState) { @@ -52,30 +54,41 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R init { // Observe joined room (from the sync) - observeJoinedRooms() + observeRoomSummary() + observeMembershipChanges() } - private fun observeJoinedRooms() { + private fun observeRoomSummary() { val queryParams = roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) + roomId = QueryStringValue.Equals(initialState.roomId) } session .rx() .liveRoomSummaries(queryParams) .subscribe { list -> - withState { state -> - val isRoomJoined = list - ?.map { it.roomId } - ?.toList() - ?.contains(state.roomId) == true + val isRoomJoined = list.any { + it.membership == Membership.JOIN + } + if (isRoomJoined) { + setState { copy(roomJoinState = JoinState.JOINED) } + } + } + .disposeOnClear() + } - if (isRoomJoined) { - setState { - copy( - roomJoinState = JoinState.JOINED - ) - } - } + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .subscribe { + val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown + val joinState = when (changeMembership) { + is ChangeMembershipState.Joining -> JoinState.JOINING + is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR + // Other cases are handled by room summary + else -> null + } + if (joinState != null) { + setState { copy(roomJoinState = joinState) } } } .disposeOnClear() @@ -83,37 +96,27 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R override fun handle(action: RoomPreviewAction) { when (action) { - is RoomPreviewAction.Join -> handleJoinRoom(action) + is RoomPreviewAction.Join -> handleJoinRoom() }.exhaustive } - private fun handleJoinRoom(action: RoomPreviewAction.Join) = withState { state -> + private fun handleJoinRoom() = withState { state -> if (state.roomJoinState == JoinState.JOINING) { // Request already sent, should not happen Timber.w("Try to join an already joining room. Should not happen") return@withState } - - setState { - copy( - roomJoinState = JoinState.JOINING, - lastError = null - ) - } - - session.joinRoom(action.roomAlias ?: state.roomId, callback = object : MatrixCallback { + val viaServers = state.homeServer?.let { + listOf(it) + } ?: emptyList() + session.joinRoom(state.roomId, viaServers = viaServers, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined } override fun onFailure(failure: Throwable) { - setState { - copy( - roomJoinState = JoinState.JOINING_ERROR, - lastError = failure - ) - } + setState { copy(lastError = failure) } } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt index d3c75f95e0..04806ccf27 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewState.kt @@ -22,11 +22,21 @@ import im.vector.riotx.features.roomdirectory.JoinState data class RoomPreviewViewState( // The room id val roomId: String = "", + val roomAlias: String? = null, + /** + * The server name (might be null) + * Set null when the server is the current user's home server. + */ + val homeServer: String? = null, // Current state of the room in preview val roomJoinState: JoinState = JoinState.NOT_JOINED, // Last error of join room request val lastError: Throwable? = null ) : MvRxState { - constructor(args: RoomPreviewData) : this(roomId = args.roomId) + constructor(args: RoomPreviewData) : this( + roomId = args.roomId, + roomAlias = args.roomAlias, + homeServer = args.homeServer + ) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index aa414ec2a1..1ff5094517 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -112,7 +112,6 @@ class RoomProfileFragment @Inject constructor( when (it) { is RoomProfileViewEvents.Loading -> showLoading(it.message) is RoomProfileViewEvents.Failure -> showFailure(it.throwable) - is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() is RoomProfileViewEvents.ShareRoomProfile -> onShareRoomProfile(it.permalink) RoomProfileViewEvents.OnChangeAvatarSuccess -> dismissLoadingDialog() }.exhaustive diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt index 78df127f72..c0c1f2eb24 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt @@ -25,7 +25,6 @@ sealed class RoomProfileViewEvents : VectorViewEvents { data class Loading(val message: CharSequence? = null) : RoomProfileViewEvents() data class Failure(val throwable: Throwable) : RoomProfileViewEvents() - object OnLeaveRoomSuccess : RoomProfileViewEvents() object OnChangeAvatarSuccess : RoomProfileViewEvents() data class ShareRoomProfile(val permalink: String) : RoomProfileViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index 925ab716a4..d673422d06 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -98,7 +98,7 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted private val ini _viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room))) room.leave(null, object : MatrixCallback { override fun onSuccess(data: Unit) { - _viewEvents.post(RoomProfileViewEvents.OnLeaveRoomSuccess) + // Do nothing, we will be closing the room automatically when it will get back from sync } override fun onFailure(failure: Throwable) { diff --git a/vector/src/main/res/layout/vector_invite_view.xml b/vector/src/main/res/layout/vector_invite_view.xml index 5e557895c2..7356fcf64b 100644 --- a/vector/src/main/res/layout/vector_invite_view.xml +++ b/vector/src/main/res/layout/vector_invite_view.xml @@ -57,34 +57,35 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/inviteIdentifierView" /> - - - + +