Show space invites in the room list. (#4545)

This commit is contained in:
Doug
2025-09-29 13:57:58 +01:00
committed by GitHub
parent 1f7c324077
commit c5fccdce5e
18 changed files with 313 additions and 44 deletions
@@ -1,4 +1,4 @@
// Generated using Sourcery 2.2.7 — https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.3.0 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable all
@@ -8149,6 +8149,75 @@ open class EncryptionSDKMock: MatrixRustSDK.Encryption, @unchecked Sendable {
}
}
//MARK: - hasDevicesToVerifyAgainst
open var hasDevicesToVerifyAgainstThrowableError: Error?
var hasDevicesToVerifyAgainstUnderlyingCallsCount = 0
open var hasDevicesToVerifyAgainstCallsCount: Int {
get {
if Thread.isMainThread {
return hasDevicesToVerifyAgainstUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = hasDevicesToVerifyAgainstUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
hasDevicesToVerifyAgainstUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
hasDevicesToVerifyAgainstUnderlyingCallsCount = newValue
}
}
}
}
open var hasDevicesToVerifyAgainstCalled: Bool {
return hasDevicesToVerifyAgainstCallsCount > 0
}
var hasDevicesToVerifyAgainstUnderlyingReturnValue: Bool!
open var hasDevicesToVerifyAgainstReturnValue: Bool! {
get {
if Thread.isMainThread {
return hasDevicesToVerifyAgainstUnderlyingReturnValue
} else {
var returnValue: Bool? = nil
DispatchQueue.main.sync {
returnValue = hasDevicesToVerifyAgainstUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
hasDevicesToVerifyAgainstUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
hasDevicesToVerifyAgainstUnderlyingReturnValue = newValue
}
}
}
}
open var hasDevicesToVerifyAgainstClosure: (() async throws -> Bool)?
open override func hasDevicesToVerifyAgainst() async throws -> Bool {
if let error = hasDevicesToVerifyAgainstThrowableError {
throw error
}
hasDevicesToVerifyAgainstCallsCount += 1
if let hasDevicesToVerifyAgainstClosure = hasDevicesToVerifyAgainstClosure {
return try await hasDevicesToVerifyAgainstClosure()
} else {
return hasDevicesToVerifyAgainstReturnValue
}
}
//MARK: - isLastDevice
open var isLastDeviceThrowableError: Error?
@@ -14289,6 +14358,81 @@ open class RoomSDKMock: MatrixRustSDK.Room, @unchecked Sendable {
}
}
//MARK: - loadOrFetchEvent
open var loadOrFetchEventEventIdThrowableError: Error?
var loadOrFetchEventEventIdUnderlyingCallsCount = 0
open var loadOrFetchEventEventIdCallsCount: Int {
get {
if Thread.isMainThread {
return loadOrFetchEventEventIdUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = loadOrFetchEventEventIdUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
loadOrFetchEventEventIdUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
loadOrFetchEventEventIdUnderlyingCallsCount = newValue
}
}
}
}
open var loadOrFetchEventEventIdCalled: Bool {
return loadOrFetchEventEventIdCallsCount > 0
}
open var loadOrFetchEventEventIdReceivedEventId: String?
open var loadOrFetchEventEventIdReceivedInvocations: [String] = []
var loadOrFetchEventEventIdUnderlyingReturnValue: TimelineEvent!
open var loadOrFetchEventEventIdReturnValue: TimelineEvent! {
get {
if Thread.isMainThread {
return loadOrFetchEventEventIdUnderlyingReturnValue
} else {
var returnValue: TimelineEvent? = nil
DispatchQueue.main.sync {
returnValue = loadOrFetchEventEventIdUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
loadOrFetchEventEventIdUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
loadOrFetchEventEventIdUnderlyingReturnValue = newValue
}
}
}
}
open var loadOrFetchEventEventIdClosure: ((String) async throws -> TimelineEvent)?
open override func loadOrFetchEvent(eventId: String) async throws -> TimelineEvent {
if let error = loadOrFetchEventEventIdThrowableError {
throw error
}
loadOrFetchEventEventIdCallsCount += 1
loadOrFetchEventEventIdReceivedEventId = eventId
DispatchQueue.main.async {
self.loadOrFetchEventEventIdReceivedInvocations.append(eventId)
}
if let loadOrFetchEventEventIdClosure = loadOrFetchEventEventIdClosure {
return try await loadOrFetchEventEventIdClosure(eventId)
} else {
return loadOrFetchEventEventIdReturnValue
}
}
//MARK: - markAsRead
open var markAsReadReceiptTypeThrowableError: Error?
@@ -25317,6 +25461,71 @@ open class TimelineEventSDKMock: MatrixRustSDK.TimelineEvent, @unchecked Sendabl
}
}
//MARK: - threadRootEventId
var threadRootEventIdUnderlyingCallsCount = 0
open var threadRootEventIdCallsCount: Int {
get {
if Thread.isMainThread {
return threadRootEventIdUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = threadRootEventIdUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
threadRootEventIdUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
threadRootEventIdUnderlyingCallsCount = newValue
}
}
}
}
open var threadRootEventIdCalled: Bool {
return threadRootEventIdCallsCount > 0
}
var threadRootEventIdUnderlyingReturnValue: String?
open var threadRootEventIdReturnValue: String? {
get {
if Thread.isMainThread {
return threadRootEventIdUnderlyingReturnValue
} else {
var returnValue: String?? = nil
DispatchQueue.main.sync {
returnValue = threadRootEventIdUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
threadRootEventIdUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
threadRootEventIdUnderlyingReturnValue = newValue
}
}
}
}
open var threadRootEventIdClosure: (() -> String?)?
open override func threadRootEventId() -> String? {
threadRootEventIdCallsCount += 1
if let threadRootEventIdClosure = threadRootEventIdClosure {
return threadRootEventIdClosure()
} else {
return threadRootEventIdReturnValue
}
}
//MARK: - timestamp
var timestampUnderlyingCallsCount = 0
@@ -76,6 +76,7 @@ extension RoomSummary {
joinRequestType: nil,
name: name,
isDirect: false,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -101,6 +102,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Foundation 🔭🪐🌌",
isDirect: false,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -121,6 +123,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Foundation and Empire",
isDirect: false,
isSpace: false,
avatarURL: .mockMXCAvatar,
heroes: [],
activeMembersCount: 0,
@@ -141,6 +144,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Second Foundation",
isDirect: false,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -161,6 +165,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Foundation's Edge",
isDirect: false,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -181,6 +186,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Foundation and Earth",
isDirect: true,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -201,6 +207,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Prelude to Foundation",
isDirect: true,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -221,6 +228,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Tombstoned",
isDirect: false,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -241,6 +249,7 @@ extension Array where Element == RoomSummary {
joinRequestType: nil,
name: "Unknown",
isDirect: false,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -294,6 +303,7 @@ extension Array where Element == RoomSummary {
joinRequestType: .invite(inviter: RoomMemberProxyMock.mockCharlie),
name: "First room",
isDirect: false,
isSpace: false,
avatarURL: .mockMXCAvatar,
heroes: [],
activeMembersCount: 0,
@@ -314,6 +324,7 @@ extension Array where Element == RoomSummary {
joinRequestType: .invite(inviter: RoomMemberProxyMock.mockCharlie),
name: "Second room",
isDirect: true,
isSpace: false,
avatarURL: nil,
heroes: [],
activeMembersCount: 0,
@@ -155,23 +155,34 @@ struct HomeScreenInviteCell_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
VStack(spacing: 0) {
HomeScreenInviteCell(room: .dmInvite,
context: makeViewModel().context, hideInviteAvatars: false)
context: makeViewModel().context,
hideInviteAvatars: false)
HomeScreenInviteCell(room: .dmInvite,
context: makeViewModel().context, hideInviteAvatars: false)
HomeScreenInviteCell(room: .invite(),
context: makeViewModel().context,
hideInviteAvatars: false)
HomeScreenInviteCell(room: .roomInvite(),
context: makeViewModel().context, hideInviteAvatars: false)
HomeScreenInviteCell(room: .invite(alias: "#footest:somewhere.org",
avatarURL: .mockMXCAvatar),
context: makeViewModel().context,
hideInviteAvatars: false)
HomeScreenInviteCell(room: .roomInvite(),
context: makeViewModel().context, hideInviteAvatars: false)
// Not the final design, may get its own cell type entirely.
HomeScreenInviteCell(room: .invite(name: "Awesome Space",
isSpace: true,
alias: "#footest:somewhere.org",
avatarURL: .mockMXCAvatar),
context: makeViewModel().context,
hideInviteAvatars: false)
HomeScreenInviteCell(room: .roomInvite(alias: "#footest:somewhere.org", avatarURL: .mockMXCAvatar),
context: makeViewModel().context, hideInviteAvatars: false)
HomeScreenInviteCell(room: .roomInvite(alias: "#footest-hidden-avatars:somewhere.org", avatarURL: .mockMXCAvatar),
context: makeViewModel().context, hideInviteAvatars: true)
HomeScreenInviteCell(room: .roomInvite(alias: "#footest:somewhere.org"),
context: makeViewModel().context, hideInviteAvatars: false)
HomeScreenInviteCell(room: .invite(name: "Hidden Avatars",
avatarURL: .mockMXCAvatar),
context: makeViewModel().context,
hideInviteAvatars: true)
HomeScreenInviteCell(room: .invite(alias: "#footest:somewhere.org"),
context: makeViewModel().context,
hideInviteAvatars: false)
.dynamicTypeSize(.accessibility1)
.previewDisplayName("Aliased room (AX1)")
}
@@ -204,6 +215,7 @@ private extension HomeScreenRoom {
joinRequestType: .invite(inviter: inviter),
name: "Some Guy",
isDirect: true,
isSpace: false,
avatarURL: nil,
heroes: [.init(userID: "@someone:somewhere.com")],
activeMembersCount: 0,
@@ -223,7 +235,10 @@ private extension HomeScreenRoom {
return .init(summary: summary, hideUnreadMessagesBadge: false)
}
static func roomInvite(alias: String? = nil, avatarURL: URL? = nil) -> HomeScreenRoom {
static func invite(name: String = "Awesome Room",
isSpace: Bool = false,
alias: String? = nil,
avatarURL: URL? = nil) -> HomeScreenRoom {
let inviter = RoomMemberProxyMock()
inviter.displayName = "Luca"
inviter.userID = "@jack:somewhi.nl"
@@ -232,8 +247,9 @@ private extension HomeScreenRoom {
let summary = RoomSummary(room: RoomSDKMock(),
id: "@someone:somewhere.com",
joinRequestType: .invite(inviter: inviter),
name: "Awesome Room",
name: name,
isDirect: false,
isSpace: isSpace,
avatarURL: avatarURL,
heroes: [.init(userID: "@someone:somewhere.com")],
activeMembersCount: 0,
@@ -149,6 +149,7 @@ private extension HomeScreenRoom {
joinRequestType: .invite(inviter: inviter),
name: "Some Guy",
isDirect: true,
isSpace: false,
avatarURL: nil,
heroes: [.init(userID: "@someone:somewhere.com")],
activeMembersCount: 0,
@@ -179,6 +180,7 @@ private extension HomeScreenRoom {
joinRequestType: .invite(inviter: inviter),
name: "Awesome Room",
isDirect: false,
isSpace: false,
avatarURL: avatarURL,
heroes: [.init(userID: "@someone:somewhere.com")],
activeMembersCount: 0,
@@ -55,6 +55,8 @@ struct RoomEventStringBuilder {
default: L10n.commonWaitingForDecryptionKey
}
return prefix(errorMessage, with: displayName, isOutgoing: isOutgoing)
case .other(eventType: let eventType):
return nil // We shouldn't receive these without asking for custom event types.
}
case .failedToParseMessageLike, .failedToParseState:
return prefix(L10n.commonUnsupportedEvent, with: displayName, isOutgoing: isOutgoing)
@@ -37,6 +37,7 @@ struct RoomSummary {
let name: String
let isDirect: Bool
let isSpace: Bool
let avatarURL: URL?
let heroes: [UserProfileProxy]
@@ -107,6 +108,7 @@ extension RoomSummary {
let string = "\(settingsMode) - messages: \(hasUnreadMessages) - mentions: \(hasUnreadMentions) - notifications: \(hasUnreadNotifications)"
name = string
isDirect = true
isSpace = false
avatarURL = nil
heroes = []
@@ -134,12 +136,9 @@ extension RoomSummary {
return .tombstoned
}
// NOTE: The check for isSpace isn't implemented yet, waiting to see
// whether we end up with a different type for spaces in the room list.
//
// Don't forget to add a test when you remove this comment 😄
if isDirect, avatarURL == nil, heroes.count == 1 {
if isSpace {
return .space(id: id, name: name, avatarURL: avatarURL)
} else if isDirect, avatarURL == nil, heroes.count == 1 {
return .heroes(heroes)
} else {
return .room(id: id, name: name, avatarURL: avatarURL)
@@ -118,19 +118,26 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
}
func setFilter(_ filter: RoomSummaryProviderFilter) {
let baseFilter: [RoomListEntriesDynamicFilterKind] = if appSettings.spacesEnabled {
[.any(filters: [.all(filters: [.nonSpace, .nonLeft]),
.all(filters: [.space, .invite])]),
.deduplicateVersions]
} else {
[.nonLeft, .nonSpace, .deduplicateVersions]
}
switch filter {
case .excludeAll:
_ = listUpdatesSubscriptionResult?.controller().setFilter(kind: .none)
case let .search(query):
let filters: [RoomListEntriesDynamicFilterKind] = if appSettings.fuzzyRoomListSearchEnabled {
[.fuzzyMatchRoomName(pattern: query), .nonLeft, .nonSpace, .deduplicateVersions]
let filters = if appSettings.fuzzyRoomListSearchEnabled {
[.fuzzyMatchRoomName(pattern: query)] + baseFilter
} else {
[.normalizedMatchRoomName(pattern: query), .nonLeft, .nonSpace, .deduplicateVersions]
[.normalizedMatchRoomName(pattern: query)] + baseFilter
}
_ = listUpdatesSubscriptionResult?.controller().setFilter(kind: .all(filters: filters))
case let .all(filters):
var rustFilters = filters.map(\.rustFilter)
rustFilters.append(contentsOf: [.nonLeft, .nonSpace, .deduplicateVersions])
var rustFilters = filters.map(\.rustFilter) + baseFilter
if !filters.contains(.lowPriority), appSettings.lowPriorityFilterEnabled {
rustFilters.append(.nonLowPriority)
@@ -277,6 +284,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
joinRequestType: joinRequestType,
name: roomInfo.displayName ?? roomInfo.id,
isDirect: roomInfo.isDirect,
isSpace: roomInfo.isSpace,
avatarURL: roomInfo.avatarUrl.flatMap(URL.init(string:)),
heroes: roomInfo.heroes.map(UserProfileProxy.init),
activeMembersCount: UInt(roomInfo.activeMembersCount),
@@ -40,6 +40,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return buildRedactedTimelineItem(eventItemProxy, messageLikeContent, isOutgoing)
case .unableToDecrypt(let encryptedMessage):
return buildEncryptedTimelineItem(eventItemProxy, messageLikeContent, encryptedMessage, isOutgoing)
case .other(eventType: let eventType):
return nil // We shouldn't receive these without asking for custom event types.
}
case .failedToParseMessageLike(let eventType, let error):
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)