Advance the join room screen when the server joins us to the room

The homeserver can join a user without a join request from their client,
auto-accepting a knock (element-hq/synapse#16307) or an invite
accepted on another device. The join room screen only navigated on a
user-initiated join and only observed membership changes while knocked,
so a server-side join left it sitting on a stale invited state.
This commit is contained in:
Matthew Hodgson
2026-07-14 02:21:48 +01:00
committed by Stefan Ceriu
parent c8b12d9496
commit c812b723dc
@@ -22,6 +22,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
private var room: RoomProxyType?
private var isLoadingPreview = true
private var membershipStateChangeCancellable: AnyCancellable?
private var hasSentJoinAction = false
private let actionsSubject: PassthroughSubject<JoinRoomScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<JoinRoomScreenViewModelAction, Never> {
@@ -148,21 +149,10 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
case .invited(let invitedRoomProxy):
inviter = invitedRoomProxy.inviter.map(RoomInviterDetails.init)
roomInfo = invitedRoomProxy.info
watchForMembershipChange(from: .invited)
case .knocked(let knockedRoomProxy):
roomInfo = knockedRoomProxy.info
membershipStateChangeCancellable = clientProxy
.staticRoomSummaryProvider
.roomListPublisher
.compactMap { summaries -> Void? in
guard let roomSummary = summaries.first(where: { $0.id == roomInfo?.id }),
roomSummary.room.membership() != .knocked else {
return nil
}
return ()
}
.sink { [weak self] in
Task { await self?.loadRoomDetails() }
}
watchForMembershipChange(from: .knocked)
case .banned(let bannedRoomProxy):
roomInfo = bannedRoomProxy.info
default:
@@ -176,6 +166,32 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
await updateSpaceRoomDetails(spaceServiceRoom: spaceServiceRoom, inviter: inviter)
}
await updateMode()
// The homeserver can join us to the room without a join request from this client:
// it auto-joined us after our knock was accepted
// (https://github.com/element-hq/synapse/issues/16307), or the invite was accepted
// on another of our devices. Advance into the room as if the join button had been
// tapped, matching Android's RoomFlowNode.
if case .joined = room {
await finishJoinAction()
}
}
private func watchForMembershipChange(from membership: Membership) {
let roomID = state.roomID
membershipStateChangeCancellable = clientProxy
.staticRoomSummaryProvider
.roomListPublisher
.compactMap { summaries -> Void? in
guard let roomSummary = summaries.first(where: { $0.id == roomID }),
roomSummary.room.membership() != membership else {
return nil
}
return ()
}
.sink { [weak self] in
Task { await self?.loadRoomDetails() }
}
}
private func updateGenericRoomDetails(roomID: String, roomInfo: BaseRoomInfoProxyProtocol?, inviter: RoomInviterDetails?) async {
@@ -329,16 +345,22 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
}
private func finishJoinAction() async {
// A user-initiated join and a server-side one can complete concurrently:
// only ever advance once.
guard !hasSentJoinAction else { return }
let roomID = state.roomID
appSettings.seenInvites.remove(roomID)
guard state.roomDetails?.isSpace == true else {
hasSentJoinAction = true
actionsSubject.send(.joined(.roomID(roomID)))
return
}
switch await clientProxy.spaceService.spaceRoomList(spaceID: roomID) {
case .success(let spaceRoomListProxy):
hasSentJoinAction = true
actionsSubject.send(.joined(.space(spaceRoomListProxy)))
case .failure(let error):
MXLog.error("Failed to get the space room list after joining: \(error)")