mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Pinned items timeline implementation for the banner (#3099)
This commit is contained in:
@@ -759,10 +759,11 @@ class ClientProxy: ClientProxyProtocol {
|
||||
let roomListService = syncService.roomListService()
|
||||
|
||||
let roomMessageEventStringBuilder = RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(cacheKey: "roomList",
|
||||
mentionBuilder: PlainMentionBuilder()))
|
||||
mentionBuilder: PlainMentionBuilder()), prefix: .senderName)
|
||||
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID, shouldDisambiguateDisplayNames: false),
|
||||
messageEventStringBuilder: roomMessageEventStringBuilder,
|
||||
shouldDisambiguateDisplayNames: false)
|
||||
shouldDisambiguateDisplayNames: false,
|
||||
shouldPrefixSenderName: true)
|
||||
|
||||
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||
eventStringBuilder: eventStringBuilder,
|
||||
|
||||
@@ -23,6 +23,24 @@ class RoomProxy: RoomProxyProtocol {
|
||||
private let roomListItem: RoomListItemProtocol
|
||||
private let room: RoomProtocol
|
||||
let timeline: TimelineProxyProtocol
|
||||
private var innerPinnedEventsTimeline: TimelineProxyProtocol?
|
||||
var pinnedEventsTimeline: TimelineProxyProtocol? {
|
||||
get async {
|
||||
if let innerPinnedEventsTimeline {
|
||||
return innerPinnedEventsTimeline
|
||||
} else {
|
||||
do {
|
||||
let timeline = try await TimelineProxy(timeline: room.pinnedEventsTimeline(internalIdPrefix: nil, maxEventsToLoad: 100), isLive: false)
|
||||
await timeline.subscribeForUpdates()
|
||||
innerPinnedEventsTimeline = timeline
|
||||
return timeline
|
||||
} catch {
|
||||
MXLog.error("Failed creating pinned events timeline with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// periphery:ignore - required for instance retention in the rust codebase
|
||||
private var roomInfoObservationToken: TaskHandle?
|
||||
@@ -92,9 +110,12 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
var pinnedEventIDs: [String] {
|
||||
var pinnedEventIDs: Set<String> {
|
||||
get async {
|
||||
await (try? room.roomInfo().pinnedEventIds) ?? []
|
||||
guard let pinnedEventIDs = try? await room.roomInfo().pinnedEventIds else {
|
||||
return []
|
||||
}
|
||||
return .init(pinnedEventIDs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ protocol RoomProxyProtocol {
|
||||
var isSpace: Bool { get }
|
||||
var isEncrypted: Bool { get }
|
||||
var isFavourite: Bool { get async }
|
||||
var pinnedEventIDs: [String] { get async }
|
||||
var pinnedEventIDs: Set<String> { get async }
|
||||
var membership: Membership { get }
|
||||
var inviter: RoomMemberProxyProtocol? { get async }
|
||||
var hasOngoingCall: Bool { get }
|
||||
@@ -66,6 +66,8 @@ protocol RoomProxyProtocol {
|
||||
|
||||
var timeline: TimelineProxyProtocol { get }
|
||||
|
||||
var pinnedEventsTimeline: TimelineProxyProtocol? { get async }
|
||||
|
||||
func subscribeForUpdates() async
|
||||
|
||||
func subscribeToRoomInfoUpdates()
|
||||
|
||||
@@ -21,6 +21,7 @@ struct RoomEventStringBuilder {
|
||||
let stateEventStringBuilder: RoomStateEventStringBuilder
|
||||
let messageEventStringBuilder: RoomMessageEventStringBuilder
|
||||
let shouldDisambiguateDisplayNames: Bool
|
||||
let shouldPrefixSenderName: Bool
|
||||
|
||||
func buildAttributedString(for eventItemProxy: EventTimelineItemProxy) -> AttributedString? {
|
||||
let sender = eventItemProxy.sender
|
||||
@@ -50,7 +51,7 @@ struct RoomEventStringBuilder {
|
||||
}
|
||||
|
||||
let messageType = messageContent.msgtype()
|
||||
return messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, prefixWithSenderName: true)
|
||||
return messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName)
|
||||
case .state(_, let state):
|
||||
return stateEventStringBuilder
|
||||
.buildString(for: state, sender: sender, isOutgoing: isOutgoing)
|
||||
@@ -78,6 +79,9 @@ struct RoomEventStringBuilder {
|
||||
}
|
||||
|
||||
private func prefix(_ eventSummary: String, with senderDisplayName: String) -> AttributedString {
|
||||
guard shouldPrefixSenderName else {
|
||||
return AttributedString(eventSummary)
|
||||
}
|
||||
let attributedEventSummary = AttributedString(eventSummary.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
|
||||
var attributedSenderDisplayName = AttributedString(senderDisplayName)
|
||||
@@ -86,4 +90,13 @@ struct RoomEventStringBuilder {
|
||||
// Don't include the message body in the markdown otherwise it makes tappable links.
|
||||
return attributedSenderDisplayName + ": " + attributedEventSummary
|
||||
}
|
||||
|
||||
static func pinnedEventStringBuilder(userID: String) -> Self {
|
||||
RoomEventStringBuilder(stateEventStringBuilder: .init(userID: userID,
|
||||
shouldDisambiguateDisplayNames: false),
|
||||
messageEventStringBuilder: .init(attributedStringBuilder: AttributedStringBuilder(cacheKey: "pinnedEvents", mentionBuilder: PlainMentionBuilder()),
|
||||
prefix: .mediaType),
|
||||
shouldDisambiguateDisplayNames: false,
|
||||
shouldPrefixSenderName: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,17 @@ import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct RoomMessageEventStringBuilder {
|
||||
let attributedStringBuilder: AttributedStringBuilderProtocol
|
||||
enum Prefix {
|
||||
case senderName
|
||||
case mediaType
|
||||
case none
|
||||
}
|
||||
|
||||
func buildAttributedString(for messageType: MessageType, senderDisplayName: String, prefixWithSenderName: Bool) -> AttributedString {
|
||||
let message: String
|
||||
let attributedStringBuilder: AttributedStringBuilderProtocol
|
||||
let prefix: Prefix
|
||||
|
||||
func buildAttributedString(for messageType: MessageType, senderDisplayName: String) -> AttributedString {
|
||||
let message: AttributedString
|
||||
switch messageType {
|
||||
// Message types that don't need a prefix.
|
||||
case .emote(content: let content):
|
||||
@@ -33,46 +40,54 @@ struct RoomMessageEventStringBuilder {
|
||||
// Message types that should be prefixed with the sender's name.
|
||||
case .audio(content: let content):
|
||||
let isVoiceMessage = content.voice != nil
|
||||
message = isVoiceMessage ? L10n.commonVoiceMessage : L10n.commonAudio
|
||||
var content = AttributedString(isVoiceMessage ? L10n.commonVoiceMessage : L10n.commonAudio)
|
||||
if prefix == .mediaType {
|
||||
content.bold()
|
||||
}
|
||||
message = content
|
||||
case .image(let content):
|
||||
message = "\(L10n.commonImage) - \(content.body)"
|
||||
message = prefix == .mediaType ? prefix(AttributedString(content.body), with: L10n.commonImage) : AttributedString("\(L10n.commonImage) - \(content.body)")
|
||||
case .video(let content):
|
||||
message = "\(L10n.commonVideo) - \(content.body)"
|
||||
message = prefix == .mediaType ? prefix(AttributedString(content.body), with: L10n.commonVideo) : AttributedString("\(L10n.commonVideo) - \(content.body)")
|
||||
case .file(let content):
|
||||
message = "\(L10n.commonFile) - \(content.body)"
|
||||
message = prefix == .mediaType ? prefix(AttributedString(content.body), with: L10n.commonFile) : AttributedString("\(L10n.commonFile) - \(content.body)")
|
||||
case .location:
|
||||
message = L10n.commonSharedLocation
|
||||
var content = AttributedString(L10n.commonSharedLocation)
|
||||
if prefix == .mediaType {
|
||||
content.bold()
|
||||
}
|
||||
message = content
|
||||
case .notice(content: let content):
|
||||
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
|
||||
message = String(attributedMessage.characters)
|
||||
message = attributedMessage
|
||||
} else {
|
||||
message = content.body
|
||||
message = AttributedString(content.body)
|
||||
}
|
||||
case .text(content: let content):
|
||||
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
|
||||
message = String(attributedMessage.characters)
|
||||
message = attributedMessage
|
||||
} else {
|
||||
message = content.body
|
||||
message = AttributedString(content.body)
|
||||
}
|
||||
case .other(_, let body):
|
||||
message = body
|
||||
message = AttributedString(body)
|
||||
}
|
||||
|
||||
if prefixWithSenderName {
|
||||
if prefix == .senderName {
|
||||
return prefix(message, with: senderDisplayName)
|
||||
} else {
|
||||
return AttributedString(message)
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
private func prefix(_ eventSummary: String, with senderDisplayName: String) -> AttributedString {
|
||||
let attributedEventSummary = AttributedString(eventSummary.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
private func prefix(_ eventSummary: AttributedString, with textToBold: String) -> AttributedString {
|
||||
let attributedEventSummary = AttributedString(eventSummary.string.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
|
||||
var attributedSenderDisplayName = AttributedString(senderDisplayName)
|
||||
attributedSenderDisplayName.bold()
|
||||
var attributedPrefix = AttributedString(textToBold + ":")
|
||||
attributedPrefix.bold()
|
||||
|
||||
// Don't include the message body in the markdown otherwise it makes tappable links.
|
||||
return attributedSenderDisplayName + ": " + attributedEventSummary
|
||||
return attributedPrefix + " " + attributedEventSummary
|
||||
}
|
||||
|
||||
private func attributedMessageFrom(formattedBody: FormattedBody?) -> AttributedString? {
|
||||
|
||||
@@ -42,16 +42,17 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
init(roomProxy: RoomProxyProtocol,
|
||||
timelineProxy: TimelineProxyProtocol,
|
||||
initialFocussedEventID: String?,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
appSettings: AppSettings) {
|
||||
self.roomProxy = roomProxy
|
||||
liveTimelineProvider = roomProxy.timeline.timelineProvider
|
||||
liveTimelineProvider = timelineProxy.timelineProvider
|
||||
self.timelineItemFactory = timelineItemFactory
|
||||
self.appSettings = appSettings
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
|
||||
activeTimeline = roomProxy.timeline
|
||||
activeTimeline = timelineProxy
|
||||
activeTimelineProvider = liveTimelineProvider
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||
initialFocussedEventID: String?,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol) -> RoomTimelineControllerProtocol {
|
||||
RoomTimelineController(roomProxy: roomProxy,
|
||||
timelineProxy: roomProxy.timeline,
|
||||
initialFocussedEventID: initialFocussedEventID,
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
Reference in New Issue
Block a user