mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
c41a38159b
* Initial plan * Offer only joined rooms in destination pickers Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com> * Clean whitespace in share picker test Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com> * Restrict the shared picker room list to joined rooms The alternate room summary provider, which backs the forward, share and Add rooms pickers and global search, now always includes the SDK's joined filter in its base filter. This replaces the per-call joinedOnly flags, so Add rooms no longer offers invited rooms or spaces and no screen can forget the flag. A filter set before the provider has its room list is now applied once the list is set, instead of being reset to the default. The share picker hit this on a cold launch from the share extension. The forward and share pickers also hide upgraded rooms, matching the Suggestions section. The SDK has no filter for these. Also registers the share picker's test file in the project, fixes the blank-line indentation SwiftFormat flagged, and re-records the picker snapshots without the upgraded room. * Check room membership for forwarding suggestions and search breadcrumbs RoomSummary now has isJoined, built from the room's membership. The forwarding suggestions and the search breadcrumbs used a missing joinRequestType to mean joined, which is also the case for rooms that have been left or banned from. It only worked because the room list's base filter already drops those. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com> Co-authored-by: Mauro Romito <mauro.romito@element.io>
196 lines
5.9 KiB
Swift
196 lines
5.9 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2022-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
|
|
|
|
/// A quick summary of a Room, useful to describe and give quick informations for the room list
|
|
nonisolated struct RoomSummary {
|
|
enum JoinRequestType {
|
|
case invite(inviter: RoomMemberProxyProtocol?)
|
|
case knock
|
|
|
|
var isInvite: Bool {
|
|
switch self {
|
|
case .invite: true
|
|
default: false
|
|
}
|
|
}
|
|
}
|
|
|
|
enum LastMessageState { case sending, failed }
|
|
|
|
let room: Room
|
|
|
|
let id: String
|
|
|
|
let joinRequestType: JoinRequestType?
|
|
let isJoined: Bool
|
|
|
|
let name: String
|
|
let isDirect: Bool
|
|
let isSpace: Bool
|
|
let avatarURL: URL?
|
|
|
|
let heroes: [UserProfile]
|
|
let activeMembersCount: UInt
|
|
|
|
let lastMessage: AttributedString?
|
|
let lastMessageDate: Date?
|
|
let lastMessageState: LastMessageState?
|
|
let unreadMessagesCount: UInt
|
|
let unreadMentionsCount: UInt
|
|
let unreadNotificationsCount: UInt
|
|
let notificationMode: RoomNotificationModeProxy?
|
|
let canonicalAlias: String?
|
|
let alternativeAliases: Set<String>
|
|
|
|
let hasOngoingCall: Bool
|
|
let activeCallIntent: CallIntent?
|
|
|
|
let isMarkedUnread: Bool
|
|
let isFavourite: Bool
|
|
let isTombstoned: Bool
|
|
|
|
var hasUnreadMessages: Bool {
|
|
unreadMessagesCount > 0
|
|
}
|
|
|
|
var hasUnreadMentions: Bool {
|
|
unreadMentionsCount > 0
|
|
}
|
|
|
|
var hasUnreadNotifications: Bool {
|
|
unreadNotificationsCount > 0
|
|
}
|
|
|
|
var isMuted: Bool {
|
|
notificationMode == .mute
|
|
}
|
|
}
|
|
|
|
nonisolated extension RoomSummary: CustomStringConvertible {
|
|
var description: String {
|
|
"""
|
|
RoomSummary: - id: \(id) \
|
|
- isDirect: \(isDirect) \
|
|
- unreadMessagesCount: \(unreadMessagesCount) \
|
|
- unreadMentionsCount: \(unreadMentionsCount) \
|
|
- unreadNotificationsCount: \(unreadNotificationsCount) \
|
|
- notificationMode: \(notificationMode?.rawValue ?? "nil")
|
|
"""
|
|
}
|
|
|
|
/// Used where summaries are shown in a list e.g. message forwarding,
|
|
/// global search, share destination list etc.
|
|
var roomListDescription: String {
|
|
if isDirect {
|
|
return canonicalAlias ?? ""
|
|
}
|
|
|
|
if let alias = canonicalAlias {
|
|
return alias
|
|
}
|
|
|
|
guard !heroes.isEmpty else {
|
|
return ""
|
|
}
|
|
|
|
var heroComponents = heroes.compactMap(\.displayName)
|
|
|
|
let othersCount = Int(activeMembersCount) - heroes.count
|
|
if othersCount > 0 {
|
|
heroComponents.append(L10n.commonManyMembers(othersCount))
|
|
}
|
|
|
|
return heroComponents.formatted(.list(type: .and))
|
|
}
|
|
}
|
|
|
|
nonisolated extension RoomSummary {
|
|
/// An empty summary used when the room info is momentarily unavailable (e.g. while the client tears
|
|
/// down on logout). Keeps the diff indices aligned with the SDK instead of crashing.
|
|
nonisolated static func placeholder(room: Room) -> RoomSummary {
|
|
RoomSummary(room: room,
|
|
id: room.id(),
|
|
joinRequestType: nil,
|
|
isJoined: false,
|
|
name: room.id(),
|
|
isDirect: false,
|
|
isSpace: false,
|
|
avatarURL: nil,
|
|
heroes: [],
|
|
activeMembersCount: 0,
|
|
lastMessage: nil,
|
|
lastMessageDate: nil,
|
|
lastMessageState: nil,
|
|
unreadMessagesCount: 0,
|
|
unreadMentionsCount: 0,
|
|
unreadNotificationsCount: 0,
|
|
notificationMode: nil,
|
|
canonicalAlias: nil,
|
|
alternativeAliases: [],
|
|
hasOngoingCall: false,
|
|
activeCallIntent: nil,
|
|
isMarkedUnread: false,
|
|
isFavourite: false,
|
|
isTombstoned: false)
|
|
}
|
|
|
|
init(room: Room, id: String, settingsMode: RoomNotificationModeProxy, hasUnreadMessages: Bool, hasUnreadMentions: Bool, hasUnreadNotifications: Bool) {
|
|
self.room = room
|
|
self.id = id
|
|
let string = "\(settingsMode) - messages: \(hasUnreadMessages) - mentions: \(hasUnreadMentions) - notifications: \(hasUnreadNotifications)"
|
|
name = string
|
|
isDirect = true
|
|
isSpace = false
|
|
avatarURL = nil
|
|
|
|
heroes = []
|
|
activeMembersCount = 0
|
|
|
|
lastMessage = AttributedString(string)
|
|
lastMessageDate = .mock
|
|
lastMessageState = nil
|
|
unreadMessagesCount = hasUnreadMessages ? 1 : 0
|
|
unreadMentionsCount = hasUnreadMentions ? 1 : 0
|
|
unreadNotificationsCount = hasUnreadNotifications ? 1 : 0
|
|
notificationMode = settingsMode
|
|
canonicalAlias = nil
|
|
alternativeAliases = []
|
|
hasOngoingCall = false
|
|
activeCallIntent = nil
|
|
|
|
joinRequestType = nil
|
|
isJoined = true
|
|
isMarkedUnread = false
|
|
isFavourite = false
|
|
isTombstoned = false
|
|
}
|
|
|
|
/// This doesn't have to work properly for DM invites, the heroes are always empty
|
|
var avatar: RoomAvatar {
|
|
guard !isTombstoned else {
|
|
return .tombstoned
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
var statusEmoji: Character? {
|
|
guard case let .heroes(heroes) = avatar else { return nil }
|
|
return heroes.first?.status.displayed?.emoji
|
|
}
|
|
}
|