mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
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:
parent
7c28d9709f
commit
7c75498b4d
@ -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 = "<group>"; };
|
||||
40316EFFEAC7B206EE9A55AE /* SecureBackupScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
406C90AF8C3E98DF5D4E5430 /* ElementCallServiceConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallServiceConstants.swift; sourceTree = "<group>"; };
|
||||
40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoProxy.swift; sourceTree = "<group>"; };
|
||||
40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
4100DDE6BF3C566AB66B80CC /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = "<group>"; };
|
||||
4137900E28201C314C835C11 /* RoomScreenFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenFooterView.swift; sourceTree = "<group>"; };
|
||||
@ -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 */,
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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 infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> {
|
||||
get { return underlyingInfoPublisher }
|
||||
set(value) { underlyingInfoPublisher = value }
|
||||
}
|
||||
|
||||
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 pinnedEventIDs: Set<String> {
|
||||
get async {
|
||||
pinnedEventIDsCallsCount += 1
|
||||
if let pinnedEventIDsClosure = pinnedEventIDsClosure {
|
||||
return await pinnedEventIDsClosure()
|
||||
} else {
|
||||
return underlyingPinnedEventIDs
|
||||
}
|
||||
}
|
||||
}
|
||||
var underlyingPinnedEventIDs: Set<String>!
|
||||
var pinnedEventIDsClosure: (() async -> Set<String>)?
|
||||
var hasOngoingCall: Bool {
|
||||
get { return underlyingHasOngoingCall }
|
||||
set(value) { underlyingHasOngoingCall = value }
|
||||
}
|
||||
var underlyingHasOngoingCall: Bool!
|
||||
var activeRoomCallParticipants: [String] = []
|
||||
var underlyingInfoPublisher: CurrentValuePublisher<RoomInfoProxy, Never>!
|
||||
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<JoinedRoomProxyAction, Never> {
|
||||
get { return underlyingActionsPublisher }
|
||||
set(value) { underlyingActionsPublisher = value }
|
||||
}
|
||||
var underlyingActionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never>!
|
||||
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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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: [])
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 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 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<Void, RoomProxyError> {
|
||||
|
@ -62,6 +62,11 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
|
||||
private var identityStatusChangesObservationToken: TaskHandle?
|
||||
|
||||
private var subscribedForUpdates = false
|
||||
|
||||
private let infoSubject: CurrentValueSubject<RoomInfoProxy, Never>
|
||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> {
|
||||
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<JoinedRoomProxyAction, Never>()
|
||||
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> {
|
||||
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<String> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 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())
|
||||
}
|
||||
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<Void, RoomProxyError> {
|
||||
|
56
ElementX/Sources/Services/Room/RoomInfoProxy.swift
Normal file
56
ElementX/Sources/Services/Room/RoomInfoProxy.swift
Normal file
@ -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<String> { Set(roomInfo.pinnedEventIds) }
|
||||
}
|
@ -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<Void, RoomProxyError>
|
||||
func acceptInvitation() async -> Result<Void, RoomProxyError>
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
protocol KnockedRoomProxyProtocol: RoomProxyProtocol {
|
||||
var info: RoomInfoProxy { get }
|
||||
func cancelKnock() async -> Result<Void, RoomProxyError>
|
||||
}
|
||||
|
||||
@ -69,11 +51,8 @@ enum JoinedRoomProxyAction: Equatable {
|
||||
// sourcery: AutoMockable
|
||||
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
||||
var isEncrypted: Bool { get }
|
||||
var isFavourite: Bool { get async }
|
||||
var pinnedEventIDs: Set<String> { get async }
|
||||
|
||||
var hasOngoingCall: Bool { get }
|
||||
var activeRoomCallParticipants: [String] { get }
|
||||
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { get }
|
||||
|
||||
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
|
||||
|
||||
@ -81,8 +60,6 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
|
||||
|
||||
var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { get }
|
||||
|
||||
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> { 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]? {
|
||||
|
@ -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:
|
||||
|
@ -24,15 +24,16 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testPinnedEventsBanner() async throws {
|
||||
var configuration = JoinedRoomProxyMockConfiguration()
|
||||
let timelineSubject = PassthroughSubject<TimelineProxyProtocol, Never>()
|
||||
let updateSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init())
|
||||
let infoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.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<JoinedRoomProxyAction, Never>()
|
||||
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<RoomInfoProxy, Never>(.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()
|
||||
}
|
||||
|
||||
|
@ -349,10 +349,11 @@ class TimelineViewModelTests: XCTestCase {
|
||||
// MARK: - Pins
|
||||
|
||||
func testPinnedEvents() async throws {
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: "",
|
||||
pinnedEventIDs: .init(["test1"])))
|
||||
let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
|
||||
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
||||
var configuration = JoinedRoomProxyMockConfiguration(name: "",
|
||||
pinnedEventIDs: .init(["test1"]))
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
let infoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.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<JoinedRoomProxyAction, Never>()
|
||||
roomProxyMock.underlyingActionsPublisher = actionsSubject.eraseToAnyPublisher()
|
||||
let configuration = JoinedRoomProxyMockConfiguration(name: "", canUserPin: true)
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
let infoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.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()
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user