Adopt room info power levels (#4245)

* Use the newly RoomInfo published PowerLevels; update permissions at the same time as the room info and avoid unnecessary async calls.

* Introduce a RoomInfoProxyProtocol and associated mock and move away from mocked SDK RoomInfos

* Bump the SDK to v25.06.23

* Expose new non-throwing methods for checking one's own user permissions

* Fix the unit and preview tests (except the TimelineMediaPreviewDetails for which simplified snapshots were generated)

* Converge on one single implementation for computing a room's avatar.

* Fix more unit tests

* Fix the TimelineMediaPreviewDetailsView snapshot tests

This reverts commit 9b6c819e611ad2fa3de2c34a4a7aa556fc9faadd.

---------

Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
Stefan Ceriu
2025-06-24 19:48:00 +03:00
committed by GitHub
parent 0f9d700c99
commit 5ac6ec28d5
31 changed files with 1274 additions and 362 deletions
+5 -1
View File
@@ -350,6 +350,7 @@
42A5A42ACF063EEE6B1980D2 /* ReportContentScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */; }; 42A5A42ACF063EEE6B1980D2 /* ReportContentScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */; };
42B084FDE621FBEE433AF444 /* LegalInformationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */; }; 42B084FDE621FBEE433AF444 /* LegalInformationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176C3E20C772DE8D182863C /* LegalInformationScreen.swift */; };
42F1C8731166633E35A6D7E6 /* RoomEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A307A44F952CD73E63AE31 /* RoomEventStringBuilder.swift */; }; 42F1C8731166633E35A6D7E6 /* RoomEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0A307A44F952CD73E63AE31 /* RoomEventStringBuilder.swift */; };
432EA37BDC97CEDBAB7B23A6 /* RoomInfoProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14517E5597594956FCE1950D /* RoomInfoProxyProtocol.swift */; };
43F06DF42EC00B3CE2B020A4 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; }; 43F06DF42EC00B3CE2B020A4 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3F82523D6F48B926D6AF68 /* AppSettings.swift */; };
43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */; }; 43F35A7E5703D64DB0519C59 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */; };
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; }; 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; };
@@ -1486,6 +1487,7 @@
13F354AD441E2FD83DED89AF /* SeparatorMediaEventsTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorMediaEventsTimelineView.swift; sourceTree = "<group>"; }; 13F354AD441E2FD83DED89AF /* SeparatorMediaEventsTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorMediaEventsTimelineView.swift; sourceTree = "<group>"; };
1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; }; 1423AB065857FA546444DB15 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
1434D5169F0EE319E226DA7F /* RoomMembershipDetailsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembershipDetailsProxyProtocol.swift; sourceTree = "<group>"; }; 1434D5169F0EE319E226DA7F /* RoomMembershipDetailsProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembershipDetailsProxyProtocol.swift; sourceTree = "<group>"; };
14517E5597594956FCE1950D /* RoomInfoProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoProxyProtocol.swift; sourceTree = "<group>"; };
1454CF3AABD242F55C8A2615 /* InviteUsersScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenModels.swift; sourceTree = "<group>"; }; 1454CF3AABD242F55C8A2615 /* InviteUsersScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenModels.swift; sourceTree = "<group>"; };
1511B1DCECC0DC75EB267328 /* KnockRequestsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockRequestsListScreen.swift; sourceTree = "<group>"; }; 1511B1DCECC0DC75EB267328 /* KnockRequestsListScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnockRequestsListScreen.swift; sourceTree = "<group>"; };
1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenCoordinator.swift; sourceTree = "<group>"; }; 1562EAF6231151A675BED7A9 /* RoomDirectorySearchScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDirectorySearchScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -3649,6 +3651,7 @@
C07851F4EA81AA3339806A7B /* KnockRequestProxyProtocol.swift */, C07851F4EA81AA3339806A7B /* KnockRequestProxyProtocol.swift */,
B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */, B6404166CBF5CC88673FF9E2 /* RoomDetails.swift */,
40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */, 40A66E8BC8D9AE4A08EFB2DF /* RoomInfoProxy.swift */,
14517E5597594956FCE1950D /* RoomInfoProxyProtocol.swift */,
974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */, 974AEAF3FE0C577A6C04AD6E /* RoomPermissions.swift */,
4D635709C1D6D37C225AD40E /* RoomPowerLevelProxyProtocol.swift */, 4D635709C1D6D37C225AD40E /* RoomPowerLevelProxyProtocol.swift */,
CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */, CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */,
@@ -7561,6 +7564,7 @@
8DF0EBD97753033C715D716E /* RoomFlowCoordinatorStateMachine.swift in Sources */, 8DF0EBD97753033C715D716E /* RoomFlowCoordinatorStateMachine.swift in Sources */,
9C63171267E22FEB288EC860 /* RoomHeaderView.swift in Sources */, 9C63171267E22FEB288EC860 /* RoomHeaderView.swift in Sources */,
5C8804B4F25903516E2DAB81 /* RoomInfoProxy.swift in Sources */, 5C8804B4F25903516E2DAB81 /* RoomInfoProxy.swift in Sources */,
432EA37BDC97CEDBAB7B23A6 /* RoomInfoProxyProtocol.swift in Sources */,
8A83D715940378B9BA9F739E /* RoomInviterLabel.swift in Sources */, 8A83D715940378B9BA9F739E /* RoomInviterLabel.swift in Sources */,
F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */, F4996C82A4B3A5FF0C8EDD03 /* RoomListFilterModels.swift in Sources */,
4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */, 4A9CEEE612D6D8B3DDBD28BA /* RoomListFilterView.swift in Sources */,
@@ -8744,7 +8748,7 @@
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 25.06.20; version = 25.06.23;
}; };
}; };
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
@@ -158,8 +158,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/matrix-rust-components-swift", "location" : "https://github.com/element-hq/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "68abbf0622825f008e2e9f70678b2433006e38ef", "revision" : "f6220486952f912ec3e1224f49f9ab4d141799d0",
"version" : "25.6.20" "version" : "25.6.23"
} }
}, },
{ {
@@ -19,47 +19,52 @@ struct BannedRoomProxyMockConfiguration {
extension BannedRoomProxyMock { extension BannedRoomProxyMock {
@MainActor @MainActor
convenience init(_ configuration: KnockedRoomProxyMockConfiguration) { convenience init(_ configuration: BannedRoomProxyMockConfiguration) {
self.init() self.init()
id = configuration.id id = configuration.id
info = RoomInfoProxy(roomInfo: .init(configuration)) info = RoomInfoProxyMock(configuration)
} }
} }
extension RoomInfo { extension RoomInfoProxyMock {
@MainActor init(_ configuration: BannedRoomProxyMockConfiguration) { @MainActor convenience init(_ configuration: BannedRoomProxyMockConfiguration) {
self.init(id: configuration.id, self.init()
encryptionState: .notEncrypted,
creator: nil, id = configuration.id
displayName: configuration.name, isEncrypted = false
rawName: nil, creator = nil
topic: nil, displayName = configuration.name
avatarUrl: configuration.avatarURL?.absoluteString, rawName = nil
isDirect: false, topic = nil
isPublic: false,
isSpace: false, avatarURL = configuration.avatarURL
successorRoom: nil,
isFavourite: false, isDirect = false
canonicalAlias: nil, isPublic = false
alternativeAliases: [], isSpace = false
membership: .knocked, successor = nil
inviter: nil, isFavourite = false
heroes: [], canonicalAlias = nil
activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), alternativeAliases = []
invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), membership = .knocked
joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), inviter = nil
userPowerLevels: [:], heroes = []
highlightCount: 0, activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
notificationCount: 0, invitedMembersCount = configuration.members.filter { $0.membership == .invite }.count
cachedUserDefinedNotificationMode: nil, joinedMembersCount = configuration.members.filter { $0.membership == .join }.count
hasRoomCall: false, highlightCount = 0
activeRoomCallParticipants: [], notificationCount = 0
isMarkedUnread: false, cachedUserDefinedNotificationMode = nil
numUnreadMessages: 0, hasRoomCall = false
numUnreadNotifications: 0, activeRoomCallParticipants = []
numUnreadMentions: 0, isMarkedUnread = false
pinnedEventIds: [], unreadMessagesCount = 0
joinRule: .knock, unreadNotificationsCount = 0
historyVisibility: .shared) unreadMentionsCount = 0
pinnedEventIDs = []
joinRule = .knock
historyVisibility = .shared
powerLevels = RoomPowerLevelsProxyMock(configuration: .init())
} }
} }
@@ -6472,11 +6472,11 @@ class InvitedRoomProxyMock: InvitedRoomProxyProtocol, @unchecked Sendable {
} }
} }
class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable { class JoinedRoomProxyMock: JoinedRoomProxyProtocol, @unchecked Sendable {
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> {
get { return underlyingInfoPublisher } get { return underlyingInfoPublisher }
set(value) { underlyingInfoPublisher = value } set(value) { underlyingInfoPublisher = value }
} }
var underlyingInfoPublisher: CurrentValuePublisher<RoomInfoProxy, Never>! var underlyingInfoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never>!
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
get { return underlyingMembersPublisher } get { return underlyingMembersPublisher }
set(value) { underlyingMembersPublisher = value } set(value) { underlyingMembersPublisher = value }
@@ -13406,6 +13406,127 @@ class RoomDirectorySearchProxyMock: RoomDirectorySearchProxyProtocol, @unchecked
} }
} }
} }
class RoomInfoProxyMock: RoomInfoProxyProtocol, @unchecked Sendable {
var id: String {
get { return underlyingId }
set(value) { underlyingId = value }
}
var underlyingId: String!
var creator: String?
var displayName: String?
var rawName: String?
var topic: String?
var avatarURL: URL?
var isEncrypted: Bool {
get { return underlyingIsEncrypted }
set(value) { underlyingIsEncrypted = value }
}
var underlyingIsEncrypted: Bool!
var isDirect: Bool {
get { return underlyingIsDirect }
set(value) { underlyingIsDirect = value }
}
var underlyingIsDirect: Bool!
var isPublic: Bool {
get { return underlyingIsPublic }
set(value) { underlyingIsPublic = value }
}
var underlyingIsPublic: Bool!
var isPrivate: Bool {
get { return underlyingIsPrivate }
set(value) { underlyingIsPrivate = value }
}
var underlyingIsPrivate: Bool!
var isSpace: Bool {
get { return underlyingIsSpace }
set(value) { underlyingIsSpace = value }
}
var underlyingIsSpace: Bool!
var isFavourite: Bool {
get { return underlyingIsFavourite }
set(value) { underlyingIsFavourite = value }
}
var underlyingIsFavourite: Bool!
var canonicalAlias: String?
var alternativeAliases: [String] = []
var membership: Membership {
get { return underlyingMembership }
set(value) { underlyingMembership = value }
}
var underlyingMembership: Membership!
var inviter: RoomMemberProxyProtocol?
var activeMembersCount: Int {
get { return underlyingActiveMembersCount }
set(value) { underlyingActiveMembersCount = value }
}
var underlyingActiveMembersCount: Int!
var invitedMembersCount: Int {
get { return underlyingInvitedMembersCount }
set(value) { underlyingInvitedMembersCount = value }
}
var underlyingInvitedMembersCount: Int!
var joinedMembersCount: Int {
get { return underlyingJoinedMembersCount }
set(value) { underlyingJoinedMembersCount = value }
}
var underlyingJoinedMembersCount: Int!
var highlightCount: Int {
get { return underlyingHighlightCount }
set(value) { underlyingHighlightCount = value }
}
var underlyingHighlightCount: Int!
var notificationCount: Int {
get { return underlyingNotificationCount }
set(value) { underlyingNotificationCount = value }
}
var underlyingNotificationCount: Int!
var cachedUserDefinedNotificationMode: RoomNotificationMode?
var hasRoomCall: Bool {
get { return underlyingHasRoomCall }
set(value) { underlyingHasRoomCall = value }
}
var underlyingHasRoomCall: Bool!
var activeRoomCallParticipants: [String] = []
var isMarkedUnread: Bool {
get { return underlyingIsMarkedUnread }
set(value) { underlyingIsMarkedUnread = value }
}
var underlyingIsMarkedUnread: Bool!
var unreadMessagesCount: UInt {
get { return underlyingUnreadMessagesCount }
set(value) { underlyingUnreadMessagesCount = value }
}
var underlyingUnreadMessagesCount: UInt!
var unreadNotificationsCount: UInt {
get { return underlyingUnreadNotificationsCount }
set(value) { underlyingUnreadNotificationsCount = value }
}
var underlyingUnreadNotificationsCount: UInt!
var unreadMentionsCount: UInt {
get { return underlyingUnreadMentionsCount }
set(value) { underlyingUnreadMentionsCount = value }
}
var underlyingUnreadMentionsCount: UInt!
var pinnedEventIDs: Set<String> {
get { return underlyingPinnedEventIDs }
set(value) { underlyingPinnedEventIDs = value }
}
var underlyingPinnedEventIDs: Set<String>!
var joinRule: JoinRule?
var historyVisibility: RoomHistoryVisibility {
get { return underlyingHistoryVisibility }
set(value) { underlyingHistoryVisibility = value }
}
var underlyingHistoryVisibility: RoomHistoryVisibility!
var powerLevels: RoomPowerLevelsProxyProtocol {
get { return underlyingPowerLevels }
set(value) { underlyingPowerLevels = value }
}
var underlyingPowerLevels: RoomPowerLevelsProxyProtocol!
var successor: SuccessorRoom?
var heroes: [RoomHero] = []
}
class RoomMemberProxyMock: RoomMemberProxyProtocol, @unchecked Sendable { class RoomMemberProxyMock: RoomMemberProxyProtocol, @unchecked Sendable {
var userID: String { var userID: String {
get { return underlyingUserID } get { return underlyingUserID }
@@ -13467,6 +13588,658 @@ class RoomPowerLevelsProxyMock: RoomPowerLevelsProxyProtocol, @unchecked Sendabl
} }
var underlyingValues: RoomPowerLevelsValues! var underlyingValues: RoomPowerLevelsValues!
//MARK: - canOwnUser
var canOwnUserSendMessageUnderlyingCallsCount = 0
var canOwnUserSendMessageCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserSendMessageUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserSendMessageUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserSendMessageUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserSendMessageUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserSendMessageCalled: Bool {
return canOwnUserSendMessageCallsCount > 0
}
var canOwnUserSendMessageReceivedMessageType: MessageLikeEventType?
var canOwnUserSendMessageReceivedInvocations: [MessageLikeEventType] = []
var canOwnUserSendMessageUnderlyingReturnValue: Bool!
var canOwnUserSendMessageReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserSendMessageUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserSendMessageUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserSendMessageUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserSendMessageUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserSendMessageClosure: ((MessageLikeEventType) -> Bool)?
func canOwnUser(sendMessage messageType: MessageLikeEventType) -> Bool {
canOwnUserSendMessageCallsCount += 1
canOwnUserSendMessageReceivedMessageType = messageType
DispatchQueue.main.async {
self.canOwnUserSendMessageReceivedInvocations.append(messageType)
}
if let canOwnUserSendMessageClosure = canOwnUserSendMessageClosure {
return canOwnUserSendMessageClosure(messageType)
} else {
return canOwnUserSendMessageReturnValue
}
}
//MARK: - canOwnUser
var canOwnUserSendStateEventUnderlyingCallsCount = 0
var canOwnUserSendStateEventCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserSendStateEventUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserSendStateEventUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserSendStateEventUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserSendStateEventUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserSendStateEventCalled: Bool {
return canOwnUserSendStateEventCallsCount > 0
}
var canOwnUserSendStateEventReceivedEvent: StateEventType?
var canOwnUserSendStateEventReceivedInvocations: [StateEventType] = []
var canOwnUserSendStateEventUnderlyingReturnValue: Bool!
var canOwnUserSendStateEventReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserSendStateEventUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserSendStateEventUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserSendStateEventUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserSendStateEventUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserSendStateEventClosure: ((StateEventType) -> Bool)?
func canOwnUser(sendStateEvent event: StateEventType) -> Bool {
canOwnUserSendStateEventCallsCount += 1
canOwnUserSendStateEventReceivedEvent = event
DispatchQueue.main.async {
self.canOwnUserSendStateEventReceivedInvocations.append(event)
}
if let canOwnUserSendStateEventClosure = canOwnUserSendStateEventClosure {
return canOwnUserSendStateEventClosure(event)
} else {
return canOwnUserSendStateEventReturnValue
}
}
//MARK: - canOwnUserInvite
var canOwnUserInviteUnderlyingCallsCount = 0
var canOwnUserInviteCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserInviteUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserInviteUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserInviteUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserInviteUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserInviteCalled: Bool {
return canOwnUserInviteCallsCount > 0
}
var canOwnUserInviteUnderlyingReturnValue: Bool!
var canOwnUserInviteReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserInviteUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserInviteUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserInviteUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserInviteUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserInviteClosure: (() -> Bool)?
func canOwnUserInvite() -> Bool {
canOwnUserInviteCallsCount += 1
if let canOwnUserInviteClosure = canOwnUserInviteClosure {
return canOwnUserInviteClosure()
} else {
return canOwnUserInviteReturnValue
}
}
//MARK: - canOwnUserRedactOther
var canOwnUserRedactOtherUnderlyingCallsCount = 0
var canOwnUserRedactOtherCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserRedactOtherUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserRedactOtherUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserRedactOtherUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserRedactOtherUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserRedactOtherCalled: Bool {
return canOwnUserRedactOtherCallsCount > 0
}
var canOwnUserRedactOtherUnderlyingReturnValue: Bool!
var canOwnUserRedactOtherReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserRedactOtherUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserRedactOtherUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserRedactOtherUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserRedactOtherUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserRedactOtherClosure: (() -> Bool)?
func canOwnUserRedactOther() -> Bool {
canOwnUserRedactOtherCallsCount += 1
if let canOwnUserRedactOtherClosure = canOwnUserRedactOtherClosure {
return canOwnUserRedactOtherClosure()
} else {
return canOwnUserRedactOtherReturnValue
}
}
//MARK: - canOwnUserRedactOwn
var canOwnUserRedactOwnUnderlyingCallsCount = 0
var canOwnUserRedactOwnCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserRedactOwnUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserRedactOwnUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserRedactOwnUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserRedactOwnUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserRedactOwnCalled: Bool {
return canOwnUserRedactOwnCallsCount > 0
}
var canOwnUserRedactOwnUnderlyingReturnValue: Bool!
var canOwnUserRedactOwnReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserRedactOwnUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserRedactOwnUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserRedactOwnUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserRedactOwnUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserRedactOwnClosure: (() -> Bool)?
func canOwnUserRedactOwn() -> Bool {
canOwnUserRedactOwnCallsCount += 1
if let canOwnUserRedactOwnClosure = canOwnUserRedactOwnClosure {
return canOwnUserRedactOwnClosure()
} else {
return canOwnUserRedactOwnReturnValue
}
}
//MARK: - canOwnUserKick
var canOwnUserKickUnderlyingCallsCount = 0
var canOwnUserKickCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserKickUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserKickUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserKickUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserKickUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserKickCalled: Bool {
return canOwnUserKickCallsCount > 0
}
var canOwnUserKickUnderlyingReturnValue: Bool!
var canOwnUserKickReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserKickUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserKickUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserKickUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserKickUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserKickClosure: (() -> Bool)?
func canOwnUserKick() -> Bool {
canOwnUserKickCallsCount += 1
if let canOwnUserKickClosure = canOwnUserKickClosure {
return canOwnUserKickClosure()
} else {
return canOwnUserKickReturnValue
}
}
//MARK: - canOwnUserBan
var canOwnUserBanUnderlyingCallsCount = 0
var canOwnUserBanCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserBanUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserBanUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserBanUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserBanUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserBanCalled: Bool {
return canOwnUserBanCallsCount > 0
}
var canOwnUserBanUnderlyingReturnValue: Bool!
var canOwnUserBanReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserBanUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserBanUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserBanUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserBanUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserBanClosure: (() -> Bool)?
func canOwnUserBan() -> Bool {
canOwnUserBanCallsCount += 1
if let canOwnUserBanClosure = canOwnUserBanClosure {
return canOwnUserBanClosure()
} else {
return canOwnUserBanReturnValue
}
}
//MARK: - canOwnUserTriggerRoomNotification
var canOwnUserTriggerRoomNotificationUnderlyingCallsCount = 0
var canOwnUserTriggerRoomNotificationCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserTriggerRoomNotificationUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserTriggerRoomNotificationUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserTriggerRoomNotificationUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserTriggerRoomNotificationUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserTriggerRoomNotificationCalled: Bool {
return canOwnUserTriggerRoomNotificationCallsCount > 0
}
var canOwnUserTriggerRoomNotificationUnderlyingReturnValue: Bool!
var canOwnUserTriggerRoomNotificationReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserTriggerRoomNotificationUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserTriggerRoomNotificationUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserTriggerRoomNotificationUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserTriggerRoomNotificationUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserTriggerRoomNotificationClosure: (() -> Bool)?
func canOwnUserTriggerRoomNotification() -> Bool {
canOwnUserTriggerRoomNotificationCallsCount += 1
if let canOwnUserTriggerRoomNotificationClosure = canOwnUserTriggerRoomNotificationClosure {
return canOwnUserTriggerRoomNotificationClosure()
} else {
return canOwnUserTriggerRoomNotificationReturnValue
}
}
//MARK: - canOwnUserPinOrUnpin
var canOwnUserPinOrUnpinUnderlyingCallsCount = 0
var canOwnUserPinOrUnpinCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserPinOrUnpinUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserPinOrUnpinUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserPinOrUnpinUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserPinOrUnpinUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserPinOrUnpinCalled: Bool {
return canOwnUserPinOrUnpinCallsCount > 0
}
var canOwnUserPinOrUnpinUnderlyingReturnValue: Bool!
var canOwnUserPinOrUnpinReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserPinOrUnpinUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserPinOrUnpinUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserPinOrUnpinUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserPinOrUnpinUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserPinOrUnpinClosure: (() -> Bool)?
func canOwnUserPinOrUnpin() -> Bool {
canOwnUserPinOrUnpinCallsCount += 1
if let canOwnUserPinOrUnpinClosure = canOwnUserPinOrUnpinClosure {
return canOwnUserPinOrUnpinClosure()
} else {
return canOwnUserPinOrUnpinReturnValue
}
}
//MARK: - canOwnUserJoinCall
var canOwnUserJoinCallUnderlyingCallsCount = 0
var canOwnUserJoinCallCallsCount: Int {
get {
if Thread.isMainThread {
return canOwnUserJoinCallUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserJoinCallUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserJoinCallUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
canOwnUserJoinCallUnderlyingCallsCount = newValue
}
}
}
}
var canOwnUserJoinCallCalled: Bool {
return canOwnUserJoinCallCallsCount > 0
}
var canOwnUserJoinCallUnderlyingReturnValue: Bool!
var canOwnUserJoinCallReturnValue: Bool! {
get {
if Thread.isMainThread {
return canOwnUserJoinCallUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = canOwnUserJoinCallUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
canOwnUserJoinCallUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
canOwnUserJoinCallUnderlyingReturnValue = newValue
}
}
}
}
var canOwnUserJoinCallClosure: (() -> Bool)?
func canOwnUserJoinCall() -> Bool {
canOwnUserJoinCallCallsCount += 1
if let canOwnUserJoinCallClosure = canOwnUserJoinCallClosure {
return canOwnUserJoinCallClosure()
} else {
return canOwnUserJoinCallReturnValue
}
}
//MARK: - canUser //MARK: - canUser
var canUserUserIDSendMessageUnderlyingCallsCount = 0 var canUserUserIDSendMessageUnderlyingCallsCount = 0
@@ -24,47 +24,52 @@ extension InvitedRoomProxyMock {
self.init() self.init()
id = configuration.id id = configuration.id
inviter = configuration.inviter inviter = configuration.inviter
info = RoomInfoProxy(roomInfo: .init(configuration)) info = RoomInfoProxyMock(configuration)
rejectInvitationReturnValue = .success(()) rejectInvitationReturnValue = .success(())
} }
} }
extension RoomInfo { extension RoomInfoProxyMock {
@MainActor init(_ configuration: InvitedRoomProxyMockConfiguration) { @MainActor convenience init(_ configuration: InvitedRoomProxyMockConfiguration) {
self.init(id: configuration.id, self.init()
encryptionState: .notEncrypted,
creator: nil, id = configuration.id
displayName: configuration.name, isEncrypted = false
rawName: nil, creator = nil
topic: nil, displayName = configuration.name
avatarUrl: configuration.avatarURL?.absoluteString, rawName = nil
isDirect: false, topic = nil
isPublic: false,
isSpace: false, avatarURL = configuration.avatarURL
successorRoom: nil,
isFavourite: false, isDirect = false
canonicalAlias: nil, isPublic = false
alternativeAliases: [], isSpace = false
membership: .knocked, successor = nil
inviter: .init(configuration.inviter), isFavourite = false
heroes: [], canonicalAlias = nil
activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), alternativeAliases = []
invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), membership = .knocked
joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), inviter = .init(configuration.inviter)
userPowerLevels: [:], heroes = []
highlightCount: 0, activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
notificationCount: 0, invitedMembersCount = configuration.members.filter { $0.membership == .invite }.count
cachedUserDefinedNotificationMode: nil, joinedMembersCount = configuration.members.filter { $0.membership == .join }.count
hasRoomCall: false, highlightCount = 0
activeRoomCallParticipants: [], notificationCount = 0
isMarkedUnread: false, cachedUserDefinedNotificationMode = nil
numUnreadMessages: 0, hasRoomCall = false
numUnreadNotifications: 0, activeRoomCallParticipants = []
numUnreadMentions: 0, isMarkedUnread = false
pinnedEventIds: [], unreadMessagesCount = 0
joinRule: .invite, unreadNotificationsCount = 0
historyVisibility: .shared) unreadMentionsCount = 0
pinnedEventIDs = []
joinRule = .invite
historyVisibility = .shared
powerLevels = RoomPowerLevelsProxyMock(configuration: .init())
} }
} }
@@ -62,7 +62,6 @@ extension JoinedRoomProxyMock {
ownUserID = configuration.ownUserID ownUserID = configuration.ownUserID
infoPublisher = CurrentValueSubject(.init(roomInfo: .init(configuration))).asCurrentValuePublisher()
membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher() membersPublisher = CurrentValueSubject(configuration.members).asCurrentValuePublisher()
knockRequestsStatePublisher = CurrentValueSubject(configuration.knockRequestsState).asCurrentValuePublisher() knockRequestsStatePublisher = CurrentValueSubject(configuration.knockRequestsState).asCurrentValuePublisher()
typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher() typingMembersPublisher = CurrentValueSubject([]).asCurrentValuePublisher()
@@ -134,53 +133,52 @@ extension JoinedRoomProxyMock {
isVisibleInRoomDirectoryReturnValue = .success(configuration.isVisibleInPublicDirectory) isVisibleInRoomDirectoryReturnValue = .success(configuration.isVisibleInPublicDirectory)
predecessorRoom = configuration.predecessor predecessorRoom = configuration.predecessor
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
roomInfoProxyMock.powerLevels = powerLevelsProxyMock
infoPublisher = CurrentValueSubject(roomInfoProxyMock).asCurrentValuePublisher()
} }
} }
extension RoomInfo { extension RoomInfoProxyMock {
@MainActor init(_ configuration: JoinedRoomProxyMockConfiguration) { @MainActor convenience init(_ configuration: JoinedRoomProxyMockConfiguration) {
self.init(id: configuration.id, self.init()
encryptionState: configuration.isEncrypted ? .encrypted : .notEncrypted,
creator: nil, id = configuration.id
displayName: configuration.name, isEncrypted = configuration.isEncrypted
rawName: configuration.name, creator = nil
topic: configuration.topic, displayName = configuration.name
avatarUrl: configuration.avatarURL?.absoluteString, rawName = configuration.name
isDirect: configuration.isDirect, topic = configuration.topic
isPublic: configuration.isPublic, avatarURL = configuration.avatarURL
isSpace: configuration.isSpace, isDirect = configuration.isDirect
successorRoom: configuration.successor, isPublic = configuration.isPublic
isFavourite: false, isSpace = configuration.isSpace
canonicalAlias: configuration.canonicalAlias, successor = configuration.successor
alternativeAliases: configuration.alternativeAliases, isFavourite = false
membership: configuration.membership, canonicalAlias = configuration.canonicalAlias
inviter: configuration.inviter.map { RoomMember(userId: $0.userID, alternativeAliases = configuration.alternativeAliases
displayName: $0.displayName, membership = configuration.membership
avatarUrl: $0.avatarURL?.absoluteString, inviter = configuration.inviter
membership: $0.membership, heroes = configuration.heroes.map(RoomHero.init)
isNameAmbiguous: false, activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
powerLevel: Int64($0.powerLevel), invitedMembersCount = configuration.members.filter { $0.membership == .invite }.count
normalizedPowerLevel: Int64($0.powerLevel), joinedMembersCount = configuration.members.filter { $0.membership == .join }.count
isIgnored: $0.isIgnored, highlightCount = 0
suggestedRoleForPowerLevel: $0.role, notificationCount = 0
membershipChangeReason: $0.membershipChangeReason) }, cachedUserDefinedNotificationMode = .allMessages
heroes: configuration.heroes.map(RoomHero.init), hasRoomCall = configuration.hasOngoingCall
activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), activeRoomCallParticipants = []
invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), isMarkedUnread = false
joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), unreadMessagesCount = 0
userPowerLevels: [:], unreadNotificationsCount = 0
highlightCount: 0, unreadMentionsCount = 0
notificationCount: 0, pinnedEventIDs = configuration.pinnedEventIDs
cachedUserDefinedNotificationMode: .allMessages, joinRule = configuration.joinRule
hasRoomCall: configuration.hasOngoingCall, historyVisibility = .shared
activeRoomCallParticipants: [],
isMarkedUnread: false, powerLevels = RoomPowerLevelsProxyMock(configuration: configuration.powerLevelsConfiguration)
numUnreadMessages: 0,
numUnreadNotifications: 0,
numUnreadMentions: 0,
pinnedEventIds: Array(configuration.pinnedEventIDs),
joinRule: configuration.joinRule,
historyVisibility: .shared)
} }
} }
@@ -22,44 +22,49 @@ extension KnockedRoomProxyMock {
convenience init(_ configuration: KnockedRoomProxyMockConfiguration) { convenience init(_ configuration: KnockedRoomProxyMockConfiguration) {
self.init() self.init()
id = configuration.id id = configuration.id
info = RoomInfoProxy(roomInfo: .init(configuration)) info = RoomInfoProxyMock(configuration)
} }
} }
extension RoomInfo { extension RoomInfoProxyMock {
@MainActor init(_ configuration: KnockedRoomProxyMockConfiguration) { @MainActor convenience init(_ configuration: KnockedRoomProxyMockConfiguration) {
self.init(id: configuration.id, self.init()
encryptionState: .notEncrypted,
creator: nil, id = configuration.id
displayName: configuration.name, isEncrypted = false
rawName: nil, creator = nil
topic: nil, displayName = configuration.name
avatarUrl: configuration.avatarURL?.absoluteString, rawName = nil
isDirect: false, topic = nil
isPublic: false,
isSpace: false, avatarURL = configuration.avatarURL
successorRoom: nil,
isFavourite: false, isDirect = false
canonicalAlias: nil, isPublic = false
alternativeAliases: [], isSpace = false
membership: .knocked, successor = nil
inviter: nil, isFavourite = false
heroes: [], canonicalAlias = nil
activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count), alternativeAliases = []
invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count), membership = .knocked
joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count), inviter = nil
userPowerLevels: [:], heroes = []
highlightCount: 0, activeMembersCount = configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count
notificationCount: 0, invitedMembersCount = configuration.members.filter { $0.membership == .invite }.count
cachedUserDefinedNotificationMode: nil, joinedMembersCount = configuration.members.filter { $0.membership == .join }.count
hasRoomCall: false, highlightCount = 0
activeRoomCallParticipants: [], notificationCount = 0
isMarkedUnread: false, cachedUserDefinedNotificationMode = nil
numUnreadMessages: 0, hasRoomCall = false
numUnreadNotifications: 0, activeRoomCallParticipants = []
numUnreadMentions: 0, isMarkedUnread = false
pinnedEventIds: [], unreadMessagesCount = 0
joinRule: .knock, unreadNotificationsCount = 0
historyVisibility: .shared) unreadMentionsCount = 0
pinnedEventIDs = []
joinRule = .knock
historyVisibility = .shared
powerLevels = RoomPowerLevelsProxyMock(configuration: .init())
} }
} }
@@ -26,6 +26,17 @@ extension RoomPowerLevelsProxyMock {
underlyingValues = RoomPowerLevelsValues.mock underlyingValues = RoomPowerLevelsValues.mock
canOwnUserSendMessageReturnValue = configuration.canUserSendMessage
canOwnUserSendStateEventReturnValue = configuration.canUserSendState
canOwnUserInviteReturnValue = configuration.canUserInvite
canOwnUserRedactOtherReturnValue = configuration.canUserRedactOther
canOwnUserRedactOwnReturnValue = configuration.canUserRedactOwn
canOwnUserKickReturnValue = configuration.canUserKick
canOwnUserBanReturnValue = configuration.canUserBan
canOwnUserTriggerRoomNotificationReturnValue = configuration.canUserTriggerRoomNotification
canOwnUserPinOrUnpinReturnValue = configuration.canUserPin
canOwnUserJoinCallReturnValue = configuration.canUserJoinCall
canUserUserIDSendMessageReturnValue = .success(configuration.canUserSendMessage) canUserUserIDSendMessageReturnValue = .success(configuration.canUserSendMessage)
canUserUserIDSendStateEventReturnValue = .success(configuration.canUserSendState) canUserUserIDSendStateEventReturnValue = .success(configuration.canUserSendState)
canUserInviteUserIDReturnValue = .success(configuration.canUserInvite) canUserInviteUserIDReturnValue = .success(configuration.canUserInvite)
@@ -140,7 +140,7 @@ enum TimelineMediaPreviewItem: Equatable {
case loading(Loading) case loading(Loading)
/// Wraps a media file and title to be previewed with QuickLook. /// Wraps a media file and title to be previewed with QuickLook.
class Media: NSObject, QLPreviewItem, Identifiable { @Observable class Media: NSObject, QLPreviewItem, Identifiable {
fileprivate(set) var timelineItem: EventBasedMessageTimelineItemProtocol fileprivate(set) var timelineItem: EventBasedMessageTimelineItemProtocol
var fileHandle: MediaFileHandleProxy? var fileHandle: MediaFileHandleProxy?
var downloadError: Error? var downloadError: Error?
@@ -180,27 +180,24 @@ struct TimelineMediaPreviewDetailsView_Previews: PreviewProvider, TestablePrevie
if case let .media(mediaItem) = viewModel.state.currentItem { if case let .media(mediaItem) = viewModel.state.currentItem {
TimelineMediaPreviewDetailsView(item: mediaItem, context: viewModel.context, sheetHeight: $sheetHeight) TimelineMediaPreviewDetailsView(item: mediaItem, context: viewModel.context, sheetHeight: $sheetHeight)
.previewDisplayName("Image") .previewDisplayName("Image")
.snapshotPreferences(expect: viewModel.context.$viewState.map { state in .snapshotPreferences(expect: mediaItem.observe(\.fileHandle).map { $0 != nil }.eraseToStream())
state.currentItemActions?.secondaryActions.contains(.redact) ?? false
})
} }
if case let .media(mediaItem) = loadingViewModel.state.currentItem { if case let .media(mediaItem) = loadingViewModel.state.currentItem {
TimelineMediaPreviewDetailsView(item: mediaItem, context: loadingViewModel.context, sheetHeight: $sheetHeight) TimelineMediaPreviewDetailsView(item: mediaItem, context: loadingViewModel.context, sheetHeight: $sheetHeight)
.previewDisplayName("Loading") .previewDisplayName("Loading")
.snapshotPreferences(expect: loadingViewModel.context.$viewState.map { state in
state.currentItemActions?.secondaryActions.contains(.redact) ?? false
})
} }
if case let .media(mediaItem) = unknownTypeViewModel.state.currentItem { if case let .media(mediaItem) = unknownTypeViewModel.state.currentItem {
TimelineMediaPreviewDetailsView(item: mediaItem, context: unknownTypeViewModel.context, sheetHeight: $sheetHeight) TimelineMediaPreviewDetailsView(item: mediaItem, context: unknownTypeViewModel.context, sheetHeight: $sheetHeight)
.previewDisplayName("Unknown type") .previewDisplayName("Unknown type")
.snapshotPreferences(expect: mediaItem.observe(\.fileHandle).map { $0 != nil }.eraseToStream())
} }
if case let .media(mediaItem) = presentedOnRoomViewModel.state.currentItem { if case let .media(mediaItem) = presentedOnRoomViewModel.state.currentItem {
TimelineMediaPreviewDetailsView(item: mediaItem, context: presentedOnRoomViewModel.context, sheetHeight: $sheetHeight) TimelineMediaPreviewDetailsView(item: mediaItem, context: presentedOnRoomViewModel.context, sheetHeight: $sheetHeight)
.previewDisplayName("Incoming on Room") .previewDisplayName("Incoming on Room")
.snapshotPreferences(expect: mediaItem.observe(\.fileHandle).map { $0 != nil }.eraseToStream())
} }
} }
@@ -27,9 +27,6 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider) super.init(initialViewState: KnockRequestsListScreenViewState(), mediaProvider: mediaProvider)
updateRoomInfo(roomInfo: roomProxy.infoPublisher.value) updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
Task {
await updatePermissions()
}
setupSubscriptions() setupSubscriptions()
} }
@@ -189,7 +186,6 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] roomInfo in .sink { [weak self] roomInfo in
self?.updateRoomInfo(roomInfo: roomInfo) self?.updateRoomInfo(roomInfo: roomInfo)
Task { await self?.updatePermissions() }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -214,20 +210,17 @@ class KnockRequestsListScreenViewModel: KnockRequestsListScreenViewModelType, Kn
.store(in: &cancellables) .store(in: &cancellables)
} }
private func updateRoomInfo(roomInfo: RoomInfoProxy) { private func updateRoomInfo(roomInfo: RoomInfoProxyProtocol) {
switch roomInfo.joinRule { switch roomInfo.joinRule {
case .knock, .knockRestricted: case .knock, .knockRestricted:
state.isKnockableRoom = true state.isKnockableRoom = true
default: default:
state.isKnockableRoom = false state.isKnockableRoom = false
} }
}
state.canAccept = (try? roomInfo.powerLevels.canUserInvite(userID: roomProxy.ownUserID).get()) == true
private func updatePermissions() async { state.canDecline = (try? roomInfo.powerLevels.canUserKick(userID: roomProxy.ownUserID).get()) == true
let powerLevels = try? await roomProxy.powerLevels().get() state.canBan = (try? roomInfo.powerLevels.canUserBan(userID: roomProxy.ownUserID).get()) == true
state.canAccept = (try? powerLevels?.canUserInvite(userID: roomProxy.ownUserID).get()) == true
state.canDecline = (try? powerLevels?.canUserKick(userID: roomProxy.ownUserID).get()) == true
state.canBan = (try? powerLevels?.canUserBan(userID: roomProxy.ownUserID).get()) == true
} }
private static let loadingIndicatorIdentifier = "\(KnockRequestsListScreenViewModel.self)-Loading" private static let loadingIndicatorIdentifier = "\(KnockRequestsListScreenViewModel.self)-Loading"
@@ -39,12 +39,7 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe
avatarURL: roomAvatar, avatarURL: roomAvatar,
bindings: .init(name: roomName ?? "", topic: roomTopic ?? "")), mediaProvider: mediaProvider) bindings: .init(name: roomName ?? "", topic: roomTopic ?? "")), mediaProvider: mediaProvider)
Task { updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
let powerLevels = try? await roomProxy.powerLevels().get()
state.canEditAvatar = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar).get()) == .some(true)
state.canEditName = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == .some(true)
state.canEditTopic = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == .some(true)
}
} }
// MARK: - Public // MARK: - Public
@@ -91,6 +86,12 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe
// MARK: - Private // MARK: - Private
private func updateRoomInfo(roomInfo: RoomInfoProxyProtocol) {
state.canEditAvatar = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar).get()) == .some(true)
state.canEditName = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == .some(true)
state.canEditTopic = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == .some(true)
}
private func saveRoomDetails() { private func saveRoomDetails() {
Task { Task {
let userIndicatorID = UUID().uuidString let userIndicatorID = UUID().uuidString
@@ -97,7 +97,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
} }
updateRoomInfo(roomProxy.infoPublisher.value) updateRoomInfo(roomProxy.infoPublisher.value)
Task { await updatePowerLevelPermissions() }
setupRoomSubscription() setupRoomSubscription()
Task { await fetchMembersIfNeeded() } Task { await fetchMembersIfNeeded() }
@@ -183,7 +182,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true) .throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
.sink { [weak self] roomInfo in .sink { [weak self] roomInfo in
self?.updateRoomInfo(roomInfo) self?.updateRoomInfo(roomInfo)
Task { await self?.updatePowerLevelPermissions() }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -206,7 +204,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
.store(in: &cancellables) .store(in: &cancellables)
} }
private func updateRoomInfo(_ roomInfo: RoomInfoProxy) { private func updateRoomInfo(_ roomInfo: RoomInfoProxyProtocol) {
state.isEncrypted = roomInfo.isEncrypted state.isEncrypted = roomInfo.isEncrypted
state.isDirect = roomInfo.isDirect state.isDirect = roomInfo.isDirect
state.bindings.isFavourite = roomInfo.isFavourite state.bindings.isFavourite = roomInfo.isFavourite
@@ -225,6 +223,18 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
default: default:
state.isKnockableRoom = false state.isKnockableRoom = false
} }
state.canEditRoomName = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == true
state.canEditRoomTopic = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == true
state.canEditRoomAvatar = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar).get()) == true
state.canInviteUsers = (try? roomInfo.powerLevels.canUserInvite(userID: roomProxy.ownUserID).get()) == true
state.canKickUsers = (try? roomInfo.powerLevels.canUserKick(userID: roomProxy.ownUserID).get()) == true
state.canBanUsers = (try? roomInfo.powerLevels.canUserBan(userID: roomProxy.ownUserID).get()) == true
state.canJoinCall = (try? roomInfo.powerLevels.canUserJoinCall(userID: roomProxy.ownUserID).get()) == true
Task {
state.canEditRolesOrPermissions = await (try? roomProxy.suggestedRole(for: roomProxy.ownUserID).get()) == .administrator
}
} }
private func fetchMembersIfNeeded() async { private func fetchMembersIfNeeded() async {
@@ -280,19 +290,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
} }
} }
private func updatePowerLevelPermissions() async {
state.canEditRolesOrPermissions = await (try? roomProxy.suggestedRole(for: roomProxy.ownUserID).get()) == .administrator
let powerLevels = try? await roomProxy.powerLevels().get()
state.canEditRoomName = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == true
state.canEditRoomTopic = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == true
state.canEditRoomAvatar = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar).get()) == true
state.canInviteUsers = (try? powerLevels?.canUserInvite(userID: roomProxy.ownUserID).get()) == true
state.canKickUsers = (try? powerLevels?.canUserKick(userID: roomProxy.ownUserID).get()) == true
state.canBanUsers = (try? powerLevels?.canUserBan(userID: roomProxy.ownUserID).get()) == true
state.canJoinCall = (try? powerLevels?.canUserJoinCall(userID: roomProxy.ownUserID).get()) == true
}
private func setupNotificationSettingsSubscription() { private func setupNotificationSettingsSubscription() {
notificationSettingsProxy.callbacks notificationSettingsProxy.callbacks
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
@@ -338,13 +338,13 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
RoomDetailsScreen(context: genericRoomViewModel.context) RoomDetailsScreen(context: genericRoomViewModel.context)
.snapshotPreferences(expect: genericRoomViewModel.context.$viewState.map { state in .snapshotPreferences(expect: genericRoomViewModel.context.$viewState.map { state in
state.shortcuts.contains(.invite) state.canSeeSecurityAndPrivacy == true
}) })
.previewDisplayName("Generic Room") .previewDisplayName("Generic Room")
RoomDetailsScreen(context: simpleRoomViewModel.context) RoomDetailsScreen(context: simpleRoomViewModel.context)
.snapshotPreferences(expect: simpleRoomViewModel.context.$viewState.map { state in .snapshotPreferences(expect: simpleRoomViewModel.context.$viewState.map { state in
state.shortcuts.contains(.invite) state.canSeeSecurityAndPrivacy == true
}) })
.previewDisplayName("Simple Room") .previewDisplayName("Simple Room")
@@ -99,10 +99,10 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
bannedMembers: roomMembersDetails.bannedMembers, bannedMembers: roomMembersDetails.bannedMembers,
bindings: state.bindings) bindings: state.bindings)
let powerLevels = try? await roomProxy.powerLevels().get() let powerLevels = roomProxy.infoPublisher.value.powerLevels
self.state.canInviteUsers = (try? powerLevels?.canUserInvite(userID: roomProxy.ownUserID).get()) == true self.state.canInviteUsers = (try? powerLevels.canUserInvite(userID: roomProxy.ownUserID).get()) == true
self.state.canKickUsers = (try? powerLevels?.canUserKick(userID: roomProxy.ownUserID).get()) == true self.state.canKickUsers = (try? powerLevels.canUserKick(userID: roomProxy.ownUserID).get()) == true
self.state.canBanUsers = (try? powerLevels?.canUserBan(userID: roomProxy.ownUserID).get()) == true self.state.canBanUsers = (try? powerLevels.canUserBan(userID: roomProxy.ownUserID).get()) == true
hideLoadingIndicator(Self.updateStateLoadingIndicatorIdentifier) hideLoadingIndicator(Self.updateStateLoadingIndicatorIdentifier)
} }
@@ -39,12 +39,12 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
// Automatically update the room permissions // Automatically update the room permissions
roomProxy.infoPublisher roomProxy.infoPublisher
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] _ in .sink { [weak self] roomInfo in
Task { await self?.updatePermissions() } self?.updateRoomInfo(roomInfo: roomInfo)
} }
.store(in: &cancellables) .store(in: &cancellables)
Task { await updatePermissions() } updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
} }
// MARK: - Public // MARK: - Public
@@ -113,13 +113,8 @@ class RoomRolesAndPermissionsScreenViewModel: RoomRolesAndPermissionsScreenViewM
// MARK: - Permissions // MARK: - Permissions
private func updatePermissions() async { private func updateRoomInfo(roomInfo: RoomInfoProxyProtocol) {
switch await roomProxy.powerLevels() { state.permissions = .init(powerLevels: roomInfo.powerLevels.values)
case .success(let powerLevels):
state.permissions = .init(powerLevels: powerLevels.values)
case .failure:
break
}
} }
private func editPermissions(group: RoomRolesAndPermissionsScreenPermissionsGroup) { private func editPermissions(group: RoomRolesAndPermissionsScreenPermissionsGroup) {
@@ -47,9 +47,7 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
self?.suggestionTriggerSubject.value != nil ? .milliseconds(500) : .milliseconds(0) self?.suggestionTriggerSubject.value != nil ? .milliseconds(500) : .milliseconds(0)
} }
Task { canMentionAllUsers = (try? roomProxy.infoPublisher.value.powerLevels.canUserTriggerRoomNotification(userID: roomProxy.ownUserID).get()) == true
canMentionAllUsers = await (try? roomProxy.powerLevels().get().canUserTriggerRoomNotification(userID: roomProxy.ownUserID).get()) == true
}
} }
func processTextMessage(_ textMessage: String, selectedRange: NSRange) { func processTextMessage(_ textMessage: String, selectedRange: NSRange) {
@@ -332,7 +332,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
} }
} }
private func handleRoomInfoUpdate(_ roomInfo: RoomInfoProxy) async { private func handleRoomInfoUpdate(_ roomInfo: RoomInfoProxyProtocol) async {
state.hasSuccessor = roomInfo.successor != nil state.hasSuccessor = roomInfo.successor != nil
let pinnedEventIDs = roomInfo.pinnedEventIDs let pinnedEventIDs = roomInfo.pinnedEventIDs
@@ -348,12 +348,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.isKnockableRoom = false state.isKnockableRoom = false
} }
let powerLevels = try? await roomProxy.powerLevels().get() state.canSendMessage = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendMessage: .roomMessage).get()) == true
state.canSendMessage = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendMessage: .roomMessage).get()) == true state.canJoinCall = (try? roomInfo.powerLevels.canUserJoinCall(userID: roomProxy.ownUserID).get()) == true
state.canJoinCall = (try? powerLevels?.canUserJoinCall(userID: roomProxy.ownUserID).get()) == true state.canAcceptKnocks = (try? roomInfo.powerLevels.canUserInvite(userID: roomProxy.ownUserID).get()) == true
state.canAcceptKnocks = (try? powerLevels?.canUserInvite(userID: roomProxy.ownUserID).get()) == true state.canDeclineKnocks = (try? roomInfo.powerLevels.canUserKick(userID: roomProxy.ownUserID).get()) == true
state.canDeclineKnocks = (try? powerLevels?.canUserKick(userID: roomProxy.ownUserID).get()) == true state.canBan = (try? roomInfo.powerLevels.canUserBan(userID: roomProxy.ownUserID).get()) == true
state.canBan = (try? powerLevels?.canUserBan(userID: roomProxy.ownUserID).get()) == true
} }
private func setupPinnedEventsTimelineItemProviderIfNeeded() { private func setupPinnedEventsTimelineItemProviderIfNeeded() {
@@ -29,14 +29,12 @@ class ThreadTimelineScreenViewModel: ThreadTimelineScreenViewModelType, ThreadTi
return return
} }
await self?.handleRoomInfoUpdate(roomInfo) self?.handleRoomInfoUpdate(roomInfo)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
Task { handleRoomInfoUpdate(roomProxy.infoPublisher.value)
await handleRoomInfoUpdate(roomProxy.infoPublisher.value)
}
} }
// MARK: - Public // MARK: - Public
@@ -64,7 +62,7 @@ class ThreadTimelineScreenViewModel: ThreadTimelineScreenViewModelType, ThreadTi
// MARK: - Private // MARK: - Private
private func handleRoomInfoUpdate(_ roomInfo: RoomInfoProxy) async { private func handleRoomInfoUpdate(_ roomInfo: RoomInfoProxyProtocol) {
state.canSendMessage = await (try? roomProxy.powerLevels().get().canUser(userID: roomProxy.ownUserID, sendMessage: .roomMessage).get()) == true state.canSendMessage = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendMessage: .roomMessage).get()) == true
} }
} }
@@ -119,9 +119,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
setupSubscriptions() setupSubscriptions()
setupDirectRoomSubscriptionsIfNeeded() setupDirectRoomSubscriptionsIfNeeded()
// Set initial values for redacting from the macOS context menu. updateRoomInfo(roomInfo: roomProxy.infoPublisher.value)
Task { await updatePermissions() }
state.audioPlayerStateProvider = { [weak self] itemID -> AudioPlayerState? in state.audioPlayerStateProvider = { [weak self] itemID -> AudioPlayerState? in
guard let self else { guard let self else {
return nil return nil
@@ -403,14 +402,15 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
} }
} }
private func updatePermissions() async { private func updateRoomInfo(roomInfo: RoomInfoProxyProtocol) {
let powerLevels = try? await roomProxy.powerLevels().get() state.pinnedEventIDs = roomInfo.pinnedEventIDs
state.canCurrentUserSendMessage = (try? powerLevels?.canUser(userID: roomProxy.ownUserID, sendMessage: .roomMessage).get()) == true
state.canCurrentUserRedactOthers = (try? powerLevels?.canUserRedactOther(userID: roomProxy.ownUserID).get()) == true state.canCurrentUserSendMessage = (try? roomInfo.powerLevels.canUser(userID: roomProxy.ownUserID, sendMessage: .roomMessage).get()) == true
state.canCurrentUserRedactSelf = (try? powerLevels?.canUserRedactOwn(userID: roomProxy.ownUserID).get()) == true state.canCurrentUserRedactOthers = (try? roomInfo.powerLevels.canUserRedactOther(userID: roomProxy.ownUserID).get()) == true
state.canCurrentUserPin = (try? powerLevels?.canUserPinOrUnpin(userID: roomProxy.ownUserID).get()) == true state.canCurrentUserRedactSelf = (try? roomInfo.powerLevels.canUserRedactOwn(userID: roomProxy.ownUserID).get()) == true
state.canCurrentUserKick = (try? powerLevels?.canUserKick(userID: roomProxy.ownUserID).get()) == true state.canCurrentUserPin = (try? roomInfo.powerLevels.canUserPinOrUnpin(userID: roomProxy.ownUserID).get()) == true
state.canCurrentUserBan = (try? powerLevels?.canUserBan(userID: roomProxy.ownUserID).get()) == true state.canCurrentUserKick = (try? roomInfo.powerLevels.canUserKick(userID: roomProxy.ownUserID).get()) == true
state.canCurrentUserBan = (try? roomInfo.powerLevels.canUserBan(userID: roomProxy.ownUserID).get()) == true
} }
private func setupSubscriptions() { private func setupSubscriptions() {
@@ -445,8 +445,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
guard !Task.isCancelled else { guard !Task.isCancelled else {
return return
} }
self?.state.pinnedEventIDs = roomInfo.pinnedEventIDs
await self?.updatePermissions() self?.updateRoomInfo(roomInfo: roomInfo)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -487,10 +487,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
case .displayMediaUploadPreviewScreen(let url): case .displayMediaUploadPreviewScreen(let url):
actionsSubject.send(.displayMediaUploadPreviewScreen(url: url)) actionsSubject.send(.displayMediaUploadPreviewScreen(url: url))
case .showActionMenu(let actionMenuInfo): case .showActionMenu(let actionMenuInfo):
Task { self.state.bindings.actionMenuInfo = actionMenuInfo
await self.updatePermissions()
self.state.bindings.actionMenuInfo = actionMenuInfo
}
case .showDebugInfo(let debugInfo): case .showDebugInfo(let debugInfo):
state.bindings.debugInfo = debugInfo state.bindings.debugInfo = debugInfo
case .viewInRoomTimeline(let eventID): case .viewInRoomTimeline(let eventID):
@@ -996,13 +993,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
} }
} }
private extension RoomInfoProxy {
/// Checks if the other person left the room in a direct chat
var isUserAloneInDirectRoom: Bool {
isDirect && activeMembersCount == 1
}
}
// MARK: - Mocks // MARK: - Mocks
extension TimelineViewModel { extension TimelineViewModel {
@@ -46,8 +46,8 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
let timeline: TimelineProxyProtocol let timeline: TimelineProxyProtocol
private let infoSubject: CurrentValueSubject<RoomInfoProxy, Never> private let infoSubject: CurrentValueSubject<RoomInfoProxyProtocol, Never>
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> {
infoSubject.asCurrentValuePublisher() infoSubject.asCurrentValuePublisher()
} }
@@ -137,7 +137,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: SDKListener { [weak self] roomInfo in roomInfoObservationToken = room.subscribeToRoomInfoUpdates(listener: SDKListener { [weak self] roomInfo in
MXLog.info("Received room info update") MXLog.info("Received room info update")
self?.infoSubject.send(.init(roomInfo: roomInfo)) self?.infoSubject.send(RoomInfoProxy(roomInfo: roomInfo))
}) })
} }
@@ -8,20 +8,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
protocol BaseRoomInfoProxyProtocol { struct RoomInfoProxy: RoomInfoProxyProtocol {
var id: String { get }
var displayName: String? { get }
var avatar: RoomAvatar { get }
var topic: String? { get }
var canonicalAlias: String? { get }
var avatarURL: URL? { get }
var activeMembersCount: Int { get }
var joinedMembersCount: Int { get }
var isDirect: Bool { get }
var isSpace: Bool { get }
}
struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
let roomInfo: RoomInfo let roomInfo: RoomInfo
var id: String { roomInfo.id } var id: String { roomInfo.id }
@@ -31,18 +18,6 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
var topic: String? { roomInfo.topic } var topic: String? { roomInfo.topic }
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display. /// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
var avatarURL: URL? { roomInfo.avatarUrl.flatMap(URL.init) } var avatarURL: URL? { roomInfo.avatarUrl.flatMap(URL.init) }
/// The room's avatar info for use in a ``RoomAvatarImage``.
var avatar: RoomAvatar {
guard successor == nil else {
return .tombstoned
}
if isDirect, avatarURL == nil, heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
return .room(id: id, name: displayName, avatarURL: avatarURL)
}
// Here we're assuming unknown rooms are unencrypted. // Here we're assuming unknown rooms are unencrypted.
// Fortunately https://github.com/matrix-org/matrix-rust-sdk/pull/4778 makes that very much of an edge case and we // Fortunately https://github.com/matrix-org/matrix-rust-sdk/pull/4778 makes that very much of an edge case and we
@@ -54,28 +29,17 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
var isDirect: Bool { roomInfo.isDirect } var isDirect: Bool { roomInfo.isDirect }
var isPublic: Bool { roomInfo.isPublic } var isPublic: Bool { roomInfo.isPublic }
// A room might be non public but also not private given the fact that the join rule might be missing or unsupported.
var isPrivate: Bool {
switch roomInfo.joinRule {
case .invite, .knock, .restricted, .knockRestricted:
true
default:
false
}
}
var isSpace: Bool { roomInfo.isSpace } var isSpace: Bool { roomInfo.isSpace }
var successor: SuccessorRoom? { roomInfo.successorRoom } var successor: SuccessorRoom? { roomInfo.successorRoom }
var isFavourite: Bool { roomInfo.isFavourite } var isFavourite: Bool { roomInfo.isFavourite }
var canonicalAlias: String? { roomInfo.canonicalAlias } var canonicalAlias: String? { roomInfo.canonicalAlias }
var alternativeAliases: [String] { roomInfo.alternativeAliases } var alternativeAliases: [String] { roomInfo.alternativeAliases }
var membership: Membership { roomInfo.membership } var membership: Membership { roomInfo.membership }
var inviter: RoomMemberProxy? { roomInfo.inviter.map(RoomMemberProxy.init) } var inviter: RoomMemberProxyProtocol? { roomInfo.inviter.map(RoomMemberProxy.init) }
var heroes: [RoomHero] { roomInfo.heroes } var heroes: [RoomHero] { roomInfo.heroes }
var activeMembersCount: Int { Int(roomInfo.activeMembersCount) } var activeMembersCount: Int { Int(roomInfo.activeMembersCount) }
var invitedMembersCount: Int { Int(roomInfo.invitedMembersCount) } var invitedMembersCount: Int { Int(roomInfo.invitedMembersCount) }
var joinedMembersCount: Int { Int(roomInfo.joinedMembersCount) } var joinedMembersCount: Int { Int(roomInfo.joinedMembersCount) }
var userPowerLevels: [String: Int] { roomInfo.userPowerLevels.mapValues(Int.init) }
var highlightCount: Int { Int(roomInfo.highlightCount) } var highlightCount: Int { Int(roomInfo.highlightCount) }
var notificationCount: Int { Int(roomInfo.notificationCount) } var notificationCount: Int { Int(roomInfo.notificationCount) }
var cachedUserDefinedNotificationMode: RoomNotificationMode? { roomInfo.cachedUserDefinedNotificationMode } var cachedUserDefinedNotificationMode: RoomNotificationMode? { roomInfo.cachedUserDefinedNotificationMode }
@@ -89,41 +53,14 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol {
var joinRule: JoinRule? { roomInfo.joinRule } var joinRule: JoinRule? { roomInfo.joinRule }
var historyVisibility: RoomHistoryVisibility { roomInfo.historyVisibility } var historyVisibility: RoomHistoryVisibility { roomInfo.historyVisibility }
/// Find the first alias that matches the given homeserver var powerLevels: RoomPowerLevelsProxyProtocol { RoomPowerLevelsProxy(roomInfo.powerLevels) }
/// - Parameters:
/// - serverName: the homserver in question
/// - useFallback: whether to return any alias if none match
func firstAliasMatching(serverName: String?, useFallback: Bool) -> String? {
guard let serverName else { return nil }
// Check if the canonical alias matches the homeserver
if let canonicalAlias = roomInfo.canonicalAlias,
canonicalAlias.range(of: serverName) != nil {
return canonicalAlias
}
// Otherwise check the alternative aliases and return the first one that matches
if let matchingAlternativeAlias = roomInfo.alternativeAliases.filter({ $0.range(of: serverName) != nil }).first {
return matchingAlternativeAlias
}
guard useFallback else {
return nil
}
// Or just return the canonical alias if any
if let canonicalAlias = roomInfo.canonicalAlias {
return canonicalAlias
}
// And finally return whatever the first alternative alias is
return roomInfo.alternativeAliases.first
}
} }
struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol { struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
let roomPreviewInfo: RoomPreviewInfo let roomPreviewInfo: RoomPreviewInfo
let successor: SuccessorRoom? = nil
var id: String { roomPreviewInfo.roomId } var id: String { roomPreviewInfo.roomId }
var displayName: String? { roomPreviewInfo.name } var displayName: String? { roomPreviewInfo.name }
var heroes: [RoomHero] { roomPreviewInfo.heroes ?? [] } var heroes: [RoomHero] { roomPreviewInfo.heroes ?? [] }
@@ -137,12 +74,4 @@ struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
var joinRule: JoinRule { roomPreviewInfo.joinRule } var joinRule: JoinRule { roomPreviewInfo.joinRule }
var membership: Membership? { roomPreviewInfo.membership } var membership: Membership? { roomPreviewInfo.membership }
/// The room's avatar info for use in a ``RoomAvatarImage``.
var avatar: RoomAvatar {
if isDirect, avatarURL == nil, heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
return .room(id: id, name: displayName, avatarURL: avatarURL)
}
} }
@@ -0,0 +1,130 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
protocol BaseRoomInfoProxyProtocol {
var id: String { get }
var displayName: String? { get }
var topic: String? { get }
var canonicalAlias: String? { get }
var avatarURL: URL? { get }
var activeMembersCount: Int { get }
var joinedMembersCount: Int { get }
var isDirect: Bool { get }
var isSpace: Bool { get }
var successor: SuccessorRoom? { get }
var heroes: [RoomHero] { get }
}
// sourcery: AutoMockable
protocol RoomInfoProxyProtocol: BaseRoomInfoProxyProtocol {
var id: String { get }
var creator: String? { get }
var displayName: String? { get }
var rawName: String? { get }
var topic: String? { get }
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
var avatarURL: URL? { get }
var isEncrypted: Bool { get }
var isDirect: Bool { get }
var isPublic: Bool { get }
var isPrivate: Bool { get }
var isSpace: Bool { get }
var isFavourite: Bool { get }
var canonicalAlias: String? { get }
var alternativeAliases: [String] { get }
var membership: Membership { get }
var inviter: RoomMemberProxyProtocol? { get }
var activeMembersCount: Int { get }
var invitedMembersCount: Int { get }
var joinedMembersCount: Int { get }
var highlightCount: Int { get }
var notificationCount: Int { get }
var cachedUserDefinedNotificationMode: RoomNotificationMode? { get }
var hasRoomCall: Bool { get }
var activeRoomCallParticipants: [String] { get }
var isMarkedUnread: Bool { get }
var unreadMessagesCount: UInt { get }
var unreadNotificationsCount: UInt { get }
var unreadMentionsCount: UInt { get }
var pinnedEventIDs: Set<String> { get }
var joinRule: JoinRule? { get }
var historyVisibility: RoomHistoryVisibility { get }
var powerLevels: RoomPowerLevelsProxyProtocol { get }
}
extension BaseRoomInfoProxyProtocol {
/// The room's avatar info for use in a ``RoomAvatarImage``.
var avatar: RoomAvatar {
guard successor == nil else {
return .tombstoned
}
if isDirect, avatarURL == nil, heroes.count == 1 {
return .heroes(heroes.map(UserProfileProxy.init))
}
return .room(id: id, name: displayName, avatarURL: avatarURL)
}
}
extension RoomInfoProxyProtocol {
/// A room might be non public but also not private given the fact that the join rule might be missing or unsupported.
var isPrivate: Bool {
switch joinRule {
case .invite, .knock, .restricted, .knockRestricted:
true
default:
false
}
}
/// Checks if the other person left the room in a direct chat
var isUserAloneInDirectRoom: Bool {
isDirect && activeMembersCount == 1
}
/// Find the first alias that matches the given homeserver
/// - Parameters:
/// - serverName: the homserver in question
/// - useFallback: whether to return any alias if none match
func firstAliasMatching(serverName: String?, useFallback: Bool) -> String? {
guard let serverName else { return nil }
// Check if the canonical alias matches the homeserver
if let canonicalAlias,
canonicalAlias.range(of: serverName) != nil {
return canonicalAlias
}
// Otherwise check the alternative aliases and return the first one that matches
if let matchingAlternativeAlias = alternativeAliases.filter({ $0.range(of: serverName) != nil }).first {
return matchingAlternativeAlias
}
guard useFallback else {
return nil
}
// Or just return the canonical alias if any
if let canonicalAlias {
return canonicalAlias
}
// And finally return whatever the first alternative alias is
return alternativeAliases.first
}
}
@@ -11,6 +11,17 @@ import MatrixRustSDK
protocol RoomPowerLevelsProxyProtocol { protocol RoomPowerLevelsProxyProtocol {
var values: RoomPowerLevelsValues { get } var values: RoomPowerLevelsValues { get }
func canOwnUser(sendMessage messageType: MessageLikeEventType) -> Bool
func canOwnUser(sendStateEvent event: StateEventType) -> Bool
func canOwnUserInvite() -> Bool
func canOwnUserRedactOther() -> Bool
func canOwnUserRedactOwn() -> Bool
func canOwnUserKick() -> Bool
func canOwnUserBan() -> Bool
func canOwnUserTriggerRoomNotification() -> Bool
func canOwnUserPinOrUnpin() -> Bool
func canOwnUserJoinCall() -> Bool
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) -> Result<Bool, RoomProxyError> func canUser(userID: String, sendMessage messageType: MessageLikeEventType) -> Result<Bool, RoomProxyError>
func canUser(userID: String, sendStateEvent event: StateEventType) -> Result<Bool, RoomProxyError> func canUser(userID: String, sendStateEvent event: StateEventType) -> Result<Bool, RoomProxyError>
func canUserInvite(userID: String) -> Result<Bool, RoomProxyError> func canUserInvite(userID: String) -> Result<Bool, RoomProxyError>
@@ -18,6 +18,46 @@ struct RoomPowerLevelsProxy: RoomPowerLevelsProxyProtocol {
powerLevels.values() powerLevels.values()
} }
func canOwnUser(sendMessage messageType: MessageLikeEventType) -> Bool {
powerLevels.canOwnUserSendMessage(message: messageType)
}
func canOwnUser(sendStateEvent stateEventType: StateEventType) -> Bool {
powerLevels.canOwnUserSendState(stateEvent: stateEventType)
}
func canOwnUserInvite() -> Bool {
powerLevels.canOwnUserInvite()
}
func canOwnUserRedactOther() -> Bool {
powerLevels.canOwnUserRedactOther()
}
func canOwnUserRedactOwn() -> Bool {
powerLevels.canOwnUserRedactOwn()
}
func canOwnUserKick() -> Bool {
powerLevels.canOwnUserKick()
}
func canOwnUserBan() -> Bool {
powerLevels.canOwnUserBan()
}
func canOwnUserTriggerRoomNotification() -> Bool {
powerLevels.canOwnUserTriggerRoomNotification()
}
func canOwnUserPinOrUnpin() -> Bool {
powerLevels.canOwnUserPinUnpin()
}
func canOwnUserJoinCall() -> Bool {
powerLevels.canOwnUserSendState(stateEvent: .callMember)
}
func canUser(userID: String, sendMessage messageType: MessageLikeEventType) -> Result<Bool, RoomProxyError> { func canUser(userID: String, sendMessage messageType: MessageLikeEventType) -> Result<Bool, RoomProxyError> {
do { do {
return try .success(powerLevels.canUserSendMessage(userId: userID, message: messageType)) return try .success(powerLevels.canUserSendMessage(userId: userID, message: messageType))
@@ -64,7 +64,7 @@ enum KnockRequestsState {
// sourcery: AutoMockable // sourcery: AutoMockable
protocol JoinedRoomProxyProtocol: RoomProxyProtocol { protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
var infoPublisher: CurrentValuePublisher<RoomInfoProxy, Never> { get } var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> { get }
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get } var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
@@ -355,14 +355,24 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
func testCanEditAvatar() async { func testCanEditAvatar() async {
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers))
let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init()) let configuration = JoinedRoomProxyMockConfiguration(name: "Test",
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsMock) isDirect: false,
isPublic: false,
members: mockedMembers)
powerLevelsMock.canUserUserIDSendStateEventClosure = { _, event in roomProxyMock = JoinedRoomProxyMock(configuration)
let powerLevelsProxyMock = RoomPowerLevelsProxyMock(configuration: .init())
powerLevelsProxyMock.canUserUserIDSendStateEventClosure = { _, event in
.success(event == .roomAvatar) .success(event == .roomAvatar)
} }
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsProxyMock)
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
roomInfoProxyMock.powerLevels = powerLevelsProxyMock
roomProxyMock.infoPublisher = CurrentValueSubject(roomInfoProxyMock).asCurrentValuePublisher()
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()), clientProxy: ClientProxyMock(.init()),
mediaProvider: MediaProviderMock(configuration: .init()), mediaProvider: MediaProviderMock(configuration: .init()),
@@ -383,14 +393,24 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
func testCanEditName() async { func testCanEditName() async {
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers))
let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init()) let configuration = JoinedRoomProxyMockConfiguration(name: "Test",
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsMock) isDirect: false,
isPublic: false,
members: mockedMembers)
powerLevelsMock.canUserUserIDSendStateEventClosure = { _, event in roomProxyMock = JoinedRoomProxyMock(configuration)
let powerLevelsProxyMock = RoomPowerLevelsProxyMock(configuration: .init())
powerLevelsProxyMock.canUserUserIDSendStateEventClosure = { _, event in
.success(event == .roomName) .success(event == .roomName)
} }
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsProxyMock)
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
roomInfoProxyMock.powerLevels = powerLevelsProxyMock
roomProxyMock.infoPublisher = CurrentValueSubject(roomInfoProxyMock).asCurrentValuePublisher()
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()), clientProxy: ClientProxyMock(.init()),
mediaProvider: MediaProviderMock(configuration: .init()), mediaProvider: MediaProviderMock(configuration: .init()),
@@ -411,14 +431,24 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
func testCanEditTopic() async { func testCanEditTopic() async {
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice]
roomProxyMock = JoinedRoomProxyMock(.init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers))
let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init()) let configuration = JoinedRoomProxyMockConfiguration(name: "Test",
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsMock) isDirect: false,
isPublic: false,
members: mockedMembers)
powerLevelsMock.canUserUserIDSendStateEventClosure = { _, event in roomProxyMock = JoinedRoomProxyMock(configuration)
let powerLevelsProxyMock = RoomPowerLevelsProxyMock(configuration: .init())
powerLevelsProxyMock.canUserUserIDSendStateEventClosure = { _, event in
.success(event == .roomTopic) .success(event == .roomTopic)
} }
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsProxyMock)
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
roomInfoProxyMock.powerLevels = powerLevelsProxyMock
roomProxyMock.infoPublisher = CurrentValueSubject(roomInfoProxyMock).asCurrentValuePublisher()
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()), clientProxy: ClientProxyMock(.init()),
mediaProvider: MediaProviderMock(configuration: .init()), mediaProvider: MediaProviderMock(configuration: .init()),
@@ -274,7 +274,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
var configuration = JoinedRoomProxyMockConfiguration() var configuration = JoinedRoomProxyMockConfiguration()
let roomProxy = JoinedRoomProxyMock(configuration) let roomProxy = JoinedRoomProxyMock(configuration)
let roomInfoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.init(roomInfo: .init(configuration))) let roomInfoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
roomProxy.infoPublisher = roomInfoSubject.asCurrentValuePublisher() roomProxy.infoPublisher = roomInfoSubject.asCurrentValuePublisher()
clientProxy.roomForIdentifierClosure = { _ in clientProxy.roomForIdentifierClosure = { _ in
@@ -288,7 +288,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
} }
configuration.membership = .left configuration.membership = .left
roomInfoSubject.send(.init(roomInfo: .init(configuration))) roomInfoSubject.send(RoomInfoProxyMock(configuration))
try await fulfillment.fulfill() try await fulfillment.fulfill()
} }
@@ -26,7 +26,7 @@ class RoomScreenViewModelTests: XCTestCase {
func testPinnedEventsBanner() async throws { func testPinnedEventsBanner() async throws {
var configuration = JoinedRoomProxyMockConfiguration() var configuration = JoinedRoomProxyMockConfiguration()
let timelineSubject = PassthroughSubject<TimelineProxyProtocol, Never>() let timelineSubject = PassthroughSubject<TimelineProxyProtocol, Never>()
let infoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.init(roomInfo: RoomInfo(configuration))) let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
let roomProxyMock = JoinedRoomProxyMock(configuration) let roomProxyMock = JoinedRoomProxyMock(configuration)
// setup a way to inject the mock of the pinned events timeline // setup a way to inject the mock of the pinned events timeline
roomProxyMock.pinnedEventsTimelineClosure = { roomProxyMock.pinnedEventsTimelineClosure = {
@@ -63,7 +63,7 @@ class RoomScreenViewModelTests: XCTestCase {
viewState.pinnedEventsBannerState.count == 2 viewState.pinnedEventsBannerState.count == 2
} }
configuration.pinnedEventIDs = ["test1", "test2"] configuration.pinnedEventIDs = ["test1", "test2"]
infoSubject.send(.init(roomInfo: RoomInfo(configuration))) infoSubject.send(RoomInfoProxyMock(configuration))
try await deferred.fulfill() try await deferred.fulfill()
XCTAssertTrue(viewModel.context.viewState.pinnedEventsBannerState.isLoading) XCTAssertTrue(viewModel.context.viewState.pinnedEventsBannerState.isLoading)
XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner) XCTAssertTrue(viewModel.context.viewState.shouldShowPinnedEventsBanner)
@@ -165,7 +165,7 @@ class RoomScreenViewModelTests: XCTestCase {
func testRoomInfoUpdate() async throws { func testRoomInfoUpdate() async throws {
var configuration = JoinedRoomProxyMockConfiguration(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 infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
let roomProxyMock = JoinedRoomProxyMock(configuration) let roomProxyMock = JoinedRoomProxyMock(configuration)
let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init()) let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init())
@@ -206,7 +206,7 @@ class RoomScreenViewModelTests: XCTestCase {
viewState.hasOngoingCall viewState.hasOngoingCall
} }
infoSubject.send(.init(roomInfo: RoomInfo(configuration))) infoSubject.send(RoomInfoProxyMock(configuration))
try await deferred.fulfill() try await deferred.fulfill()
} }
+10 -7
View File
@@ -468,7 +468,7 @@ class TimelineViewModelTests: XCTestCase {
var configuration = JoinedRoomProxyMockConfiguration(name: "", var configuration = JoinedRoomProxyMockConfiguration(name: "",
pinnedEventIDs: .init(["test1"])) pinnedEventIDs: .init(["test1"]))
let roomProxyMock = JoinedRoomProxyMock(configuration) let roomProxyMock = JoinedRoomProxyMock(configuration)
let infoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.init(roomInfo: RoomInfo(configuration))) let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
let viewModel = TimelineViewModel(roomProxy: roomProxyMock, let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
@@ -489,7 +489,7 @@ class TimelineViewModelTests: XCTestCase {
let deferred = deferFulfillment(viewModel.context.$viewState) { value in let deferred = deferFulfillment(viewModel.context.$viewState) { value in
value.pinnedEventIDs == ["test1", "test2"] value.pinnedEventIDs == ["test1", "test2"]
} }
infoSubject.send(.init(roomInfo: RoomInfo(configuration))) infoSubject.send(RoomInfoProxyMock(configuration))
try await deferred.fulfill() try await deferred.fulfill()
} }
@@ -497,7 +497,7 @@ class TimelineViewModelTests: XCTestCase {
let configuration = JoinedRoomProxyMockConfiguration(name: "", let configuration = JoinedRoomProxyMockConfiguration(name: "",
powerLevelsConfiguration: .init(canUserPin: true)) powerLevelsConfiguration: .init(canUserPin: true))
let roomProxyMock = JoinedRoomProxyMock(configuration) let roomProxyMock = JoinedRoomProxyMock(configuration)
let infoSubject = CurrentValueSubject<RoomInfoProxy, Never>(.init(roomInfo: RoomInfo(configuration))) let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher() roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
let viewModel = TimelineViewModel(roomProxy: roomProxyMock, let viewModel = TimelineViewModel(roomProxy: roomProxyMock,
@@ -518,14 +518,17 @@ class TimelineViewModelTests: XCTestCase {
} }
try await deferred.fulfill() try await deferred.fulfill()
let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init()) let powerLevelsProxyMock = RoomPowerLevelsProxyMock(configuration: .init())
powerLevelsMock.canUserPinOrUnpinUserIDReturnValue = .success(false) powerLevelsProxyMock.canUserPinOrUnpinUserIDReturnValue = .success(false)
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsMock) roomProxyMock.powerLevelsReturnValue = .success(powerLevelsProxyMock)
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
roomInfoProxyMock.powerLevels = powerLevelsProxyMock
deferred = deferFulfillment(viewModel.context.$viewState) { value in deferred = deferFulfillment(viewModel.context.$viewState) { value in
!value.canCurrentUserPin !value.canCurrentUserPin
} }
infoSubject.send(.init(roomInfo: RoomInfo(configuration))) infoSubject.send(roomInfoProxyMock)
try await deferred.fulfill() try await deferred.fulfill()
} }
+1 -1
View File
@@ -65,7 +65,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/element-hq/matrix-rust-components-swift url: https://github.com/element-hq/matrix-rust-components-swift
exactVersion: 25.06.20 exactVersion: 25.06.23
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/element-hq/compound-ios url: https://github.com/element-hq/compound-ios