Replace individual RoomProxy properties with a stored RoomInfo. (#3445)

* Store RoomInfo updates in JoinedRoomProxy and read from them directly.

* Remove all RoomProxy properties that were reading from the RoomInfo.
This commit is contained in:
Doug
2024-10-28 12:29:31 +00:00
committed by GitHub
parent 7c28d9709f
commit 7c75498b4d
29 changed files with 353 additions and 580 deletions
@@ -170,7 +170,8 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
return
}
await elementCallService.setupCallSession(roomID: roomProxy.id, roomDisplayName: roomProxy.roomTitle)
await elementCallService.setupCallSession(roomID: roomProxy.id,
roomDisplayName: roomProxy.infoPublisher.value.displayName ?? roomProxy.id)
_ = await roomProxy.sendCallNotificationIfNeeded()
@@ -360,10 +360,10 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return
}
if roomProxy.isPublic {
if roomProxy.infoPublisher.value.isPublic {
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isEncryptedOneToOneRoom, state: .public)
} else {
state.bindings.leaveRoomAlertItem = if roomProxy.joinedMembersCount > 1 {
state.bindings.leaveRoomAlertItem = if roomProxy.infoPublisher.value.joinedMembersCount > 1 {
LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isEncryptedOneToOneRoom, state: .private)
} else {
LeaveRoomAlertItem(roomID: roomID, isDM: roomProxy.isEncryptedOneToOneRoom, state: .empty)
@@ -408,7 +408,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
switch await roomProxy.acceptInvitation() {
case .success:
actionsSubject.send(.presentRoom(roomIdentifier: roomID))
analyticsService.trackJoinedRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace, activeMemberCount: UInt(roomProxy.activeMembersCount))
analyticsService.trackJoinedRoom(isDM: roomProxy.info.isDirect,
isSpace: roomProxy.info.isSpace,
activeMemberCount: UInt(roomProxy.info.activeMembersCount))
case .failure:
displayError()
}
@@ -74,7 +74,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
defer {
hideLoadingIndicator()
Task { await updateRoomDetails() }
updateRoomDetails()
}
await updateRoom()
@@ -82,7 +82,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
switch await clientProxy.roomPreviewForIdentifier(roomID, via: via) {
case .success(let roomPreviewDetails):
self.roomPreviewDetails = roomPreviewDetails
await updateRoomDetails()
updateRoomDetails()
case .failure(.roomPreviewIsPrivate):
break // Handled by the mode, we don't need an error indicator.
case .failure:
@@ -97,32 +97,32 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
// take priority over the preview one.
if let room = await clientProxy.roomForIdentifier(roomID) {
self.room = room
await updateRoomDetails()
updateRoomDetails()
}
}
private func updateRoomDetails() async {
var roomProxy: RoomProxyProtocol?
private func updateRoomDetails() {
var roomInfo: RoomInfoProxy?
var inviter: RoomInviterDetails?
switch room {
case .joined(let joinedRoomProxy):
roomProxy = joinedRoomProxy
roomInfo = joinedRoomProxy.infoPublisher.value
case .invited(let invitedRoomProxy):
inviter = await invitedRoomProxy.inviter.flatMap(RoomInviterDetails.init)
roomProxy = invitedRoomProxy
inviter = invitedRoomProxy.info.inviter.flatMap(RoomInviterDetails.init)
roomInfo = invitedRoomProxy.info
case .knocked(let knockedRoomProxy):
roomProxy = knockedRoomProxy
roomInfo = knockedRoomProxy.info
default:
break
}
let name = roomProxy?.name ?? roomPreviewDetails?.name
let name = roomInfo?.displayName ?? roomPreviewDetails?.name
state.roomDetails = JoinRoomScreenRoomDetails(name: name,
topic: roomProxy?.topic ?? roomPreviewDetails?.topic,
canonicalAlias: roomProxy?.canonicalAlias ?? roomPreviewDetails?.canonicalAlias,
avatar: roomProxy?.avatar ?? .room(id: roomID, name: name ?? "", avatarURL: roomPreviewDetails?.avatarURL),
memberCount: UInt(roomProxy?.activeMembersCount ?? Int(roomPreviewDetails?.memberCount ?? 0)),
topic: roomInfo?.topic ?? roomPreviewDetails?.topic,
canonicalAlias: roomInfo?.canonicalAlias ?? roomPreviewDetails?.canonicalAlias,
avatar: roomInfo?.avatar ?? .room(id: roomID, name: name ?? "", avatarURL: roomPreviewDetails?.avatarURL),
memberCount: UInt(roomInfo?.activeMembersCount ?? Int(roomPreviewDetails?.memberCount ?? 0)),
inviter: inviter)
updateMode()
@@ -143,7 +143,8 @@ class RoomChangeRolesScreenViewModel: RoomChangeRolesScreenViewModelType, RoomCh
let demotingUpdates = state.membersToDemote.map { ($0.id, Int64(0)) }
// A task we can await until the room's info gets modified with the new power levels.
let infoTask = Task { await roomProxy.actionsPublisher.values.first { $0 == .roomInfoUpdate } }
// Note: Ignore the first value as the publisher is backed by a current value subject.
let infoTask = Task { await roomProxy.infoPublisher.dropFirst().values.first { _ in true } }
switch await roomProxy.updatePowerLevelsForUsers(promotingUpdates + demotingUpdates) {
case .success:
@@ -28,9 +28,9 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe
self.mediaUploadingPreprocessor = mediaUploadingPreprocessor
self.userIndicatorController = userIndicatorController
let roomAvatar = roomProxy.avatarURL
let roomName = roomProxy.name
let roomTopic = roomProxy.topic
let roomAvatar = roomProxy.infoPublisher.value.avatarURL
let roomName = roomProxy.infoPublisher.value.displayName
let roomTopic = roomProxy.infoPublisher.value.topic
super.init(initialViewState: RoomDetailsEditScreenViewState(roomID: roomProxy.id,
initialAvatarURL: roomAvatar,
@@ -63,14 +63,14 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
self.attributedStringBuilder = attributedStringBuilder
self.appSettings = appSettings
let topic = attributedStringBuilder.fromPlain(roomProxy.topic)
let topic = attributedStringBuilder.fromPlain(roomProxy.infoPublisher.value.topic)
super.init(initialViewState: .init(details: roomProxy.details,
isEncrypted: roomProxy.isEncrypted,
isDirect: roomProxy.isDirect,
isDirect: roomProxy.infoPublisher.value.isDirect,
topic: topic,
topicSummary: topic?.unattributedStringByReplacingNewlinesWithSpaces(),
joinedMembersCount: roomProxy.joinedMembersCount,
joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
notificationSettingsState: .loading,
bindings: .init()),
mediaProvider: mediaProvider)
@@ -96,7 +96,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
}
}
updateRoomInfo()
updateRoomInfo(roomProxy.infoPublisher.value)
Task { await updatePowerLevelPermissions() }
setupRoomSubscription()
@@ -124,7 +124,9 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id, isDM: roomProxy.isEncryptedOneToOneRoom, state: .empty)
return
}
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id, isDM: roomProxy.isEncryptedOneToOneRoom, state: roomProxy.isPublic ? .public : .private)
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(roomID: roomProxy.id,
isDM: roomProxy.isEncryptedOneToOneRoom,
state: roomProxy.infoPublisher.value.isPublic ? .public : .private)
case .confirmLeave:
Task { await leaveRoom() }
case .processTapIgnore:
@@ -162,29 +164,25 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
}
// MARK: - Private
private func setupRoomSubscription() {
roomProxy.actionsPublisher
.filter { $0 == .roomInfoUpdate }
roomProxy.infoPublisher
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in
self?.updateRoomInfo()
.sink { [weak self] roomInfo in
self?.updateRoomInfo(roomInfo)
Task { await self?.updatePowerLevelPermissions() }
}
.store(in: &cancellables)
}
private func updateRoomInfo() {
private func updateRoomInfo(_ roomInfo: RoomInfoProxy) {
state.details = roomProxy.details
let topic = attributedStringBuilder.fromPlain(roomProxy.topic)
let topic = attributedStringBuilder.fromPlain(roomInfo.topic)
state.topic = topic
state.topicSummary = topic?.unattributedStringByReplacingNewlinesWithSpaces()
state.joinedMembersCount = roomProxy.joinedMembersCount
Task {
state.bindings.isFavourite = await roomProxy.isFavourite
}
state.joinedMembersCount = roomInfo.joinedMembersCount
state.bindings.isFavourite = roomInfo.isFavourite
}
private func fetchMembersIfNeeded() async {
@@ -240,7 +238,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
do {
let notificationMode = try await notificationSettingsProxy.getNotificationSettings(roomId: roomProxy.id,
isEncrypted: roomProxy.isEncrypted,
isOneToOne: roomProxy.activeMembersCount == 2)
isOneToOne: roomProxy.infoPublisher.value.activeMembersCount == 2)
state.notificationSettingsState = .loaded(settings: notificationMode)
} catch {
state.notificationSettingsState = .error
@@ -258,7 +256,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
do {
try await notificationSettingsProxy.unmuteRoom(roomId: roomProxy.id,
isEncrypted: roomProxy.isEncrypted,
isOneToOne: roomProxy.activeMembersCount == 2)
isOneToOne: roomProxy.infoPublisher.value.activeMembersCount == 2)
} catch {
state.bindings.alertInfo = AlertInfo(id: .alert,
title: L10n.commonError,
@@ -352,7 +350,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
// We don't actually know the mime type here, assume it's an image.
if case let .success(file) = await mediaProvider.loadFileFromSource(.init(url: url, mimeType: "image/jpeg")) {
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: roomProxy.roomTitle)
state.bindings.mediaPreviewItem = MediaPreviewItem(file: file, title: roomProxy.infoPublisher.value.displayName)
}
}
}
@@ -32,7 +32,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
self.userIndicatorController = userIndicatorController
self.analytics = analytics
super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount,
super.init(initialViewState: .init(joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
bindings: .init(mode: initialMode)),
mediaProvider: mediaProvider)
@@ -92,7 +92,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
let members = members.sorted()
let roomMembersDetails = await buildMembersDetails(members: members)
self.members = members
self.state = .init(joinedMembersCount: roomProxy.joinedMembersCount,
self.state = .init(joinedMembersCount: roomProxy.infoPublisher.value.joinedMembersCount,
joinedMembers: roomMembersDetails.joinedMembers,
invitedMembers: roomMembersDetails.invitedMembers,
bannedMembers: roomMembersDetails.bannedMembers,
@@ -26,11 +26,11 @@ class RoomNotificationSettingsScreenViewModel: RoomNotificationSettingsScreenVie
let bindings = RoomNotificationSettingsScreenViewStateBindings()
self.notificationSettingsProxy = notificationSettingsProxy
self.roomProxy = roomProxy
let navigationTitle = displayAsUserDefinedRoomSettings ? roomProxy.roomTitle : L10n.screenRoomDetailsNotificationTitle
let navigationTitle = displayAsUserDefinedRoomSettings ? roomProxy.infoPublisher.value.displayName : L10n.screenRoomDetailsNotificationTitle
let customSettingsSectionHeader = displayAsUserDefinedRoomSettings ? L10n.screenRoomNotificationSettingsRoomCustomSettingsTitle : L10n.screenRoomNotificationSettingsCustomSettingsTitle
super.init(initialViewState: RoomNotificationSettingsScreenViewState(bindings: bindings,
displayAsUserDefinedRoomSettings: displayAsUserDefinedRoomSettings,
navigationTitle: navigationTitle,
navigationTitle: navigationTitle ?? L10n.screenRoomDetailsNotificationTitle,
customSettingsSectionHeader: customSettingsSectionHeader))
setupNotificationSettingsSubscription()
@@ -80,7 +80,7 @@ class RoomNotificationSettingsScreenViewModel: RoomNotificationSettingsScreenVie
// `isOneToOne` here is not the same as `isDirect` on the room. From the point of view of the push rule, a one-to-one room is a room with exactly two active members.
let settings = try await notificationSettingsProxy.getNotificationSettings(roomId: roomProxy.id,
isEncrypted: roomProxy.isEncrypted,
isOneToOne: roomProxy.activeMembersCount == 2)
isOneToOne: roomProxy.infoPublisher.value.activeMembersCount == 2)
guard !Task.isCancelled else { return }
state.notificationSettingsState = .loaded(settings: settings)
if !state.isRestoringDefaultSetting {
@@ -37,8 +37,7 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
updateMembers(roomProxy.membersPublisher.value)
// Automatically update the room permissions
roomProxy.actionsPublisher
.filter { $0 == .roomInfoUpdate }
roomProxy.infoPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
Task { await self?.updatePermissions() }
@@ -93,7 +92,8 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
showSavingIndicator()
// A task we can await until the room's info gets modified with the new power levels.
let infoTask = Task { await roomProxy.actionsPublisher.values.first { $0 == .roomInfoUpdate } }
// Note: Ignore the first value as the publisher is backed by a current value subject.
let infoTask = Task { await roomProxy.infoPublisher.dropFirst().values.first { _ in true } }
switch await roomProxy.updatePowerLevelsForUsers([(userID: roomProxy.ownUserID, powerLevel: role.rustPowerLevel)]) {
case .success:
@@ -51,7 +51,7 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
membersSuggestion
.insert(SuggestionItem.allUsers(item: .init(id: PillConstants.atRoom,
displayName: PillConstants.everyone,
avatarURL: self.roomProxy.avatarURL,
avatarURL: self.roomProxy.infoPublisher.value.avatarURL,
range: suggestionTrigger.range)), at: 0)
}
@@ -68,14 +68,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
self.initialSelectedPinnedEventID = initialSelectedPinnedEventID
pinnedEventStringBuilder = .pinnedEventStringBuilder(userID: roomProxy.ownUserID)
super.init(initialViewState: .init(roomTitle: roomProxy.roomTitle,
roomAvatar: roomProxy.avatar,
hasOngoingCall: roomProxy.hasOngoingCall,
super.init(initialViewState: .init(roomTitle: roomProxy.infoPublisher.value.displayName ?? roomProxy.id,
roomAvatar: roomProxy.infoPublisher.value.avatar,
hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall,
bindings: .init()),
mediaProvider: mediaProvider)
Task {
await handleRoomInfoUpdate()
await handleRoomInfoUpdate(roomProxy.infoPublisher.value)
}
setupSubscriptions(ongoingCallRoomIDPublisher: ongoingCallRoomIDPublisher)
@@ -118,26 +118,25 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private func setupSubscriptions(ongoingCallRoomIDPublisher: CurrentValuePublisher<String?, Never>) {
let roomInfoSubscription = roomProxy
.actionsPublisher
.filter { $0 == .roomInfoUpdate }
.infoPublisher
roomInfoSubscription
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] _ in
.sink { [weak self] roomInfo in
guard let self else { return }
state.roomTitle = roomProxy.roomTitle
state.roomAvatar = roomProxy.avatar
state.hasOngoingCall = roomProxy.hasOngoingCall
state.roomTitle = roomInfo.displayName ?? roomProxy.id
state.roomAvatar = roomInfo.avatar
state.hasOngoingCall = roomInfo.hasRoomCall
}
.store(in: &cancellables)
Task { [weak self] in
for await _ in roomInfoSubscription.receive(on: DispatchQueue.main).values {
for await roomInfo in roomInfoSubscription.receive(on: DispatchQueue.main).values {
guard !Task.isCancelled else {
return
}
await self?.handleRoomInfoUpdate()
await self?.handleRoomInfoUpdate(roomInfo)
}
}
.store(in: &cancellables)
@@ -230,8 +229,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
}
}
private func handleRoomInfoUpdate() async {
let pinnedEventIDs = await roomProxy.pinnedEventIDs
private func handleRoomInfoUpdate(_ roomInfo: RoomInfoProxy) async {
let pinnedEventIDs = roomInfo.pinnedEventIDs
// Only update the loading state of the banner
if state.pinnedEventsBannerState.isLoading {
state.pinnedEventsBannerState = .loading(numbersOfEvents: pinnedEventIDs.count)
@@ -131,7 +131,7 @@ class NotificationSettingsEditScreenViewModel: NotificationSettingsEditScreenVie
for roomSummary in filteredRoomsSummary {
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomSummary.id) else { continue }
// `isOneToOneRoom` here is not the same as `isDirect` on the room. From the point of view of the push rule, a one-to-one room is a room with exactly two active members.
let isOneToOneRoom = roomProxy.activeMembersCount == 2
let isOneToOneRoom = roomProxy.infoPublisher.value.activeMembersCount == 2
// display only the rooms we're interested in
switch chatType {
case .oneToOneChat where isOneToOneRoom, .groupChat where !isOneToOneRoom:
@@ -82,6 +82,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
ownUserID: roomProxy.ownUserID,
isViewSourceEnabled: appSettings.viewSourceEnabled,
hideTimelineMedia: appSettings.hideTimelineMedia,
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
bindings: .init(reactionsCollapsed: [:]),
emojiProvider: emojiProvider),
mediaProvider: mediaProvider)
@@ -91,10 +92,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
showFocusLoadingIndicator()
}
Task {
await updatePinnedEventIDs()
}
setupSubscriptions()
setupDirectRoomSubscriptionsIfNeeded()
@@ -380,15 +377,13 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
}
.store(in: &cancellables)
let roomInfoSubscription = roomProxy
.actionsPublisher
.filter { $0 == .roomInfoUpdate }
let roomInfoSubscription = roomProxy.infoPublisher
Task { [weak self] in
for await _ in roomInfoSubscription.receive(on: DispatchQueue.main).values {
for await roomInfo in roomInfoSubscription.receive(on: DispatchQueue.main).values {
guard !Task.isCancelled else {
return
}
await self?.updatePinnedEventIDs()
self?.state.pinnedEventIDs = roomInfo.pinnedEventIDs
await self?.updatePermissions()
}
}
@@ -456,13 +451,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
.weakAssign(to: \.state.hideTimelineMedia, on: self)
.store(in: &cancellables)
}
private func updatePinnedEventIDs() async {
state.pinnedEventIDs = await roomProxy.pinnedEventIDs
}
private func setupDirectRoomSubscriptionsIfNeeded() {
guard roomProxy.isDirect else {
guard roomProxy.infoPublisher.value.isDirect else {
return
}
@@ -471,7 +462,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
.map { [weak self] isFocused in
guard let self else { return false }
return isFocused && self.roomProxy.isUserAloneInDirectRoom
return isFocused && self.roomProxy.infoPublisher.value.isUserAloneInDirectRoom
}
// We want to show the alert just once, so we are taking the first "true" emitted
.first { $0 }
@@ -742,7 +733,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
private let inviteLoadingIndicatorID = UUID().uuidString
private func inviteOtherDMUserBack() {
guard roomProxy.isUserAloneInDirectRoom else {
guard roomProxy.infoPublisher.value.isUserAloneInDirectRoom else {
userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError)
return
}
@@ -848,7 +839,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
}
}
private extension RoomProxyProtocol {
private extension RoomInfoProxy {
/// Checks if the other person left the room in a direct chat
var isUserAloneInDirectRoom: Bool {
isDirect && activeMembersCount == 1