diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index d762d3b043..62b5b0931c 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -412,6 +412,7 @@ 5BC6C4ADFE7F2A795ECDE130 /* SecureBackupKeyBackupScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */; }; 5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; }; 5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C616D90B1E2F033CAA325439 /* StaticLocationScreenViewModelProtocol.swift */; }; + 5C8804B4F25903516E2DAB81 /* RoomInfoProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */; }; 5D27B6537591471A42C89027 /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450E04B2A976CC4C8CC1807C /* EmoteRoomTimelineItem.swift */; }; 5D52925FEB1B780C65B0529F /* PinnedEventsTimelineScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA4F6D7000EDCD187E0989E7 /* PinnedEventsTimelineScreen.swift */; }; 5D53AE9342A4C06B704247ED /* MediaLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A02406480C351B8C6E0682C /* MediaLoaderProtocol.swift */; }; @@ -1495,6 +1496,7 @@ 40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManager.swift; sourceTree = ""; }; 40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelTests.swift; sourceTree = ""; }; 406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceConstants.swift; sourceTree = ""; }; + 40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoProxy.swift; sourceTree = ""; }; 40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 4100DDE6BF3C566AB66B80CC /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = ""; }; 4137900E28201C314C835C11 /* RoomScreenFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenFooterView.swift; sourceTree = ""; }; @@ -3211,6 +3213,7 @@ 07C6B0B087FE6601C3F77816 /* JoinedRoomProxy.swift */, 858DA81F2ACF484B7CAD6AE4 /* KnockedRoomProxy.swift */, B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */, + 40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */, 974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */, 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */, 2C0F49BD446849654C0D24E0 /* RoomMember */, @@ -6775,6 +6778,7 @@ 42F1C8731166633E35A6D7E6 /* RoomEventStringBuilder.swift in Sources */, D55AF9B5B55FEED04771A461 /* RoomFlowCoordinator.swift in Sources */, 9C63171267E22FEB288EC860 /* RoomHeaderView.swift in Sources */, + 5C8804B4F25903516E2DAB81 /* RoomInfoProxy.swift in Sources */, 8A83D715940378B9BA9F739E /* RoomInviterLabel.swift in Sources */, F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */, 4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index b5af3ad085..89faaa1c86 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -584,7 +584,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { timelineItemFactory: timelineItemFactory) self.timelineController = timelineController - analytics.trackViewRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace) + analytics.trackViewRoom(isDM: roomProxy.infoPublisher.value.isDirect, isSpace: roomProxy.infoPublisher.value.isSpace) let completionSuggestionService = CompletionSuggestionService(roomProxy: roomProxy) @@ -681,7 +681,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { await storeAndSubscribeToRoomProxy(roomProxy) stateMachine.tryEvent(.presentRoom(focussedEvent: nil), userInfo: EventUserInfo(animated: animated)) - analytics.trackJoinedRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace, activeMemberCount: UInt(roomProxy.activeMembersCount)) + analytics.trackJoinedRoom(isDM: roomProxy.infoPublisher.value.isDirect, + isSpace: roomProxy.infoPublisher.value.isSpace, + activeMemberCount: UInt(roomProxy.infoPublisher.value.activeMembersCount)) } else { stateMachine.tryEvent(.dismissFlow, userInfo: EventUserInfo(animated: animated)) } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 9755771e0b..2438f6b7f3 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -5824,67 +5824,21 @@ class ElementCallWidgetDriverMock: ElementCallWidgetDriverProtocol { } } class InvitedRoomProxyMock: InvitedRoomProxyProtocol { - var inviterCallsCount = 0 - var inviterCalled: Bool { - return inviterCallsCount > 0 + var info: RoomInfoProxy { + get { return underlyingInfo } + set(value) { underlyingInfo = value } } - - var inviter: RoomMemberProxyProtocol? { - get async { - inviterCallsCount += 1 - if let inviterClosure = inviterClosure { - return await inviterClosure() - } else { - return underlyingInviter - } - } - } - var underlyingInviter: RoomMemberProxyProtocol? - var inviterClosure: (() async -> RoomMemberProxyProtocol?)? + var underlyingInfo: RoomInfoProxy! var id: String { get { return underlyingId } set(value) { underlyingId = value } } var underlyingId: String! - var canonicalAlias: String? var ownUserID: String { get { return underlyingOwnUserID } set(value) { underlyingOwnUserID = value } } var underlyingOwnUserID: String! - var name: String? - var topic: String? - var avatar: RoomAvatar { - get { return underlyingAvatar } - set(value) { underlyingAvatar = value } - } - var underlyingAvatar: RoomAvatar! - var avatarURL: URL? - var isPublic: Bool { - get { return underlyingIsPublic } - set(value) { underlyingIsPublic = value } - } - var underlyingIsPublic: Bool! - var isDirect: Bool { - get { return underlyingIsDirect } - set(value) { underlyingIsDirect = value } - } - var underlyingIsDirect: Bool! - var isSpace: Bool { - get { return underlyingIsSpace } - set(value) { underlyingIsSpace = value } - } - var underlyingIsSpace: Bool! - var joinedMembersCount: Int { - get { return underlyingJoinedMembersCount } - set(value) { underlyingJoinedMembersCount = value } - } - var underlyingJoinedMembersCount: Int! - var activeMembersCount: Int { - get { return underlyingActiveMembersCount } - set(value) { underlyingActiveMembersCount = value } - } - var underlyingActiveMembersCount: Int! //MARK: - rejectInvitation @@ -6021,46 +5975,11 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol { set(value) { underlyingIsEncrypted = value } } var underlyingIsEncrypted: Bool! - var isFavouriteCallsCount = 0 - var isFavouriteCalled: Bool { - return isFavouriteCallsCount > 0 - } - - var isFavourite: Bool { - get async { - isFavouriteCallsCount += 1 - if let isFavouriteClosure = isFavouriteClosure { - return await isFavouriteClosure() - } else { - return underlyingIsFavourite - } - } - } - var underlyingIsFavourite: Bool! - var isFavouriteClosure: (() async -> Bool)? - var pinnedEventIDsCallsCount = 0 - var pinnedEventIDsCalled: Bool { - return pinnedEventIDsCallsCount > 0 + var infoPublisher: CurrentValuePublisher { + get { return underlyingInfoPublisher } + set(value) { underlyingInfoPublisher = value } } - - var pinnedEventIDs: Set { - get async { - pinnedEventIDsCallsCount += 1 - if let pinnedEventIDsClosure = pinnedEventIDsClosure { - return await pinnedEventIDsClosure() - } else { - return underlyingPinnedEventIDs - } - } - } - var underlyingPinnedEventIDs: Set! - var pinnedEventIDsClosure: (() async -> Set)? - var hasOngoingCall: Bool { - get { return underlyingHasOngoingCall } - set(value) { underlyingHasOngoingCall = value } - } - var underlyingHasOngoingCall: Bool! - var activeRoomCallParticipants: [String] = [] + var underlyingInfoPublisher: CurrentValuePublisher! var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get { return underlyingMembersPublisher } set(value) { underlyingMembersPublisher = value } @@ -6076,11 +5995,6 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol { set(value) { underlyingIdentityStatusChangesPublisher = value } } var underlyingIdentityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never>! - var actionsPublisher: AnyPublisher { - get { return underlyingActionsPublisher } - set(value) { underlyingActionsPublisher = value } - } - var underlyingActionsPublisher: AnyPublisher! var timeline: TimelineProxyProtocol { get { return underlyingTimeline } set(value) { underlyingTimeline = value } @@ -6108,45 +6022,11 @@ class JoinedRoomProxyMock: JoinedRoomProxyProtocol { set(value) { underlyingId = value } } var underlyingId: String! - var canonicalAlias: String? var ownUserID: String { get { return underlyingOwnUserID } set(value) { underlyingOwnUserID = value } } var underlyingOwnUserID: String! - var name: String? - var topic: String? - var avatar: RoomAvatar { - get { return underlyingAvatar } - set(value) { underlyingAvatar = value } - } - var underlyingAvatar: RoomAvatar! - var avatarURL: URL? - var isPublic: Bool { - get { return underlyingIsPublic } - set(value) { underlyingIsPublic = value } - } - var underlyingIsPublic: Bool! - var isDirect: Bool { - get { return underlyingIsDirect } - set(value) { underlyingIsDirect = value } - } - var underlyingIsDirect: Bool! - var isSpace: Bool { - get { return underlyingIsSpace } - set(value) { underlyingIsSpace = value } - } - var underlyingIsSpace: Bool! - var joinedMembersCount: Int { - get { return underlyingJoinedMembersCount } - set(value) { underlyingJoinedMembersCount = value } - } - var underlyingJoinedMembersCount: Int! - var activeMembersCount: Int { - get { return underlyingActiveMembersCount } - set(value) { underlyingActiveMembersCount = value } - } - var underlyingActiveMembersCount: Int! //MARK: - subscribeForUpdates @@ -9752,50 +9632,21 @@ class KeychainControllerMock: KeychainControllerProtocol { } } class KnockedRoomProxyMock: KnockedRoomProxyProtocol { + var info: RoomInfoProxy { + get { return underlyingInfo } + set(value) { underlyingInfo = value } + } + var underlyingInfo: RoomInfoProxy! var id: String { get { return underlyingId } set(value) { underlyingId = value } } var underlyingId: String! - var canonicalAlias: String? var ownUserID: String { get { return underlyingOwnUserID } set(value) { underlyingOwnUserID = value } } var underlyingOwnUserID: String! - var name: String? - var topic: String? - var avatar: RoomAvatar { - get { return underlyingAvatar } - set(value) { underlyingAvatar = value } - } - var underlyingAvatar: RoomAvatar! - var avatarURL: URL? - var isPublic: Bool { - get { return underlyingIsPublic } - set(value) { underlyingIsPublic = value } - } - var underlyingIsPublic: Bool! - var isDirect: Bool { - get { return underlyingIsDirect } - set(value) { underlyingIsDirect = value } - } - var underlyingIsDirect: Bool! - var isSpace: Bool { - get { return underlyingIsSpace } - set(value) { underlyingIsSpace = value } - } - var underlyingIsSpace: Bool! - var joinedMembersCount: Int { - get { return underlyingJoinedMembersCount } - set(value) { underlyingJoinedMembersCount = value } - } - var underlyingJoinedMembersCount: Int! - var activeMembersCount: Int { - get { return underlyingActiveMembersCount } - set(value) { underlyingActiveMembersCount = value } - } - var underlyingActiveMembersCount: Int! //MARK: - cancelKnock @@ -12934,45 +12785,11 @@ class RoomProxyMock: RoomProxyProtocol { set(value) { underlyingId = value } } var underlyingId: String! - var canonicalAlias: String? var ownUserID: String { get { return underlyingOwnUserID } set(value) { underlyingOwnUserID = value } } var underlyingOwnUserID: String! - var name: String? - var topic: String? - var avatar: RoomAvatar { - get { return underlyingAvatar } - set(value) { underlyingAvatar = value } - } - var underlyingAvatar: RoomAvatar! - var avatarURL: URL? - var isPublic: Bool { - get { return underlyingIsPublic } - set(value) { underlyingIsPublic = value } - } - var underlyingIsPublic: Bool! - var isDirect: Bool { - get { return underlyingIsDirect } - set(value) { underlyingIsDirect = value } - } - var underlyingIsDirect: Bool! - var isSpace: Bool { - get { return underlyingIsSpace } - set(value) { underlyingIsSpace = value } - } - var underlyingIsSpace: Bool! - var joinedMembersCount: Int { - get { return underlyingJoinedMembersCount } - set(value) { underlyingJoinedMembersCount = value } - } - var underlyingJoinedMembersCount: Int! - var activeMembersCount: Int { - get { return underlyingActiveMembersCount } - set(value) { underlyingActiveMembersCount = value } - } - var underlyingActiveMembersCount: Int! } class RoomSummaryProviderMock: RoomSummaryProviderProtocol { diff --git a/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift b/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift index 5155a6febd..66bc9a93f2 100644 --- a/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift @@ -7,6 +7,7 @@ import Combine import Foundation +import MatrixRustSDK @MainActor struct InvitedRoomProxyMockConfiguration { @@ -22,10 +23,55 @@ extension InvitedRoomProxyMock { convenience init(_ configuration: InvitedRoomProxyMockConfiguration) { self.init() id = configuration.id - name = configuration.name - avatarURL = configuration.avatarURL - avatar = .room(id: configuration.id, name: configuration.name, avatarURL: configuration.avatarURL) // Note: This doesn't replicate the real proxy logic. - underlyingInviter = configuration.inviter - activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count + info = RoomInfoProxy(roomInfo: .init(configuration)) + } +} + +extension RoomInfo { + @MainActor init(_ configuration: InvitedRoomProxyMockConfiguration) { + self.init(id: configuration.id, + creator: nil, + displayName: configuration.name, + rawName: nil, + topic: nil, + avatarUrl: configuration.avatarURL?.absoluteString, + isDirect: false, + isPublic: false, + isSpace: false, + isTombstoned: false, + isFavourite: false, + canonicalAlias: nil, + alternativeAliases: [], + membership: .knocked, + inviter: .init(configuration.inviter), + heroes: [], + activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), + invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), + joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), + userPowerLevels: [:], + highlightCount: 0, + notificationCount: 0, + cachedUserDefinedNotificationMode: nil, + hasRoomCall: false, + activeRoomCallParticipants: [], + isMarkedUnread: false, + numUnreadMessages: 0, + numUnreadNotifications: 0, + numUnreadMentions: 0, + pinnedEventIds: []) + } +} + +private extension RoomMember { + init(_ proxy: RoomMemberProxyProtocol) { + self.init(userId: proxy.userID, + displayName: proxy.displayName, + avatarUrl: proxy.avatarURL?.absoluteString, + membership: proxy.membership, + isNameAmbiguous: proxy.disambiguatedDisplayName != proxy.displayName, + powerLevel: Int64(proxy.powerLevel), + normalizedPowerLevel: Int64(proxy.powerLevel), + isIgnored: proxy.isIgnored, + suggestedRoleForPowerLevel: proxy.role) } } diff --git a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift index 0855235a08..2f147104fb 100644 --- a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift @@ -7,6 +7,7 @@ import Combine import Foundation +import MatrixRustSDK enum RoomProxyMockError: Error { case generic @@ -46,18 +47,7 @@ extension JoinedRoomProxyMock { self.init() id = configuration.id - name = configuration.name - topic = configuration.topic - avatar = .room(id: configuration.id, name: configuration.name, avatarURL: configuration.avatarURL) // Note: This doesn't replicate the real proxy logic. - avatarURL = configuration.avatarURL - isDirect = configuration.isDirect - isSpace = configuration.isSpace - isPublic = configuration.isPublic isEncrypted = configuration.isEncrypted - hasOngoingCall = configuration.hasOngoingCall - canonicalAlias = configuration.canonicalAlias - - underlyingPinnedEventIDs = configuration.pinnedEventIDs let timeline = TimelineProxyMock() timeline.sendMessageEventContentReturnValue = .success(()) @@ -78,15 +68,12 @@ extension JoinedRoomProxyMock { ownUserID = configuration.ownUserID + infoPublisher = CurrentValueSubject(.init(roomInfo: .init(configuration))).asCurrentValuePublisher() membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher() typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher() identityStatusChangesPublisher = CurrentValueSubject([]).asCurrentValuePublisher() - - joinedMembersCount = configuration.members.filter { $0.membership == .join }.count - activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count updateMembersClosure = { } - underlyingActionsPublisher = Empty(completeImmediately: false).eraseToAnyPublisher() setNameClosure = { _ in .success(()) } setTopicClosure = { _ in .success(()) } getMemberUserIDClosure = { [weak self] userID in @@ -102,7 +89,6 @@ extension JoinedRoomProxyMock { flagAsUnreadReturnValue = .success(()) markAsReadReceiptTypeReturnValue = .success(()) - underlyingIsFavourite = false flagAsFavouriteReturnValue = .success(()) powerLevelsReturnValue = .success(.mock) @@ -154,3 +140,46 @@ extension JoinedRoomProxyMock { clearDraftReturnValue = .success(()) } } + +extension RoomInfo { + @MainActor init(_ configuration: JoinedRoomProxyMockConfiguration) { + self.init(id: configuration.id, + creator: nil, + displayName: configuration.name, + rawName: configuration.name, + topic: configuration.topic, + avatarUrl: configuration.avatarURL?.absoluteString, + isDirect: configuration.isDirect, + isPublic: configuration.isPublic, + isSpace: configuration.isSpace, + isTombstoned: false, + isFavourite: false, + canonicalAlias: configuration.canonicalAlias, + alternativeAliases: [], + membership: .joined, + inviter: configuration.inviter.map { RoomMember(userId: $0.userID, + displayName: $0.displayName, + avatarUrl: $0.avatarURL?.absoluteString, + membership: $0.membership, + isNameAmbiguous: false, + powerLevel: Int64($0.powerLevel), + normalizedPowerLevel: Int64($0.powerLevel), + isIgnored: $0.isIgnored, + suggestedRoleForPowerLevel: $0.role) }, + heroes: [], + activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), + invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), + joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), + userPowerLevels: [:], + highlightCount: 0, + notificationCount: 0, + cachedUserDefinedNotificationMode: .allMessages, + hasRoomCall: configuration.hasOngoingCall, + activeRoomCallParticipants: [], + isMarkedUnread: false, + numUnreadMessages: 0, + numUnreadNotifications: 0, + numUnreadMentions: 0, + pinnedEventIds: Array(configuration.pinnedEventIDs)) + } +} diff --git a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift index 610e975a57..76559bae13 100644 --- a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift @@ -7,6 +7,7 @@ import Combine import Foundation +import MatrixRustSDK @MainActor struct KnockedRoomProxyMockConfiguration { @@ -21,9 +22,41 @@ extension KnockedRoomProxyMock { convenience init(_ configuration: KnockedRoomProxyMockConfiguration) { self.init() id = configuration.id - name = configuration.name - avatarURL = configuration.avatarURL - avatar = .room(id: configuration.id, name: configuration.name, avatarURL: configuration.avatarURL) // Note: This doesn't replicate the real proxy logic. - activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count + info = RoomInfoProxy(roomInfo: .init(configuration)) + } +} + +extension RoomInfo { + @MainActor init(_ configuration: KnockedRoomProxyMockConfiguration) { + self.init(id: configuration.id, + creator: nil, + displayName: configuration.name, + rawName: nil, + topic: nil, + avatarUrl: configuration.avatarURL?.absoluteString, + isDirect: false, + isPublic: false, + isSpace: false, + isTombstoned: false, + isFavourite: false, + canonicalAlias: nil, + alternativeAliases: [], + membership: .knocked, + inviter: nil, + heroes: [], + activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), + invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), + joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), + userPowerLevels: [:], + highlightCount: 0, + notificationCount: 0, + cachedUserDefinedNotificationMode: nil, + hasRoomCall: false, + activeRoomCallParticipants: [], + isMarkedUnread: false, + numUnreadMessages: 0, + numUnreadNotifications: 0, + numUnreadMentions: 0, + pinnedEventIds: []) } } diff --git a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift index 1e9e0315aa..4c41b43586 100644 --- a/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift +++ b/ElementX/Sources/Screens/CallScreen/CallScreenViewModel.swift @@ -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() diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 7b40e72d23..b689ce6ca1 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -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() } diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index b97b85bd7e..2d88bfb14e 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -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() diff --git a/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift b/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift index 984ce6cfea..d0b12c8f56 100644 --- a/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomChangeRolesScreen/RoomChangeRolesScreenViewModel.swift @@ -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: diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift index cc16d6a709..b750cf7280 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift @@ -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, diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index 55553d881f..d56498ef80 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -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) } } } diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift index 129b8e8faa..a43afebdc6 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift @@ -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, diff --git a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift index 748f07caa5..0a09262325 100644 --- a/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift @@ -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 { diff --git a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift index 692a32698b..2c7a4a50da 100644 --- a/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomRolesAndPermissionsScreen/RoomRolesAndPermissionsScreenViewModel.swift @@ -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: diff --git a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift index de54046aa0..a084736de8 100644 --- a/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift +++ b/ElementX/Sources/Screens/RoomScreen/ComposerToolbar/CompletionSuggestionService.swift @@ -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) } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 36f3bb7239..93c95e1a3f 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -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) { 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) diff --git a/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift b/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift index c47f71a2d6..934b383798 100644 --- a/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/NotificationSettingsEditScreen/NotificationSettingsEditScreenViewModel.swift @@ -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: diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 56307073d6..7a7103499d 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -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 diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 0bba67ad5e..28ed38ee12 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -696,7 +696,7 @@ class ClientProxy: ClientProxyProtocol { for roomID in roomIdentifiers { guard case let .joined(roomProxy) = await roomForIdentifier(roomID), - roomProxy.isDirect, + roomProxy.infoPublisher.value.isDirect, let members = await roomProxy.members() else { continue } @@ -869,15 +869,15 @@ class ClientProxy: ClientProxyProtocol { switch roomListItem.membership() { case .invited: - return try .invited(InvitedRoomProxy(roomListItem: roomListItem, - room: roomListItem.invitedRoom())) + return try await .invited(InvitedRoomProxy(roomListItem: roomListItem, + room: roomListItem.invitedRoom())) case .knocked: if appSettings.knockingEnabled { - return try .knocked(KnockedRoomProxy(roomListItem: roomListItem, - room: roomListItem.invitedRoom())) + return try await .knocked(KnockedRoomProxy(roomListItem: roomListItem, + room: roomListItem.invitedRoom())) } else { - return try .invited(InvitedRoomProxy(roomListItem: roomListItem, - room: roomListItem.invitedRoom())) + return try await .invited(InvitedRoomProxy(roomListItem: roomListItem, + room: roomListItem.invitedRoom())) } case .joined: if roomListItem.isTimelineInitialized() == false { diff --git a/ElementX/Sources/Services/ElementCall/ElementCallService.swift b/ElementX/Sources/Services/ElementCall/ElementCallService.swift index f08a4e712a..1a02da9826 100644 --- a/ElementX/Sources/Services/ElementCall/ElementCallService.swift +++ b/ElementX/Sources/Services/ElementCall/ElementCallService.swift @@ -291,16 +291,11 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe // it from what we have. If the call is running before subscribing then wait // for it to change to `false` otherwise wait for it to turn `true` before // changing to `false` - let isCallOngoing = roomProxy.hasOngoingCall + let isCallOngoing = roomProxy.infoPublisher.value.hasRoomCall roomProxy - .actionsPublisher - .compactMap { action -> (Bool, [String])? in - switch action { - case .roomInfoUpdate: - return (roomProxy.hasOngoingCall, roomProxy.activeRoomCallParticipants) - } - } + .infoPublisher + .compactMap { ($0.hasRoomCall, $0.activeRoomCallParticipants) } .removeDuplicates { $0 == $1 } .dropFirst(isCallOngoing ? 0 : 1) .sink { [weak self] hasOngoingCall, activeRoomCallParticipants in diff --git a/ElementX/Sources/Services/Room/InvitedRoomProxy.swift b/ElementX/Sources/Services/Room/InvitedRoomProxy.swift index 291bfa3304..4e2be76cc0 100644 --- a/ElementX/Sources/Services/Room/InvitedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/InvitedRoomProxy.swift @@ -17,68 +17,15 @@ class InvitedRoomProxy: InvitedRoomProxyProtocol { // multiple times over FFI lazy var id: String = room.id() - var canonicalAlias: String? { - room.canonicalAlias() - } - - var ownUserID: String { - room.ownUserId() - } - - var name: String? { - roomListItem.displayName() - } - - var topic: String? { - room.topic() - } + var ownUserID: String { room.ownUserId() } - var avatarURL: URL? { - roomListItem.avatarUrl().flatMap(URL.init(string:)) - } - - var avatar: RoomAvatar { - if isDirect, avatarURL == nil { - let heroes = room.heroes() - - if heroes.count == 1 { - return .heroes(heroes.map(UserProfileProxy.init)) - } - } - - return .room(id: id, name: name, avatarURL: avatarURL) - } - - var isDirect: Bool { - room.isDirect() - } - - var isPublic: Bool { - room.isPublic() - } - - var isSpace: Bool { - room.isSpace() - } - - var joinedMembersCount: Int { - Int(room.joinedMembersCount()) - } - - var activeMembersCount: Int { - Int(room.activeMembersCount()) - } - - var inviter: RoomMemberProxyProtocol? { - get async { - await (try? roomListItem.roomInfo().inviter).map(RoomMemberProxy.init) - } - } + let info: RoomInfoProxy init(roomListItem: RoomListItemProtocol, - room: RoomProtocol) { + room: RoomProtocol) async throws { self.roomListItem = roomListItem self.room = room + info = try await RoomInfoProxy(roomInfo: room.roomInfo()) } func acceptInvitation() async -> Result { diff --git a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift index 4ada990bcf..30211cc367 100644 --- a/ElementX/Sources/Services/Room/JoinedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/JoinedRoomProxy.swift @@ -62,6 +62,11 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { private var identityStatusChangesObservationToken: TaskHandle? private var subscribedForUpdates = false + + private let infoSubject: CurrentValueSubject + var infoPublisher: CurrentValuePublisher { + infoSubject.asCurrentValuePublisher() + } private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([]) var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { @@ -77,95 +82,17 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { identityStatusChangesSubject.asCurrentValuePublisher() } - - private let actionsSubject = PassthroughSubject() - var actionsPublisher: AnyPublisher { - actionsSubject.eraseToAnyPublisher() - } // A room identifier is constant and lazy stops it from being fetched // multiple times over FFI lazy var id: String = room.id() - - var canonicalAlias: String? { - room.canonicalAlias() - } - - var ownUserID: String { - room.ownUserId() - } - - var name: String? { - roomListItem.displayName() - } - - var topic: String? { - room.topic() - } - - var avatarURL: URL? { - roomListItem.avatarUrl().flatMap(URL.init(string:)) - } - - var avatar: RoomAvatar { - if isDirect, avatarURL == nil { - let heroes = room.heroes() - - if heroes.count == 1 { - return .heroes(heroes.map(UserProfileProxy.init)) - } - } - - return .room(id: id, name: name, avatarURL: avatarURL) - } - - var isDirect: Bool { - room.isDirect() - } - - var isPublic: Bool { - room.isPublic() - } - - var isSpace: Bool { - room.isSpace() - } - - var joinedMembersCount: Int { - Int(room.joinedMembersCount()) - } - - var activeMembersCount: Int { - Int(room.activeMembersCount()) - } + var ownUserID: String { room.ownUserId() } + var info: RoomInfoProxy { infoSubject.value } var isEncrypted: Bool { (try? room.isEncrypted()) ?? false } - var isFavourite: Bool { - get async { - await (try? room.roomInfo().isFavourite) ?? false - } - } - - var pinnedEventIDs: Set { - get async { - guard let pinnedEventIDs = try? await room.roomInfo().pinnedEventIds else { - return [] - } - return .init(pinnedEventIDs) - } - } - - var hasOngoingCall: Bool { - room.hasActiveRoomCall() - } - - var activeRoomCallParticipants: [String] { - room.activeRoomCallParticipants() - } - init(roomListService: RoomListServiceProtocol, roomListItem: RoomListItemProtocol, room: RoomProtocol) async throws { @@ -173,6 +100,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { self.roomListItem = roomListItem self.room = room + infoSubject = try await .init(RoomInfoProxy(roomInfo: room.roomInfo())) timeline = try await TimelineProxy(timeline: room.timeline(), kind: .live) Task { @@ -210,9 +138,9 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { return } - roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] in + roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: RoomInfoUpdateListener { [weak self] roomInfo in MXLog.info("Received room info update") - self?.actionsSubject.send(.roomInfoUpdate) + self?.infoSubject.send(.init(roomInfo: roomInfo)) }) } @@ -730,14 +658,14 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol { } private final class RoomInfoUpdateListener: RoomInfoListener { - private let onUpdateClosure: () -> Void + private let onUpdateClosure: (RoomInfo) -> Void - init(_ onUpdateClosure: @escaping () -> Void) { + init(_ onUpdateClosure: @escaping (RoomInfo) -> Void) { self.onUpdateClosure = onUpdateClosure } func call(roomInfo: RoomInfo) { - onUpdateClosure() + onUpdateClosure(roomInfo) } } diff --git a/ElementX/Sources/Services/Room/KnockedRoomProxy.swift b/ElementX/Sources/Services/Room/KnockedRoomProxy.swift index 7ca349f331..74b640dc54 100644 --- a/ElementX/Sources/Services/Room/KnockedRoomProxy.swift +++ b/ElementX/Sources/Services/Room/KnockedRoomProxy.swift @@ -17,62 +17,15 @@ class KnockedRoomProxy: KnockedRoomProxyProtocol { // multiple times over FFI lazy var id: String = room.id() - var canonicalAlias: String? { - room.canonicalAlias() - } - - var ownUserID: String { - room.ownUserId() - } - - var name: String? { - roomListItem.displayName() - } - - var topic: String? { - room.topic() - } - - var avatarURL: URL? { - roomListItem.avatarUrl().flatMap(URL.init(string:)) - } - - var avatar: RoomAvatar { - if isDirect, avatarURL == nil { - let heroes = room.heroes() - - if heroes.count == 1 { - return .heroes(heroes.map(UserProfileProxy.init)) - } - } - - return .room(id: id, name: name, avatarURL: avatarURL) - } - - var isDirect: Bool { - room.isDirect() - } - - var isPublic: Bool { - room.isPublic() - } + var ownUserID: String { room.ownUserId() } - var isSpace: Bool { - room.isSpace() - } - - var joinedMembersCount: Int { - Int(room.joinedMembersCount()) - } - - var activeMembersCount: Int { - Int(room.activeMembersCount()) - } + let info: RoomInfoProxy init(roomListItem: RoomListItemProtocol, - room: RoomProtocol) { + room: RoomProtocol) async throws { self.roomListItem = roomListItem self.room = room + info = try await RoomInfoProxy(roomInfo: room.roomInfo()) } func cancelKnock() async -> Result { diff --git a/ElementX/Sources/Services/Room/RoomInfoProxy.swift b/ElementX/Sources/Services/Room/RoomInfoProxy.swift new file mode 100644 index 0000000000..e402031310 --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomInfoProxy.swift @@ -0,0 +1,56 @@ +// +// Copyright 2024 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. +// + +import Foundation +import MatrixRustSDK + +struct RoomInfoProxy { + let roomInfo: RoomInfo + + var id: String { roomInfo.id } + var creator: String? { roomInfo.creator } + var displayName: String? { roomInfo.displayName } + var rawName: String? { roomInfo.rawName } + var topic: String? { roomInfo.topic } + /// The room's avatar URL. Use this for editing and favour ``avatar`` for display. + var avatarURL: URL? { roomInfo.avatarUrl.flatMap(URL.init) } + /// The room's avatar info for use in a ``RoomAvatarImage``. + var avatar: RoomAvatar { + if isDirect, avatarURL == nil { + if heroes.count == 1 { + return .heroes(heroes.map(UserProfileProxy.init)) + } + } + + return .room(id: id, name: displayName, avatarURL: avatarURL) + } + + var isDirect: Bool { roomInfo.isDirect } + var isPublic: Bool { roomInfo.isPublic } + var isSpace: Bool { roomInfo.isSpace } + var isTombstoned: Bool { roomInfo.isTombstoned } + var isFavourite: Bool { roomInfo.isFavourite } + var canonicalAlias: String? { roomInfo.canonicalAlias } + var alternativeAliases: [String] { roomInfo.alternativeAliases } + var membership: Membership { roomInfo.membership } + var inviter: RoomMemberProxy? { roomInfo.inviter.map(RoomMemberProxy.init) } + var heroes: [RoomHero] { roomInfo.heroes } + var activeMembersCount: Int { Int(roomInfo.activeMembersCount) } + var invitedMembersCount: Int { Int(roomInfo.invitedMembersCount) } + var joinedMembersCount: Int { Int(roomInfo.joinedMembersCount) } + var userPowerLevels: [String: Int] { roomInfo.userPowerLevels.mapValues(Int.init) } + var highlightCount: Int { Int(roomInfo.highlightCount) } + var notificationCount: Int { Int(roomInfo.notificationCount) } + var cachedUserDefinedNotificationMode: RoomNotificationMode? { roomInfo.cachedUserDefinedNotificationMode } + var hasRoomCall: Bool { roomInfo.hasRoomCall } + var activeRoomCallParticipants: [String] { roomInfo.activeRoomCallParticipants } + var isMarkedUnread: Bool { roomInfo.isMarkedUnread } + var unreadMessagesCount: UInt { UInt(roomInfo.numUnreadMessages) } + var unreadNotificationsCount: UInt { UInt(roomInfo.numUnreadNotifications) } + var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) } + var pinnedEventIDs: Set { Set(roomInfo.pinnedEventIds) } +} diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 46cc11f080..cf9c01fd8e 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -28,37 +28,19 @@ enum RoomProxyType { // sourcery: AutoMockable protocol RoomProxyProtocol { var id: String { get } - var canonicalAlias: String? { get } - var ownUserID: String { get } - - var name: String? { get } - var topic: String? { get } - - /// The room's avatar info for use in a ``RoomAvatarImage``. - var avatar: RoomAvatar { get } - /// The room's avatar URL. Use this for editing and favour ``avatar`` for display. - var avatarURL: URL? { get } - - var isPublic: Bool { get } - var isDirect: Bool { get } - var isSpace: Bool { get } - - var joinedMembersCount: Int { get } - - var activeMembersCount: Int { get } } // sourcery: AutoMockable protocol InvitedRoomProxyProtocol: RoomProxyProtocol { - var inviter: RoomMemberProxyProtocol? { get async } - + var info: RoomInfoProxy { get } func rejectInvitation() async -> Result func acceptInvitation() async -> Result } // sourcery: AutoMockable protocol KnockedRoomProxyProtocol: RoomProxyProtocol { + var info: RoomInfoProxy { get } func cancelKnock() async -> Result } @@ -69,11 +51,8 @@ enum JoinedRoomProxyAction: Equatable { // sourcery: AutoMockable protocol JoinedRoomProxyProtocol: RoomProxyProtocol { var isEncrypted: Bool { get } - var isFavourite: Bool { get async } - var pinnedEventIDs: Set { get async } - var hasOngoingCall: Bool { get } - var activeRoomCallParticipants: [String] { get } + var infoPublisher: CurrentValuePublisher { get } var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get } @@ -81,8 +60,6 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { get } - var actionsPublisher: AnyPublisher { get } - var timeline: TimelineProxyProtocol { get } var pinnedEventsTimeline: TimelineProxyProtocol? { get async } @@ -176,21 +153,15 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol { extension JoinedRoomProxyProtocol { var details: RoomDetails { RoomDetails(id: id, - name: name, - avatar: avatar, - canonicalAlias: canonicalAlias, + name: infoPublisher.value.displayName, + avatar: infoPublisher.value.avatar, + canonicalAlias: infoPublisher.value.canonicalAlias, isEncrypted: isEncrypted, - isPublic: isPublic) - } - - // Avoids to duplicate the same logic around in the app - // Probably this should be done in rust. - var roomTitle: String { - name ?? "Unknown room 💥" + isPublic: infoPublisher.value.isPublic) } var isEncryptedOneToOneRoom: Bool { - isDirect && isEncrypted && activeMembersCount <= 2 + infoPublisher.value.isDirect && isEncrypted && infoPublisher.value.activeMembersCount <= 2 } func members() async -> [RoomMemberProxyProtocol]? { diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index b7e704324c..09681aa418 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -326,7 +326,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { switch paginationState.backward { case .timelineEndReached: if timelineKind != .pinned, !roomProxy.isEncryptedOneToOneRoom { - let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.name) + let timelineStart = TimelineStartRoomTimelineItem(name: roomProxy.infoPublisher.value.displayName) newTimelineItems.insert(timelineStart, at: 0) } case .paginating: diff --git a/UnitTests/Sources/RoomScreenViewModelTests.swift b/UnitTests/Sources/RoomScreenViewModelTests.swift index e21891bb43..773c9af2c7 100644 --- a/UnitTests/Sources/RoomScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomScreenViewModelTests.swift @@ -24,15 +24,16 @@ class RoomScreenViewModelTests: XCTestCase { } func testPinnedEventsBanner() async throws { + var configuration = JoinedRoomProxyMockConfiguration() let timelineSubject = PassthroughSubject() - let updateSubject = PassthroughSubject() - let roomProxyMock = JoinedRoomProxyMock(.init()) + let infoSubject = CurrentValueSubject(.init(roomInfo: RoomInfo(configuration))) + let roomProxyMock = JoinedRoomProxyMock(configuration) // setup a way to inject the mock of the pinned events timeline roomProxyMock.pinnedEventsTimelineClosure = { await timelineSubject.values.first() } // setup the room proxy actions publisher - roomProxyMock.underlyingActionsPublisher = updateSubject.eraseToAnyPublisher() + roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() let viewModel = RoomScreenViewModel(clientProxy: ClientProxyMock(), roomProxy: roomProxyMock, initialSelectedPinnedEventID: nil, @@ -56,8 +57,8 @@ class RoomScreenViewModelTests: XCTestCase { deferred = deferFulfillment(viewModel.context.$viewState) { viewState in viewState.pinnedEventsBannerState.count == 2 } - roomProxyMock.underlyingPinnedEventIDs = ["test1", "test2"] - updateSubject.send(.roomInfoUpdate) + configuration.pinnedEventIDs = ["test1", "test2"] + infoSubject.send(.init(roomInfo: RoomInfo(configuration))) try await deferred.fulfill() XCTAssertTrue(viewModel.context.viewState.pinnedEventsBannerState.isLoading) XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner) @@ -157,11 +158,12 @@ class RoomScreenViewModelTests: XCTestCase { } func testRoomInfoUpdate() async throws { - let updateSubject = PassthroughSubject() - let roomProxyMock = JoinedRoomProxyMock(.init(id: "TestID", name: "StartingName", avatarURL: nil, hasOngoingCall: false)) + var configuration = JoinedRoomProxyMockConfiguration(id: "TestID", name: "StartingName", avatarURL: nil, hasOngoingCall: false) + let infoSubject = CurrentValueSubject(.init(roomInfo: RoomInfo(configuration))) + let roomProxyMock = JoinedRoomProxyMock(configuration) // setup the room proxy actions publisher roomProxyMock.canUserJoinCallUserIDReturnValue = .success(false) - roomProxyMock.underlyingActionsPublisher = updateSubject.eraseToAnyPublisher() + roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() let viewModel = RoomScreenViewModel(clientProxy: ClientProxyMock(), roomProxy: roomProxyMock, initialSelectedPinnedEventID: nil, @@ -181,9 +183,9 @@ class RoomScreenViewModelTests: XCTestCase { } try await deferred.fulfill() - roomProxyMock.name = "NewName" - roomProxyMock.avatar = .room(id: "TestID", name: "NewName", avatarURL: .documentsDirectory) - roomProxyMock.hasOngoingCall = true + configuration.name = "NewName" + configuration.avatarURL = .documentsDirectory + configuration.hasOngoingCall = true roomProxyMock.canUserJoinCallUserIDReturnValue = .success(true) deferred = deferFulfillment(viewModel.context.$viewState) { viewState in @@ -193,7 +195,7 @@ class RoomScreenViewModelTests: XCTestCase { viewState.hasOngoingCall } - updateSubject.send(.roomInfoUpdate) + infoSubject.send(.init(roomInfo: RoomInfo(configuration))) try await deferred.fulfill() } diff --git a/UnitTests/Sources/TimelineViewModelTests.swift b/UnitTests/Sources/TimelineViewModelTests.swift index 28f2ad0d32..fb89dc6f3a 100644 --- a/UnitTests/Sources/TimelineViewModelTests.swift +++ b/UnitTests/Sources/TimelineViewModelTests.swift @@ -349,10 +349,11 @@ class TimelineViewModelTests: XCTestCase { // MARK: - Pins func testPinnedEvents() async throws { - let roomProxyMock = JoinedRoomProxyMock(.init(name: "", - pinnedEventIDs: .init(["test1"]))) - let actionsSubject = PassthroughSubject() - roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher() + var configuration = JoinedRoomProxyMockConfiguration(name: "", + pinnedEventIDs: .init(["test1"])) + let roomProxyMock = JoinedRoomProxyMock(configuration) + let infoSubject = CurrentValueSubject(.init(roomInfo: RoomInfo(configuration))) + roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() let viewModel = TimelineViewModel(roomProxy: roomProxyMock, timelineController: MockRoomTimelineController(), @@ -364,24 +365,21 @@ class TimelineViewModelTests: XCTestCase { appSettings: ServiceLocator.shared.settings, analyticsService: ServiceLocator.shared.analytics, emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings)) + XCTAssertEqual(configuration.pinnedEventIDs, viewModel.context.viewState.pinnedEventIDs) - var deferred = deferFulfillment(viewModel.context.$viewState) { value in - value.pinnedEventIDs == ["test1"] - } - try await deferred.fulfill() - - roomProxyMock.underlyingPinnedEventIDs = ["test1", "test2"] - deferred = deferFulfillment(viewModel.context.$viewState) { value in + configuration.pinnedEventIDs = ["test1", "test2"] + let deferred = deferFulfillment(viewModel.context.$viewState) { value in value.pinnedEventIDs == ["test1", "test2"] } - actionsSubject.send(.roomInfoUpdate) + infoSubject.send(.init(roomInfo: RoomInfo(configuration))) try await deferred.fulfill() } func testCanUserPinEvents() async throws { - let roomProxyMock = JoinedRoomProxyMock(.init(name: "", canUserPin: true)) - let actionsSubject = PassthroughSubject() - roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher() + let configuration = JoinedRoomProxyMockConfiguration(name: "", canUserPin: true) + let roomProxyMock = JoinedRoomProxyMock(configuration) + let infoSubject = CurrentValueSubject(.init(roomInfo: RoomInfo(configuration))) + roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() let viewModel = TimelineViewModel(roomProxy: roomProxyMock, timelineController: MockRoomTimelineController(), @@ -403,7 +401,7 @@ class TimelineViewModelTests: XCTestCase { deferred = deferFulfillment(viewModel.context.$viewState) { value in !value.canCurrentUserPin } - actionsSubject.send(.roomInfoUpdate) + infoSubject.send(.init(roomInfo: RoomInfo(configuration))) try await deferred.fulfill() }