mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 11:58:32 +00:00
SDK Changes Refactor (#1312)
* internal id * notification refactor * client proxy refactor * required self * better identifier system * using the event id when required * tests fixed * tested some stuff * fixed merge conflict * improved the test wait * animation disabled * Bump Rust SDK version to 1.0.98-alpha (#1310) * code improvement * pause sync * pr suggestions * result * Apply suggestions from code review Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * enum based debug identifier --------- Co-authored-by: aringenbach <80891108+aringenbach@users.noreply.github.com> Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
@@ -31,8 +31,8 @@ class ClientProxy: ClientProxyProtocol {
|
||||
private var roomListService: RoomListService?
|
||||
private var roomListStateUpdateTaskHandle: TaskHandle?
|
||||
|
||||
private var encryptionSyncService: EncryptionSync?
|
||||
private var isEncryptionSyncing = false
|
||||
private var appService: App?
|
||||
private var appServiceUpdateTaskHandle: TaskHandle?
|
||||
|
||||
var roomSummaryProvider: RoomSummaryProviderProtocol?
|
||||
var inviteSummaryProvider: RoomSummaryProviderProtocol?
|
||||
@@ -53,7 +53,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
|
||||
deinit {
|
||||
client.setDelegate(delegate: nil)
|
||||
stopSync()
|
||||
pauseSync()
|
||||
}
|
||||
|
||||
let callbacks = PassthroughSubject<ClientProxyCallback, Never>()
|
||||
@@ -73,7 +73,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
self?.callbacks.send(.updateRestorationToken)
|
||||
})
|
||||
|
||||
await configureRoomListService()
|
||||
await configureAppService()
|
||||
|
||||
loadUserAvatarURLFromCache()
|
||||
}
|
||||
@@ -110,13 +110,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
|
||||
var isSyncing: Bool {
|
||||
let isRoomListServiceSyncing = roomListService?.isSyncing() ?? false
|
||||
|
||||
if ServiceLocator.shared.settings.isEncryptionSyncEnabled {
|
||||
return isRoomListServiceSyncing && isEncryptionSyncing
|
||||
} else {
|
||||
return isRoomListServiceSyncing
|
||||
}
|
||||
roomListService?.isSyncing() ?? false
|
||||
}
|
||||
|
||||
func startSync() {
|
||||
@@ -125,29 +119,22 @@ class ClientProxy: ClientProxyProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
startEncryptionSyncService()
|
||||
roomListService?.sync()
|
||||
do {
|
||||
try appService?.start()
|
||||
} catch {
|
||||
MXLog.error("Failed starting app service with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func stopSync() {
|
||||
func pauseSync() {
|
||||
MXLog.info("Stopping sync")
|
||||
stopEncryptionSyncService()
|
||||
|
||||
do {
|
||||
try roomListService?.stopSync()
|
||||
try appService?.pause()
|
||||
} catch {
|
||||
MXLog.error("Failed stopping room list service with error: \(error)")
|
||||
MXLog.error("Failed pausing app service with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func stopEncryptionSyncService() {
|
||||
guard isEncryptionSyncing else {
|
||||
return
|
||||
}
|
||||
isEncryptionSyncing = false
|
||||
encryptionSyncService?.stop()
|
||||
MXLog.info("Stopping Encryption Sync service")
|
||||
}
|
||||
|
||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||
await Task.dispatch(on: clientQueue) {
|
||||
@@ -366,7 +353,7 @@ class ClientProxy: ClientProxyProtocol {
|
||||
// MARK: Private
|
||||
|
||||
private func restartSync() {
|
||||
stopSync()
|
||||
pauseSync()
|
||||
startSync()
|
||||
}
|
||||
|
||||
@@ -385,89 +372,82 @@ class ClientProxy: ClientProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func startEncryptionSyncService() {
|
||||
guard appSettings.isEncryptionSyncEnabled else {
|
||||
return
|
||||
}
|
||||
configureEncryptionSyncService()
|
||||
}
|
||||
|
||||
private func configureEncryptionSyncService() {
|
||||
do {
|
||||
let listener = EncryptionSyncListenerProxy { [weak self] reason in
|
||||
switch reason {
|
||||
case .done:
|
||||
MXLog.info("Encryption Sync has finished for user: \(self?.userID ?? "unknown")")
|
||||
case .error(let msg):
|
||||
MXLog.error("Encryption Sync has terminated for user: \(self?.userID ?? "unknown") for reason: \(msg)")
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
Task {
|
||||
self.configureEncryptionSyncService()
|
||||
}
|
||||
}
|
||||
}
|
||||
let encryptionSync = try client.mainEncryptionSync(id: "Main App", listener: listener)
|
||||
isEncryptionSyncing = true
|
||||
encryptionSyncService = encryptionSync
|
||||
MXLog.info("Encryption sync started for user: \(userID)")
|
||||
} catch {
|
||||
MXLog.error("Configure encryption sync failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func configureRoomListService() async {
|
||||
guard roomListService == nil else {
|
||||
private func configureAppService() async {
|
||||
guard appService == nil else {
|
||||
fatalError("This shouldn't be called more than once")
|
||||
}
|
||||
|
||||
do {
|
||||
let roomListService = try appSettings.isEncryptionSyncEnabled ? client.roomListService() : client.roomListServiceWithEncryption()
|
||||
roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
|
||||
guard let self else { return }
|
||||
MXLog.info("Received room list update: \(state)")
|
||||
|
||||
// Restart the room list sync on every error for now
|
||||
if state == .error {
|
||||
self.restartSync()
|
||||
}
|
||||
|
||||
// The invites are available only when entering `running`
|
||||
if state == .running {
|
||||
Task {
|
||||
do {
|
||||
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
|
||||
try await self.inviteSummaryProvider?.setRoomList(roomListService.invites())
|
||||
} catch {
|
||||
MXLog.error("Failed configuring invites room list with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Anything that's not `running` is interpreted as "Loading data"
|
||||
if state == .running {
|
||||
self.callbacks.send(.receivedSyncUpdate)
|
||||
} else {
|
||||
self.callbacks.send(.startedUpdating)
|
||||
}
|
||||
})
|
||||
|
||||
let appService = try client
|
||||
.app()
|
||||
.withEncryptionSync(withCrossProcessLock: appSettings.isEncryptionSyncEnabled,
|
||||
appIdentifier: "MainApp")
|
||||
.finish()
|
||||
let roomListService = appService.roomListService()
|
||||
|
||||
let eventStringBuilder = RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID))
|
||||
roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
||||
eventStringBuilder: eventStringBuilder,
|
||||
name: "AllRooms")
|
||||
|
||||
try await roomSummaryProvider?.setRoomList(roomListService.allRooms())
|
||||
|
||||
inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService,
|
||||
eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)),
|
||||
eventStringBuilder: eventStringBuilder,
|
||||
name: "Invites")
|
||||
|
||||
|
||||
self.appService = appService
|
||||
self.roomListService = roomListService
|
||||
|
||||
appServiceUpdateTaskHandle = createAppServiceObserver(appService)
|
||||
roomListStateUpdateTaskHandle = createRoomListServiceObserver(roomListService)
|
||||
|
||||
} catch {
|
||||
MXLog.error("Failed building room list service with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func createAppServiceObserver(_ appService: App) -> TaskHandle {
|
||||
appService.state(listener: AppStateObserverProxy { [weak self] state in
|
||||
guard let self else { return }
|
||||
MXLog.info("Received app service update: \(state)")
|
||||
switch state {
|
||||
case .error:
|
||||
restartSync()
|
||||
case .terminated, .running:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
|
||||
roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
|
||||
MXLog.info("Received room list update: \(state)")
|
||||
guard let self,
|
||||
state != .error,
|
||||
state != .terminated else {
|
||||
// The app service is responsible of handling error and termination
|
||||
return
|
||||
}
|
||||
|
||||
// The invites are available only when entering `running`
|
||||
if state == .running {
|
||||
Task {
|
||||
do {
|
||||
guard let roomListService = self.roomListService else {
|
||||
MXLog.error("Room list service is not configured")
|
||||
return
|
||||
}
|
||||
// Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms
|
||||
try await self.inviteSummaryProvider?.setRoomList(roomListService.invites())
|
||||
} catch {
|
||||
MXLog.error("Failed configuring invites room list with error: \(error)")
|
||||
}
|
||||
}
|
||||
callbacks.send(.receivedSyncUpdate)
|
||||
} else {
|
||||
callbacks.send(.startedUpdating)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func roomTupleForIdentifier(_ identifier: String) -> (RoomListItem?, Room?) {
|
||||
do {
|
||||
@@ -496,13 +476,25 @@ extension ClientProxy: MediaLoaderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private class RoomListStateListenerProxy: RoomListServiceStateListener {
|
||||
private let onUpdateClosure: (RoomListServiceState) -> Void
|
||||
|
||||
init(_ onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
|
||||
private class AppStateObserverProxy: AppStateObserver {
|
||||
private let onUpdateClosure: (AppState) -> Void
|
||||
|
||||
init(onUpdateClosure: @escaping (AppState) -> Void) {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
|
||||
func onUpdate(state: AppState) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
}
|
||||
|
||||
private class RoomListStateListenerProxy: RoomListServiceStateListener {
|
||||
private let onUpdateClosure: (RoomListServiceState) -> Void
|
||||
|
||||
init(onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
|
||||
self.onUpdateClosure = onUpdateClosure
|
||||
}
|
||||
|
||||
func onUpdate(state: RoomListServiceState) {
|
||||
onUpdateClosure(state)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
||||
|
||||
func startSync()
|
||||
|
||||
func stopSync()
|
||||
func pauseSync()
|
||||
|
||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class MockClientProxy: ClientProxyProtocol {
|
||||
|
||||
func stopSync(completionHandler: () -> Void) { }
|
||||
|
||||
func stopSync() { }
|
||||
func pauseSync() { }
|
||||
|
||||
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
|
||||
.failure(.failedRetrievingDirectRoom)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
final class EncryptionSyncListenerProxy: EncryptionSyncListener {
|
||||
private let didTerminateClosure: (EncryptionSyncTerminationReason) -> Void
|
||||
|
||||
init(_ didTerminateClosure: @escaping (EncryptionSyncTerminationReason) -> Void) {
|
||||
self.didTerminateClosure = didTerminateClosure
|
||||
}
|
||||
|
||||
func didTerminate(reason: EncryptionSyncTerminationReason) {
|
||||
didTerminateClosure(reason)
|
||||
}
|
||||
}
|
||||
@@ -67,15 +67,12 @@ extension NotificationItemProxyProtocol {
|
||||
struct NotificationItemProxy: NotificationItemProxyProtocol {
|
||||
let notificationItem: NotificationItem
|
||||
let receiverID: String
|
||||
let roomID: String
|
||||
|
||||
var event: TimelineEventProxyProtocol {
|
||||
TimelineEventProxy(timelineEvent: notificationItem.event)
|
||||
}
|
||||
|
||||
var roomID: String {
|
||||
notificationItem.roomInfo.id
|
||||
}
|
||||
|
||||
var senderDisplayName: String? {
|
||||
notificationItem.senderInfo.displayName
|
||||
}
|
||||
|
||||
@@ -140,7 +140,23 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
|
||||
MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })")
|
||||
}
|
||||
|
||||
|
||||
private func fetchLastMessage(roomListItem: RoomListItemProtocol) -> EventTimelineItem? {
|
||||
class FetchResult {
|
||||
var latestRoomEvent: EventTimelineItem?
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let result = FetchResult()
|
||||
|
||||
Task {
|
||||
result.latestRoomEvent = await roomListItem.latestEvent()
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
return result.latestRoomEvent
|
||||
}
|
||||
|
||||
private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary {
|
||||
guard let roomListItem = try? roomListService.room(roomId: identifier) else {
|
||||
MXLog.error("\(name): Failed finding room with id: \(identifier)")
|
||||
@@ -149,9 +165,9 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
|
||||
var attributedLastMessage: AttributedString?
|
||||
var lastMessageFormattedTimestamp: String?
|
||||
|
||||
if let latestRoomMessage = roomListItem.latestEvent() {
|
||||
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage)
|
||||
|
||||
if let latestRoomMessage = fetchLastMessage(roomListItem: roomListItem) {
|
||||
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage, id: 0)
|
||||
lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal()
|
||||
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
|
||||
}
|
||||
|
||||
@@ -19,15 +19,15 @@ import Foundation
|
||||
enum RoomTimelineItemFixtures {
|
||||
/// The default timeline items used in Xcode previews etc.
|
||||
static var `default`: [RoomTimelineItemProtocol] = [
|
||||
SeparatorRoomTimelineItem(id: "Yesterday", text: "Yesterday"),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
SeparatorRoomTimelineItem(id: .init(timelineID: "Yesterday"), text: "Yesterday"),
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "10:10 AM",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Jacob"),
|
||||
content: .init(body: "That looks so good!"),
|
||||
properties: RoomTimelineItemProperties(isEdited: true)),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "10:11 AM",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
@@ -36,7 +36,7 @@ enum RoomTimelineItemFixtures {
|
||||
properties: RoomTimelineItemProperties(reactions: [
|
||||
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: ["me"])
|
||||
])),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "10:11 AM",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
@@ -46,21 +46,21 @@ enum RoomTimelineItemFixtures {
|
||||
AggregatedReaction(accountOwnerID: "me", key: "🙏", senders: ["helena"]),
|
||||
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: ["me", "helena", "jacob"])
|
||||
])),
|
||||
SeparatorRoomTimelineItem(id: "Today", text: "Today"),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
SeparatorRoomTimelineItem(id: .init(timelineID: "Today"), text: "Today"),
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "5 PM",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"),
|
||||
properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "5 PM",
|
||||
isOutgoing: true,
|
||||
isEditable: true,
|
||||
sender: .init(id: "", displayName: "Bob"),
|
||||
content: .init(body: "And John's speech was amazing!")),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "5 PM",
|
||||
isOutgoing: true,
|
||||
isEditable: true,
|
||||
@@ -71,7 +71,7 @@ enum RoomTimelineItemFixtures {
|
||||
ReadReceipt(userID: "bob", formattedTimestamp: nil),
|
||||
ReadReceipt(userID: "charlie", formattedTimestamp: nil),
|
||||
ReadReceipt(userID: "dan", formattedTimestamp: nil)])),
|
||||
TextRoomTimelineItem(id: UUID().uuidString,
|
||||
TextRoomTimelineItem(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "5 PM",
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
@@ -210,7 +210,7 @@ enum RoomTimelineItemFixtures {
|
||||
|
||||
private extension TextRoomTimelineItem {
|
||||
init(text: String, senderDisplayName: String) {
|
||||
self.init(id: UUID().uuidString,
|
||||
self.init(id: .init(timelineID: UUID().uuidString),
|
||||
timestamp: "10:47 am",
|
||||
isOutgoing: senderDisplayName == "Alice",
|
||||
isEditable: false,
|
||||
|
||||
@@ -169,37 +169,47 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
}
|
||||
|
||||
private extension TimelineItem {
|
||||
var debugIdentifier: String {
|
||||
var debugIdentifier: DebugIdentifier {
|
||||
if let virtualTimelineItem = asVirtual() {
|
||||
return virtualTimelineItem.debugIdentifier
|
||||
} else if let eventTimelineItem = asEvent() {
|
||||
return eventTimelineItem.uniqueIdentifier()
|
||||
return .event(timelineID: String(uniqueId()),
|
||||
eventID: eventTimelineItem.eventId(),
|
||||
transactionID: eventTimelineItem.transactionId())
|
||||
}
|
||||
|
||||
return "UnknownTimelineItem"
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineItemProxy {
|
||||
var debugIdentifier: String {
|
||||
var debugIdentifier: DebugIdentifier {
|
||||
switch self {
|
||||
case .event(let eventTimelineItem):
|
||||
return eventTimelineItem.item.uniqueIdentifier()
|
||||
return .event(timelineID: eventTimelineItem.id.timelineID,
|
||||
eventID: eventTimelineItem.id.eventID,
|
||||
transactionID: eventTimelineItem.id.transactionID)
|
||||
case .virtual(let virtualTimelineItem):
|
||||
return virtualTimelineItem.debugIdentifier
|
||||
case .unknown:
|
||||
return "UnknownTimelineItem"
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension VirtualTimelineItem {
|
||||
var debugIdentifier: String {
|
||||
var debugIdentifier: DebugIdentifier {
|
||||
switch self {
|
||||
case .dayDivider(let timestamp):
|
||||
return "DayDiviver(\(timestamp))"
|
||||
return .virtual("DayDiviver(\(timestamp))")
|
||||
case .readMarker:
|
||||
return "ReadMarker"
|
||||
return .virtual("ReadMarker")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum DebugIdentifier {
|
||||
case event(timelineID: String?, eventID: String?, transactionID: String?)
|
||||
case virtual(String)
|
||||
case unknown
|
||||
}
|
||||
|
||||
+9
-9
@@ -51,23 +51,23 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> { .success(()) }
|
||||
|
||||
func processItemAppearance(_ itemID: String) async { }
|
||||
func processItemAppearance(_ itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func processItemDisappearance(_ itemID: String) async { }
|
||||
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction { .none }
|
||||
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction { .none }
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async { }
|
||||
func sendMessage(_ message: String, inReplyTo itemID: TimelineItemIdentifier?) async { }
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: String) async { }
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func editMessage(_ newMessage: String, original itemID: String) async { }
|
||||
func editMessage(_ newMessage: String, original itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func redact(_ itemID: String) async { }
|
||||
func redact(_ itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func cancelSend(_ transactionID: String) async { }
|
||||
func cancelSend(_ itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
|
||||
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
|
||||
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
|
||||
}
|
||||
|
||||
|
||||
+45
-23
@@ -84,7 +84,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError> {
|
||||
guard roomProxy.hasUnreadNotifications,
|
||||
let eventID = timelineItems.last?.id
|
||||
let eventID = timelineItems.last?.id.eventID
|
||||
else { return .success(()) }
|
||||
|
||||
switch await roomProxy.sendReadReceipt(for: eventID) {
|
||||
@@ -95,7 +95,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func processItemAppearance(_ itemID: String) async {
|
||||
func processItemAppearance(_ itemID: TimelineItemIdentifier) async {
|
||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
||||
return
|
||||
}
|
||||
@@ -105,9 +105,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func processItemDisappearance(_ itemID: String) { }
|
||||
func processItemDisappearance(_ itemID: TimelineItemIdentifier) { }
|
||||
|
||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction {
|
||||
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction {
|
||||
guard let timelineItem = timelineItems.first(where: { $0.id == itemID }) else {
|
||||
return .none
|
||||
}
|
||||
@@ -121,14 +121,19 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async {
|
||||
func sendMessage(_ message: String, inReplyTo itemID: TimelineItemIdentifier?) async {
|
||||
var inReplyTo: String?
|
||||
if itemID == nil {
|
||||
MXLog.info("Send message in \(roomID)")
|
||||
} else {
|
||||
} else if let eventID = itemID?.eventID {
|
||||
inReplyTo = eventID
|
||||
MXLog.info("Send reply in \(roomID)")
|
||||
} else {
|
||||
MXLog.error("Send reply in \(roomID) failed: missing event ID")
|
||||
return
|
||||
}
|
||||
|
||||
switch await roomProxy.sendMessage(message, inReplyTo: itemID) {
|
||||
|
||||
switch await roomProxy.sendMessage(message, inReplyTo: inReplyTo) {
|
||||
case .success:
|
||||
MXLog.info("Finished sending message")
|
||||
case .failure(let error):
|
||||
@@ -136,9 +141,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: String) async {
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async {
|
||||
MXLog.info("Toggle reaction in \(roomID)")
|
||||
switch await roomProxy.toggleReaction(reaction, to: itemID) {
|
||||
guard let eventID = itemID.eventID else {
|
||||
MXLog.error("Failed toggling reaction: missing eventID")
|
||||
return
|
||||
}
|
||||
|
||||
switch await roomProxy.toggleReaction(reaction, to: eventID) {
|
||||
case .success:
|
||||
MXLog.info("Finished toggling reaction")
|
||||
case .failure(let error):
|
||||
@@ -146,28 +156,32 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, original itemID: String) async {
|
||||
func editMessage(_ newMessage: String, original itemID: TimelineItemIdentifier) async {
|
||||
MXLog.info("Edit message in \(roomID)")
|
||||
if let timelineItem = timelineItems.first(where: { $0.id == itemID }),
|
||||
let item = timelineItem as? EventBasedTimelineItemProtocol,
|
||||
item.hasFailedToSend,
|
||||
let transactionID = item.properties.transactionID {
|
||||
item.hasFailedToSend {
|
||||
MXLog.info("Editing a failed echo, will cancel and resend it as a new message")
|
||||
await cancelSend(transactionID)
|
||||
await cancelSend(itemID)
|
||||
await sendMessage(newMessage)
|
||||
} else {
|
||||
switch await roomProxy.editMessage(newMessage, original: itemID) {
|
||||
} else if let eventID = itemID.eventID {
|
||||
switch await roomProxy.editMessage(newMessage, original: eventID) {
|
||||
case .success:
|
||||
MXLog.info("Finished editing message")
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed editing message with error: \(error)")
|
||||
}
|
||||
} else {
|
||||
MXLog.error("Editing failed: missing identifiers")
|
||||
}
|
||||
}
|
||||
|
||||
func redact(_ itemID: String) async {
|
||||
func redact(_ itemID: TimelineItemIdentifier) async {
|
||||
MXLog.info("Send redaction in \(roomID)")
|
||||
switch await roomProxy.redact(itemID) {
|
||||
guard let eventID = itemID.eventID else {
|
||||
return
|
||||
}
|
||||
switch await roomProxy.redact(eventID) {
|
||||
case .success:
|
||||
MXLog.info("Finished redacting message")
|
||||
case .failure(let error):
|
||||
@@ -175,14 +189,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func cancelSend(_ transactionID: String) async {
|
||||
func cancelSend(_ itemID: TimelineItemIdentifier) async {
|
||||
guard let transactionID = itemID.transactionID else {
|
||||
MXLog.error("Failed cancelling send, missing transaction ID")
|
||||
return
|
||||
}
|
||||
MXLog.info("Cancelling send in \(roomID)")
|
||||
await roomProxy.cancelSend(transactionID: transactionID)
|
||||
}
|
||||
|
||||
// Handle this parallel to the timeline items so we're not forced
|
||||
// to bundle the Rust side objects within them
|
||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
|
||||
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo {
|
||||
for timelineItemProxy in timelineProvider.itemProxies {
|
||||
switch timelineItemProxy {
|
||||
case .event(let item):
|
||||
@@ -339,7 +357,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
// Separators without stable identifiers cause UI glitches
|
||||
let identifier = "\(chunkIndex)-\(dateString)"
|
||||
return SeparatorRoomTimelineItem(id: identifier, text: dateString)
|
||||
return SeparatorRoomTimelineItem(id: .init(timelineID: identifier), text: dateString)
|
||||
case .readMarker:
|
||||
return ReadMarkerRoomTimelineItem()
|
||||
}
|
||||
@@ -373,12 +391,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
|
||||
private func fetchEventDetails(for timelineItem: EventBasedMessageTimelineItemProtocol, refetchOnError: Bool) {
|
||||
guard let eventID = timelineItem.id.eventID else {
|
||||
return
|
||||
}
|
||||
|
||||
switch timelineItem.replyDetails {
|
||||
case .notLoaded:
|
||||
roomProxy.fetchDetails(for: timelineItem.id)
|
||||
roomProxy.fetchDetails(for: eventID)
|
||||
case .error:
|
||||
if refetchOnError {
|
||||
roomProxy.fetchDetails(for: timelineItem.id)
|
||||
roomProxy.fetchDetails(for: eventID)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
||||
+9
-9
@@ -41,27 +41,27 @@ protocol RoomTimelineControllerProtocol {
|
||||
var timelineItems: [RoomTimelineItemProtocol] { get }
|
||||
var callbacks: PassthroughSubject<RoomTimelineControllerCallback, Never> { get }
|
||||
|
||||
func processItemAppearance(_ itemID: String) async
|
||||
func processItemAppearance(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
func processItemDisappearance(_ itemID: String) async
|
||||
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
func processItemTap(_ itemID: String) async -> RoomTimelineControllerAction
|
||||
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func markRoomAsRead() async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo itemID: String?) async
|
||||
func sendMessage(_ message: String, inReplyTo itemID: TimelineItemIdentifier?) async
|
||||
|
||||
func editMessage(_ newMessage: String, original itemID: String) async
|
||||
func editMessage(_ newMessage: String, original itemID: TimelineItemIdentifier) async
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: String) async
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async
|
||||
|
||||
func redact(_ itemID: String) async
|
||||
func redact(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
func cancelSend(_ transactionID: String) async
|
||||
func cancelSend(_ itemID: TimelineItemIdentifier) async
|
||||
|
||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo
|
||||
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo
|
||||
|
||||
func retryDecryption(for sessionID: String) async
|
||||
}
|
||||
|
||||
-2
@@ -26,6 +26,4 @@ struct RoomTimelineItemProperties: Hashable {
|
||||
var deliveryStatus: TimelineItemDeliveryStatus?
|
||||
/// The read receipts of the item, ordered from newest to oldest
|
||||
var orderedReadReceipts: [ReadReceipt] = []
|
||||
/// The original transaction id transmitted by the client
|
||||
var transactionID: String?
|
||||
}
|
||||
|
||||
@@ -17,6 +17,20 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct TimelineItemIdentifier: Hashable {
|
||||
/// Stable id across state changes of the timeline item, it uniquely identifies an item in a timeline.
|
||||
/// It's value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
|
||||
let timelineID: String
|
||||
|
||||
/// Uniquely identifies the timeline item from the server side.
|
||||
/// Only available for EventTimelineItem and only when the item is returned by the server.
|
||||
var eventID: String?
|
||||
|
||||
/// Uniquely identfies the local echo of the timeline item.
|
||||
/// Only available for sent EventTimelineItem that have not been returned by the server yet.
|
||||
var transactionID: String?
|
||||
}
|
||||
|
||||
/// A light wrapper around timeline items returned from Rust.
|
||||
enum TimelineItemProxy {
|
||||
case event(EventTimelineItemProxy)
|
||||
@@ -25,7 +39,7 @@ enum TimelineItemProxy {
|
||||
|
||||
init(item: MatrixRustSDK.TimelineItem) {
|
||||
if let eventItem = item.asEvent() {
|
||||
self = .event(EventTimelineItemProxy(item: eventItem))
|
||||
self = .event(EventTimelineItemProxy(item: eventItem, id: item.uniqueId()))
|
||||
} else if let virtualItem = item.asVirtual() {
|
||||
self = .virtual(virtualItem)
|
||||
} else {
|
||||
@@ -44,17 +58,11 @@ enum TimelineItemDeliveryStatus: Hashable {
|
||||
/// A light wrapper around event timeline items returned from Rust.
|
||||
struct EventTimelineItemProxy {
|
||||
let item: MatrixRustSDK.EventTimelineItem
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
init(item: MatrixRustSDK.EventTimelineItem) {
|
||||
init(item: MatrixRustSDK.EventTimelineItem, id: UInt64) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
var id: String {
|
||||
item.uniqueIdentifier()
|
||||
}
|
||||
|
||||
var transactionID: String? {
|
||||
item.transactionId()
|
||||
self.id = TimelineItemIdentifier(timelineID: String(id), eventID: item.eventId(), transactionID: item.transactionId())
|
||||
}
|
||||
|
||||
var deliveryStatus: TimelineItemDeliveryStatus? {
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Hashable {
|
||||
let id: String
|
||||
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
|
||||
+3
-11
@@ -16,10 +16,10 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let items: [RoomTimelineItemProtocol]
|
||||
let itemIDs: [String]
|
||||
let itemIDs: [TimelineItemIdentifier]
|
||||
|
||||
init(items: [RoomTimelineItemProtocol]) {
|
||||
self.items = items
|
||||
@@ -38,12 +38,4 @@ struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Identifiable, Hashable
|
||||
// Technically not a correct implementation of equality as the items themselves could be updated.
|
||||
lhs.id == rhs.id && lhs.itemIDs == rhs.itemIDs
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
// Technically not a correct implementation of hashing as the items themselves could be updated.
|
||||
hasher.combine(id)
|
||||
hasher.combine(itemIDs)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,14 +16,14 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
||||
struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
enum EncryptionType: Hashable {
|
||||
case megolmV1AesSha2(sessionId: String)
|
||||
case olmV1Curve25519AesSha2(senderKey: String)
|
||||
case unknown
|
||||
}
|
||||
|
||||
let id: String
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let encryptionType: EncryptionType
|
||||
let timestamp: String
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let timestamp: String
|
||||
let isOutgoing: Bool
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
|
||||
let eventType: String
|
||||
|
||||
+2
-2
@@ -16,6 +16,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct EncryptedHistoryRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,6 +16,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id = "paginationIndicatorTimelineItemIdentifier"
|
||||
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id = TimelineItemIdentifier(timelineID: "paginationIndicatorTimelineItemIdentifier")
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,6 +16,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id = "readMarkerTimelineItemIdentifier"
|
||||
struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id = TimelineItemIdentifier(timelineID: "readMarkerTimelineItemIdentifier")
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String
|
||||
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let text: String
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Identifiable, Hashable {
|
||||
let id: String = UUID().uuidString
|
||||
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id = TimelineItemIdentifier(timelineID: UUID().uuidString)
|
||||
let name: String?
|
||||
}
|
||||
|
||||
@@ -138,8 +138,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
blurhash: imageInfo.blurhash,
|
||||
properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -190,8 +189,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -208,8 +206,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -226,8 +223,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -244,8 +240,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -262,8 +257,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -280,8 +274,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -298,8 +291,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -316,13 +308,12 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
properties: RoomTimelineItemProperties(isEdited: messageTimelineItem.isEdited(),
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
transactionID: eventItemProxy.transactionID))
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts)))
|
||||
}
|
||||
|
||||
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
|
||||
reactions.map { reaction in
|
||||
AggregatedReaction(accountOwnerID: userID, key: reaction.key, senders: reaction.senders)
|
||||
AggregatedReaction(accountOwnerID: userID, key: reaction.key, senders: reaction.senders.map(\.senderId))
|
||||
}
|
||||
.sorted { a, b in
|
||||
// Sort by count and then by key for a consistence experience.
|
||||
|
||||
@@ -18,5 +18,5 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol RoomTimelineItemProtocol {
|
||||
var id: String { get }
|
||||
var id: TimelineItemIdentifier { get }
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ final class RoomTimelineItemViewModel: Identifiable, Equatable, ObservableObject
|
||||
@Published var type: RoomTimelineItemType
|
||||
@Published var groupStyle: TimelineGroupStyle
|
||||
|
||||
var id: String {
|
||||
var id: TimelineItemIdentifier {
|
||||
type.id
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ enum RoomTimelineItemType: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var id: String {
|
||||
var id: TimelineItemIdentifier {
|
||||
switch self {
|
||||
case .text(let item as RoomTimelineItemProtocol),
|
||||
.separator(let item as RoomTimelineItemProtocol),
|
||||
|
||||
Reference in New Issue
Block a user