mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Swift 6 Migration (#5733)
* Enable Swift 6.2 with default main actor isolation for the app target Turn on SWIFT_VERSION 6.2, SWIFT_APPROACHABLE_CONCURRENCY and SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor for the ElementX target, matching the NSE and Share Extension (which already use 6.2 without the main actor default). # Conflicts: # ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved * Update the code generation templates for Swift 6 Emit nonisolated/@unchecked Sendable mocks and nonisolated string enums, and mark async mock methods @concurrent so they keep suspending like the real implementations (otherwise intermediate states are unobservable in tests). Includes the regenerated mocks and strings. # Conflicts: # ElementX/Sources/Mocks/Generated/GeneratedMocks.swift * Make the data layer and services nonisolated and Sendable Annotate the value-type models, Rust SDK proxy wrappers, timeline item graph, settings, logging and NSE-shared types as nonisolated, and conform the service protocols to Sendable so their existentials can cross isolation boundaries. These types run off the main actor and no longer inherit the target's default main actor isolation. * Adopt the new isolation across call sites, mocks and signatures Update call sites, closure signatures and mock configuration helpers to match the nonisolated/Sendable data layer, including the main actor isolated mock inits and the @Sendable filter on recentlyVisitedRooms. ^ Conflicts: ^ ElementX/Sources/Services/SecureBackup/SecureBackupController.swift * Remove redundant @MainActor annotations With the app target defaulting to main actor isolation, the explicit @MainActor on view models, coordinators, views and their protocols is no longer required. * Fix concurrency hotspots surfaced by the migration Resolve the room summary main-thread deadlock by moving diff processing onto AsyncStreams, add the SDKListener.onMainActor FIFO bridge for SDK callbacks, hop KVO/timer/UIKit callbacks to the main actor via assumeIsolated, add isolated deinits, and apply the few justified nonisolated(unsafe) escapes for queue-confined Apple APIs. * Fix the unit tests for Swift 6 concurrency Hop the waitForConfirmation body onto the caller's actor (Swift Testing runs confirmation closures off-actor), and adjust the affected suites and async signatures. * offload diffs on concurrent threads instead of main actor * make room info protocols sendable * use a task group for fetching data so that everything runs concurrently in the TextRoomTimelineView when getting the preview. * use the generic onMainActor implementation, which already makes a stream and send the continuation on the main actor. * small pr comments issues being addressed * update enterprise * Update StaticCode to 1.1.0 and specify the required Swift isolation. * fix ElementCallServiceTests * update enterprise * re-generate mocks * update enterprise * enterprise new code conformed to swift 6 --------- Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
@@ -10,7 +10,7 @@ import CoreLocation
|
||||
import Foundation
|
||||
|
||||
/// A structure that parses a geo URI (i.e. geo:53.99803101552848,-8.25347900390625;u=10) and constructs their constituent parts.
|
||||
struct GeoURI: Hashable {
|
||||
nonisolated struct GeoURI: Hashable {
|
||||
// MARK: - Properties
|
||||
|
||||
let latitude: Double
|
||||
@@ -69,8 +69,11 @@ struct GeoURI: Hashable {
|
||||
// swiftlint:disable:next large_tuple
|
||||
private typealias RegexGeoURI = Regex<(Substring, latitude: Substring, longitude: Substring, uncertainty: Substring?)>
|
||||
|
||||
private extension RegexGeoURI {
|
||||
static let standard: Self = /geo:(?<latitude>-?\d+(?:\.\d+)?),(?<longitude>-?\d+(?:\.\d+)?)(?:,-?\d+(?:\.\d+)?)?(?:;u=(?<uncertainty>\d+(?:\.\d+)?))?/
|
||||
private nonisolated extension RegexGeoURI {
|
||||
/// A computed property as `Regex` isn't Sendable.
|
||||
static var standard: Self {
|
||||
/geo:(?<latitude>-?\d+(?:\.\d+)?),(?<longitude>-?\d+(?:\.\d+)?)(?:,-?\d+(?:\.\d+)?)?(?:;u=(?<uncertainty>\d+(?:\.\d+)?))?/
|
||||
}
|
||||
}
|
||||
|
||||
extension GeoURI {
|
||||
@@ -79,7 +82,7 @@ extension GeoURI {
|
||||
}
|
||||
}
|
||||
|
||||
private extension NumberFormatter {
|
||||
private nonisolated extension NumberFormatter {
|
||||
static let decimal: NumberFormatter = {
|
||||
let numberFormatter = NumberFormatter()
|
||||
numberFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
|
||||
@@ -574,10 +574,14 @@ class TimelineController: TimelineControllerProtocol {
|
||||
|
||||
switch timelineItem.properties.replyDetails {
|
||||
case .notLoaded:
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
Task { @MainActor in
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
}
|
||||
case .error:
|
||||
if refetchOnError {
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
Task { @MainActor in
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -653,7 +657,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineItemProxy {
|
||||
private nonisolated extension TimelineItemProxy {
|
||||
var isItemCollapsible: Bool {
|
||||
if case let .event(eventItem) = self {
|
||||
switch eventItem.content {
|
||||
|
||||
-1
@@ -13,7 +13,6 @@ enum TimelineFactoryControllerError: Error {
|
||||
case roomProxyError(RoomProxyError)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol TimelineControllerFactoryProtocol {
|
||||
func buildTimelineController(roomProxy: JoinedRoomProxyProtocol,
|
||||
initialFocussedEventID: String?,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
/// Represents all reactions of the same type for a single event.
|
||||
struct AggregatedReaction: Hashable, Identifiable {
|
||||
nonisolated struct AggregatedReaction: Hashable, Identifiable {
|
||||
/// Length at which we ellipsize a reaction key for display
|
||||
/// Reactions can be free text, so we need to limit the length
|
||||
/// displayed on screen.
|
||||
@@ -28,14 +28,14 @@ struct AggregatedReaction: Hashable, Identifiable {
|
||||
}
|
||||
|
||||
/// Details of who sent the reaction
|
||||
struct ReactionSender: Hashable, Identifiable {
|
||||
nonisolated struct ReactionSender: Hashable, Identifiable {
|
||||
/// The id of the user who sent the reaction
|
||||
let id: String
|
||||
/// The time that the reaction was received on the original homeserver
|
||||
let timestamp: Date
|
||||
}
|
||||
|
||||
extension AggregatedReaction {
|
||||
nonisolated extension AggregatedReaction {
|
||||
/// The number of times this reactions was sent.
|
||||
var count: Int {
|
||||
senders.count
|
||||
|
||||
@@ -13,7 +13,7 @@ import SwiftUI
|
||||
/// Represents an issue with a timeline item's authenticity such as coming from an
|
||||
/// unsigned session or being sent unencrypted in an encrypted room. See Rust's
|
||||
/// `ShieldStateCode` for more information about the meaning of the cases.
|
||||
enum EncryptionAuthenticity: Hashable {
|
||||
nonisolated enum EncryptionAuthenticity: Hashable {
|
||||
enum Color { case red, gray }
|
||||
|
||||
case notGuaranteed(color: Color)
|
||||
@@ -65,7 +65,7 @@ enum EncryptionAuthenticity: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
extension EncryptionAuthenticity {
|
||||
nonisolated extension EncryptionAuthenticity {
|
||||
init?(shieldState: ShieldState) {
|
||||
switch shieldState {
|
||||
case .red(let code):
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ReadReceipt: Hashable {
|
||||
nonisolated struct ReadReceipt: Hashable {
|
||||
let userID: String
|
||||
let formattedTimestamp: String?
|
||||
}
|
||||
|
||||
extension ReadReceipt: Identifiable {
|
||||
nonisolated extension ReadReceipt: Identifiable {
|
||||
var id: String {
|
||||
userID
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
/// Properties of a matrix event that are common between all timeline items.
|
||||
struct RoomTimelineItemProperties: Hashable {
|
||||
nonisolated struct RoomTimelineItemProperties: Hashable {
|
||||
/// Information on the item this one replying to
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
/// Whether it's part of a thread or not
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum TimelineEventContent: Hashable {
|
||||
nonisolated enum TimelineEventContent: Hashable {
|
||||
case message(EventBasedMessageTimelineItemContentType)
|
||||
case poll(question: String)
|
||||
case liveLocation
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
struct TimelineItemKeyForwarder: Identifiable, Hashable {
|
||||
nonisolated struct TimelineItemKeyForwarder: Identifiable, Hashable {
|
||||
let id: String
|
||||
let displayName: String?
|
||||
|
||||
|
||||
+2
-2
@@ -8,12 +8,12 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TimelineItemReply {
|
||||
nonisolated struct TimelineItemReply {
|
||||
let details: TimelineItemReplyDetails
|
||||
let isThreaded: Bool
|
||||
}
|
||||
|
||||
enum TimelineItemReplyDetails: Hashable {
|
||||
nonisolated enum TimelineItemReplyDetails: Hashable {
|
||||
case notLoaded(eventID: String)
|
||||
case loading(eventID: String)
|
||||
case loaded(sender: TimelineItemSender, eventID: String, eventContent: TimelineEventContent)
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
enum TimelineItemThreadSummary: Hashable {
|
||||
nonisolated enum TimelineItemThreadSummary: Hashable {
|
||||
case notLoaded
|
||||
case loading
|
||||
case loaded(senderID: String, sender: TimelineItemSender, latestEventContent: TimelineEventContent, numberOfReplies: Int)
|
||||
|
||||
@@ -14,7 +14,7 @@ import Foundation
|
||||
/// Its value is consistent only per timeline instance, it should **not** be used to identify an item across timeline instances.
|
||||
/// - eventOrTransactionID: Contains the 2 possible identifiers of an event, either it has a remote event id or
|
||||
/// a local transaction id, never both or none.
|
||||
enum TimelineItemIdentifier: Hashable {
|
||||
nonisolated enum TimelineItemIdentifier: Hashable {
|
||||
struct UniqueID: Hashable {
|
||||
let value: String
|
||||
|
||||
@@ -77,7 +77,7 @@ enum TimelineItemIdentifier: Hashable {
|
||||
|
||||
// MARK: - Mocks
|
||||
|
||||
extension TimelineItemIdentifier {
|
||||
nonisolated extension TimelineItemIdentifier {
|
||||
static var randomEvent: Self {
|
||||
.event(uniqueID: .init(UUID().uuidString), eventOrTransactionID: .eventID(UUID().uuidString))
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
// The stream also allows to handle the sendable items in the listener.
|
||||
Task { [weak self] in
|
||||
for await diffs in diffStream {
|
||||
self?.updateItemsWithDiffs(diffs)
|
||||
await self?.applyDiffs(diffs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,17 +81,34 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateItemsWithDiffs(_ diffs: [TimelineDiff]) {
|
||||
let span = MXLog.createSpan("process_timeline_list_diffs:\(kind)")
|
||||
private func applyDiffs(_ diffs: [TimelineDiff]) async {
|
||||
MXLog.verbose("Received diffs: \(diffs)")
|
||||
|
||||
// Building the item proxies and computing/applying the CollectionDifference can be
|
||||
// expensive, so run it off the main actor and only hop back to publish the result.
|
||||
let result = await Self.processDiffs(diffs, on: itemProxies, spanName: "process_timeline_list_diffs:\(kind)")
|
||||
|
||||
itemProxies = result.itemProxies
|
||||
|
||||
if result.hasMembershipChange {
|
||||
membershipChangeSubject.send(())
|
||||
}
|
||||
}
|
||||
|
||||
@concurrent
|
||||
private static func processDiffs(_ diffs: [TimelineDiff],
|
||||
on currentItems: [TimelineItemProxy],
|
||||
spanName: String) async -> (itemProxies: [TimelineItemProxy], hasMembershipChange: Bool) {
|
||||
let span = MXLog.createSpan(spanName)
|
||||
span.enter()
|
||||
defer {
|
||||
span.exit()
|
||||
}
|
||||
|
||||
MXLog.verbose("Received diffs: \(diffs)")
|
||||
var hasMembershipChange = false
|
||||
|
||||
itemProxies = diffs.reduce(itemProxies) { currentItems, diff in
|
||||
guard let collectionDiff = buildDiff(from: diff, on: currentItems) else {
|
||||
let itemProxies = diffs.reduce(currentItems) { currentItems, diff in
|
||||
guard let collectionDiff = buildDiff(from: diff, on: currentItems, hasMembershipChange: &hasMembershipChange) else {
|
||||
MXLog.error("Failed building CollectionDifference from \(diff)")
|
||||
return currentItems
|
||||
}
|
||||
@@ -103,9 +120,11 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
|
||||
return updatedItems
|
||||
}
|
||||
|
||||
return (itemProxies, hasMembershipChange)
|
||||
}
|
||||
|
||||
private func buildDiff(from diff: TimelineDiff, on itemProxies: [TimelineItemProxy]) -> CollectionDifference<TimelineItemProxy>? {
|
||||
private nonisolated static func buildDiff(from diff: TimelineDiff, on itemProxies: [TimelineItemProxy], hasMembershipChange: inout Bool) -> CollectionDifference<TimelineItemProxy>? {
|
||||
var changes = [CollectionDifference<TimelineItemProxy>.Change]()
|
||||
|
||||
switch diff {
|
||||
@@ -114,7 +133,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
let itemProxy = TimelineItemProxy(item: item)
|
||||
|
||||
if itemProxy.isMembershipChange {
|
||||
membershipChangeSubject.send(())
|
||||
hasMembershipChange = true
|
||||
}
|
||||
|
||||
changes.append(.insert(offset: Int(itemProxies.count) + index, element: itemProxy, associatedWith: nil))
|
||||
@@ -138,7 +157,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
let itemProxy = TimelineItemProxy(item: item)
|
||||
|
||||
if itemProxy.isMembershipChange {
|
||||
membershipChangeSubject.send(())
|
||||
hasMembershipChange = true
|
||||
}
|
||||
|
||||
changes.append(.insert(offset: Int(itemProxies.count), element: itemProxy, associatedWith: nil))
|
||||
@@ -170,7 +189,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineItemProxy {
|
||||
private nonisolated extension TimelineItemProxy {
|
||||
var isMembershipChange: Bool {
|
||||
switch self {
|
||||
case .event(let eventTimelineItemProxy):
|
||||
|
||||
@@ -10,10 +10,10 @@ import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct TimelinePaginationState: Equatable {
|
||||
nonisolated struct TimelinePaginationState: Equatable {
|
||||
/// An initial state that is used to prevent pagination whilst loading the timeline.
|
||||
/// Once the initial items are loaded the TimelineProxy will publish the correct value.
|
||||
static var initial = TimelinePaginationState(backward: .endReached, forward: .endReached)
|
||||
static let initial = TimelinePaginationState(backward: .endReached, forward: .endReached)
|
||||
let backward: PaginationState
|
||||
let forward: PaginationState
|
||||
}
|
||||
@@ -21,7 +21,6 @@ struct TimelinePaginationState: Equatable {
|
||||
/// Entities implementing this protocol are responsible for processings diffs coming from the rust timeline
|
||||
/// and converting them into an array of Element X specific ``TimelineItemProxy``s that will be
|
||||
/// published as an array together with the pagination state through the ``updatePublisher``.
|
||||
@MainActor
|
||||
protocol TimelineItemProviderProtocol {
|
||||
/// A publisher that signals when ``itemProxies`` or ``paginationState`` are changed.
|
||||
var updatePublisher: AnyPublisher<([TimelineItemProxy], TimelinePaginationState), Never> { get }
|
||||
|
||||
@@ -11,7 +11,7 @@ import MatrixRustSDK
|
||||
import MatrixRustSDKMocks
|
||||
|
||||
/// A light wrapper around timeline items returned from Rust.
|
||||
enum TimelineItemProxy {
|
||||
nonisolated enum TimelineItemProxy {
|
||||
case event(EventTimelineItemProxy)
|
||||
case virtual(MatrixRustSDK.VirtualTimelineItem, uniqueID: TimelineItemIdentifier.UniqueID)
|
||||
case unknown(MatrixRustSDK.TimelineItem)
|
||||
@@ -37,7 +37,7 @@ enum TimelineItemProxy {
|
||||
}
|
||||
|
||||
/// The delivery status for the item.
|
||||
enum TimelineItemDeliveryStatus: Hashable {
|
||||
nonisolated enum TimelineItemDeliveryStatus: Hashable {
|
||||
case sending
|
||||
case sent
|
||||
case sendingFailed(TimelineItemSendFailure)
|
||||
@@ -51,7 +51,7 @@ enum TimelineItemDeliveryStatus: Hashable {
|
||||
}
|
||||
|
||||
/// The reason a timeline item failed to send.
|
||||
enum TimelineItemSendFailure: Hashable {
|
||||
nonisolated enum TimelineItemSendFailure: Hashable {
|
||||
enum VerifiedUser: Hashable {
|
||||
case hasUnsignedDevice(devices: [String: [String]])
|
||||
case changedIdentity(users: [String])
|
||||
@@ -69,7 +69,11 @@ enum TimelineItemSendFailure: Hashable {
|
||||
}
|
||||
|
||||
/// A light wrapper around event timeline items returned from Rust.
|
||||
class EventTimelineItemProxy {
|
||||
///
|
||||
/// The proxy is shared between the main actor and the background timeline item
|
||||
/// building, so it's stateless and forwards everything to the underlying
|
||||
/// (internally synchronised) Rust object.
|
||||
final nonisolated class EventTimelineItemProxy: Sendable {
|
||||
let item: MatrixRustSDK.EventTimelineItem
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
@@ -79,13 +83,13 @@ class EventTimelineItemProxy {
|
||||
id = .event(uniqueID: uniqueID, eventOrTransactionID: .init(rustValue: item.eventOrTransactionId))
|
||||
}
|
||||
|
||||
lazy var deliveryStatus: TimelineItemDeliveryStatus? = {
|
||||
var deliveryStatus: TimelineItemDeliveryStatus? {
|
||||
guard let localSendState = item.localSendState else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch localSendState {
|
||||
case .sendingFailed(let error, let isRecoverable):
|
||||
case .sendingFailed(let error, _):
|
||||
switch error {
|
||||
case .identityViolations(let users):
|
||||
return .sendingFailed(.verifiedUser(.changedIdentity(users: users)))
|
||||
@@ -99,42 +103,62 @@ class EventTimelineItemProxy {
|
||||
case .sent:
|
||||
return .sent
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
lazy var canBeRepliedTo = item.canBeRepliedTo
|
||||
var canBeRepliedTo: Bool {
|
||||
item.canBeRepliedTo
|
||||
}
|
||||
|
||||
lazy var content = item.content
|
||||
var content: TimelineItemContent {
|
||||
item.content
|
||||
}
|
||||
|
||||
lazy var isOwn = item.isOwn
|
||||
var isOwn: Bool {
|
||||
item.isOwn
|
||||
}
|
||||
|
||||
lazy var isEditable = item.isEditable
|
||||
var isEditable: Bool {
|
||||
item.isEditable
|
||||
}
|
||||
|
||||
lazy var sender = TimelineItemSender(senderID: item.sender, senderProfile: item.senderProfile)
|
||||
var sender: TimelineItemSender {
|
||||
TimelineItemSender(senderID: item.sender, senderProfile: item.senderProfile)
|
||||
}
|
||||
|
||||
lazy var forwarder: TimelineItemKeyForwarder? = {
|
||||
var forwarder: TimelineItemKeyForwarder? {
|
||||
guard let forwarderID = item.forwarder, let forwarderProfile = item.forwarderProfile else {
|
||||
return nil
|
||||
}
|
||||
return TimelineItemKeyForwarder(forwarderID: forwarderID, forwarderProfile: forwarderProfile)
|
||||
}()
|
||||
}
|
||||
|
||||
lazy var timestamp = Date(timeIntervalSince1970: TimeInterval(item.timestamp / 1000))
|
||||
var timestamp: Date {
|
||||
Date(timeIntervalSince1970: TimeInterval(item.timestamp / 1000))
|
||||
}
|
||||
|
||||
lazy var debugInfo: TimelineItemDebugInfo = {
|
||||
var debugInfo: TimelineItemDebugInfo {
|
||||
let debugInfo = item.lazyProvider.debugInfo()
|
||||
return TimelineItemDebugInfo(model: debugInfo.model, originalJSON: debugInfo.originalJson, latestEditJSON: debugInfo.latestEditJson)
|
||||
}()
|
||||
}
|
||||
|
||||
lazy var shieldState = item.lazyProvider.getShields(strict: false)
|
||||
var shieldState: ShieldState? {
|
||||
item.lazyProvider.getShields(strict: false)
|
||||
}
|
||||
|
||||
lazy var sendHandle = item.lazyProvider.getSendHandle()
|
||||
var sendHandle: SendHandle? {
|
||||
item.lazyProvider.getSendHandle()
|
||||
}
|
||||
|
||||
lazy var shouldBoost = item.lazyProvider.containsOnlyEmojis()
|
||||
var shouldBoost: Bool {
|
||||
item.lazyProvider.containsOnlyEmojis()
|
||||
}
|
||||
|
||||
lazy var readReceipts = item.readReceipts
|
||||
var readReceipts: [String: Receipt] {
|
||||
item.readReceipts
|
||||
}
|
||||
}
|
||||
|
||||
struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible {
|
||||
nonisolated struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible {
|
||||
let id = UUID()
|
||||
let model: String
|
||||
let originalJSON: String?
|
||||
@@ -175,7 +199,7 @@ struct TimelineItemDebugInfo: Identifiable, CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
struct SendHandleProxy: Hashable {
|
||||
nonisolated struct SendHandleProxy: Hashable {
|
||||
enum Error: Swift.Error {
|
||||
case sdkError(Swift.Error)
|
||||
}
|
||||
@@ -209,7 +233,7 @@ struct SendHandleProxy: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
struct VideoInfoProxy: Hashable {
|
||||
nonisolated struct VideoInfoProxy: Hashable {
|
||||
let source: MediaSourceProxy
|
||||
private(set) var duration: TimeInterval
|
||||
private(set) var size: CGSize?
|
||||
@@ -253,7 +277,7 @@ struct VideoInfoProxy: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageInfoProxy: Hashable {
|
||||
nonisolated struct ImageInfoProxy: Hashable {
|
||||
let source: MediaSourceProxy
|
||||
private(set) var size: CGSize?
|
||||
private(set) var aspectRatio: CGFloat?
|
||||
@@ -317,7 +341,7 @@ struct ImageInfoProxy: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
private struct MediaInfoProxy: Hashable {
|
||||
private nonisolated struct MediaInfoProxy: Hashable {
|
||||
private(set) var size: CGSize?
|
||||
private(set) var mimeType: String?
|
||||
private(set) var aspectRatio: CGFloat?
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import MatrixRustSDK
|
||||
import SwiftUI
|
||||
|
||||
struct TimelineItemSender: Identifiable, Hashable {
|
||||
nonisolated struct TimelineItemSender: Identifiable, Hashable {
|
||||
static let test = TimelineItemSender(id: "@test.matrix.org")
|
||||
|
||||
let id: String
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
import Foundation
|
||||
|
||||
// periphery: ignore - markdown protocol
|
||||
protocol DecorationTimelineItemProtocol: RoomTimelineItemProtocol { }
|
||||
nonisolated protocol DecorationTimelineItemProtocol: RoomTimelineItemProtocol { }
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum EventBasedMessageTimelineItemContentType: Hashable {
|
||||
nonisolated enum EventBasedMessageTimelineItemContentType: Hashable {
|
||||
case audio(AudioRoomTimelineItemContent)
|
||||
case emote(EmoteRoomTimelineItemContent)
|
||||
case file(FileRoomTimelineItemContent)
|
||||
@@ -20,11 +20,11 @@ enum EventBasedMessageTimelineItemContentType: Hashable {
|
||||
case voice(AudioRoomTimelineItemContent)
|
||||
}
|
||||
|
||||
protocol EventBasedMessageTimelineItemProtocol: EventBasedTimelineItemProtocol {
|
||||
nonisolated protocol EventBasedMessageTimelineItemProtocol: EventBasedTimelineItemProtocol {
|
||||
var contentType: EventBasedMessageTimelineItemContentType { get }
|
||||
}
|
||||
|
||||
extension EventBasedMessageTimelineItemProtocol {
|
||||
nonisolated extension EventBasedMessageTimelineItemProtocol {
|
||||
var supportsMediaCaption: Bool {
|
||||
switch contentType {
|
||||
case .audio, .file, .image, .video:
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringConvertible {
|
||||
nonisolated protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringConvertible {
|
||||
var timestamp: Date { get }
|
||||
var isOutgoing: Bool { get }
|
||||
var isEditable: Bool { get }
|
||||
@@ -22,7 +22,7 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol, CustomStringC
|
||||
var properties: RoomTimelineItemProperties { get }
|
||||
}
|
||||
|
||||
extension EventBasedTimelineItemProtocol {
|
||||
nonisolated extension EventBasedTimelineItemProtocol {
|
||||
var description: String {
|
||||
"\(String(describing: Self.self)): id: \(id), timestamp: \(timestamp), isOutgoing: \(isOutgoing), properties: \(properties)"
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
nonisolated struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct AudioRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct AudioRoomTimelineItemContent: Hashable {
|
||||
let filename: String
|
||||
var caption: String?
|
||||
var formattedCaption: AttributedString?
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
nonisolated struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct EmoteRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct EmoteRoomTimelineItemContent: Hashable {
|
||||
let body: String
|
||||
var formattedBody: AttributedString?
|
||||
/// The original textual representation of the formatted body directly from the event (usually HTML code)
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
nonisolated struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct FileRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct FileRoomTimelineItemContent: Hashable {
|
||||
let filename: String
|
||||
var caption: String?
|
||||
var formattedCaption: AttributedString?
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
nonisolated struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct ImageRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct ImageRoomTimelineItemContent: Hashable {
|
||||
let filename: String
|
||||
var caption: String?
|
||||
var formattedCaption: AttributedString?
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
nonisolated struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
/// Always empty just here for protocol conformance
|
||||
var body: String {
|
||||
@@ -25,14 +25,14 @@ struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
var properties = RoomTimelineItemProperties()
|
||||
}
|
||||
|
||||
struct LiveLocationRoomTimelineItemContent: Equatable {
|
||||
nonisolated struct LiveLocationRoomTimelineItemContent: Equatable {
|
||||
let isLive: Bool
|
||||
let timeoutDate: Date
|
||||
|
||||
let lastGeoURI: GeoURI?
|
||||
}
|
||||
|
||||
extension LiveLocationRoomTimelineItemContent {
|
||||
nonisolated extension LiveLocationRoomTimelineItemContent {
|
||||
init(from content: MatrixRustSDK.LiveLocationContent, timestamp: Date) {
|
||||
isLive = content.isLive
|
||||
timeoutDate = timestamp.addingTimeInterval(Double(content.timeoutMs) / 1000)
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
nonisolated struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
struct LocationRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct LocationRoomTimelineItemContent: Hashable {
|
||||
let body: String
|
||||
let geoURI: GeoURI?
|
||||
let kind: StaticLocationKind
|
||||
@@ -22,7 +22,7 @@ struct LocationRoomTimelineItemContent: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
enum StaticLocationKind {
|
||||
nonisolated enum StaticLocationKind {
|
||||
case sender
|
||||
case pin
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
nonisolated struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct NoticeRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct NoticeRoomTimelineItemContent: Hashable {
|
||||
let body: String
|
||||
var formattedBody: AttributedString?
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Algorithms
|
||||
import UIKit
|
||||
|
||||
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
nonisolated struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct TextRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct TextRoomTimelineItemContent: Hashable {
|
||||
let body: String
|
||||
var formattedBody: AttributedString?
|
||||
/// The original textual representation of the formatted body directly from the event (usually HTML code)
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
nonisolated struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct VideoRoomTimelineItemContent: Hashable {
|
||||
nonisolated struct VideoRoomTimelineItemContent: Hashable {
|
||||
let filename: String
|
||||
var caption: String?
|
||||
var formattedCaption: AttributedString?
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
nonisolated struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CallInviteRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
nonisolated struct CallInviteRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isEditable: Bool
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CallNotificationRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
nonisolated struct CallNotificationRoomTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
let isEditable: Bool
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
nonisolated struct CollapsibleTimelineItem: RoomTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let items: [RoomTimelineItemProtocol]
|
||||
let itemIDs: [TimelineItemIdentifier]
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
nonisolated struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
enum EncryptionType: Hashable {
|
||||
case megolmV1AesSha2(sessionID: String, cause: UTDCause)
|
||||
case olmV1Curve25519AesSha2(senderKey: String)
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PollRoomTimelineItem: Equatable, EventBasedTimelineItemProtocol {
|
||||
nonisolated struct PollRoomTimelineItem: Equatable, EventBasedTimelineItemProtocol {
|
||||
let id: TimelineItemIdentifier
|
||||
let poll: Poll
|
||||
let body: String
|
||||
@@ -20,7 +20,7 @@ struct PollRoomTimelineItem: Equatable, EventBasedTimelineItemProtocol {
|
||||
var properties: RoomTimelineItemProperties
|
||||
}
|
||||
|
||||
struct Poll: Hashable {
|
||||
nonisolated struct Poll: Hashable {
|
||||
let question: String
|
||||
let kind: Kind
|
||||
let maxSelections: Int
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
nonisolated struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let timestamp: Date
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
nonisolated struct StateRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let timestamp: Date
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
nonisolated struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
let timestamp: Date
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
nonisolated struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let body: String
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
nonisolated struct PaginationIndicatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
enum Position {
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
nonisolated struct ReadMarkerRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
nonisolated struct SeparatorRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
let timestamp: Date
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
nonisolated struct TimelineStartRoomTimelineItem: DecorationTimelineItemProtocol, Equatable {
|
||||
// Using a static identifier makes the animations consistent in SwiftUI
|
||||
let id: TimelineItemIdentifier = .virtual(uniqueID: .init("TimelineStart"))
|
||||
let name: String?
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import MatrixRustSDK
|
||||
import UIKit
|
||||
|
||||
struct RoomStateEventStringBuilder {
|
||||
nonisolated struct RoomStateEventStringBuilder {
|
||||
let userID: String
|
||||
|
||||
func buildString(for change: MembershipChange?,
|
||||
|
||||
@@ -10,7 +10,7 @@ import MatrixRustSDK
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
nonisolated struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
private let attributedStringBuilder: AttributedStringBuilderProtocol
|
||||
private let stateEventStringBuilder: RoomStateEventStringBuilder
|
||||
|
||||
@@ -927,7 +927,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private extension EmbeddedEventDetails {
|
||||
private nonisolated extension EmbeddedEventDetails {
|
||||
var isThreaded: Bool {
|
||||
switch self {
|
||||
case .ready(.msgLike(let messageLikeContent), _, _, _, _):
|
||||
@@ -938,7 +938,7 @@ private extension EmbeddedEventDetails {
|
||||
}
|
||||
}
|
||||
|
||||
private extension Receipt {
|
||||
private nonisolated extension Receipt {
|
||||
var dateTimestamp: Date? {
|
||||
guard let timestamp else {
|
||||
return nil
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
protocol RoomTimelineItemFactoryProtocol {
|
||||
nonisolated protocol RoomTimelineItemFactoryProtocol: Sendable {
|
||||
func buildTimelineItem(for eventItemProxy: EventTimelineItemProxy, isDM: Bool) -> RoomTimelineItemProtocol?
|
||||
func buildTimelineItemReply(_ details: MatrixRustSDK.InReplyToDetails) -> TimelineItemReply
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
protocol RoomTimelineItemProtocol {
|
||||
nonisolated protocol RoomTimelineItemProtocol: Sendable {
|
||||
var id: TimelineItemIdentifier { get }
|
||||
}
|
||||
|
||||
extension RoomTimelineItemProtocol {
|
||||
nonisolated extension RoomTimelineItemProtocol {
|
||||
var isReactable: Bool {
|
||||
guard let eventItem = self as? EventBasedTimelineItemProtocol else { return false }
|
||||
return !eventItem.isRedacted && !eventItem.hasFailedToSend && !eventItem.hasFailedDecryption
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol TextBasedRoomTimelineItem: EventBasedMessageTimelineItemProtocol { }
|
||||
nonisolated protocol TextBasedRoomTimelineItem: EventBasedMessageTimelineItemProtocol { }
|
||||
|
||||
@@ -31,12 +31,32 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
|
||||
deinit {
|
||||
backPaginationStatusContinuation.finish()
|
||||
backPaginationStateObservationToken?.cancel()
|
||||
}
|
||||
|
||||
/// Bridge from the SDK's synchronous callback into Swift Concurrency. Yielding is safe from any
|
||||
/// thread; a single long-lived `for await` consumer (set up in `init`) applies the status updates
|
||||
/// on the main actor in FIFO order.
|
||||
private let backPaginationStatusContinuation: AsyncStream<PaginationStatus>.Continuation
|
||||
|
||||
init(timeline: Timeline, kind: TimelineKind) {
|
||||
self.timeline = timeline
|
||||
self.kind = kind
|
||||
|
||||
let (backPaginationStatusStream, backPaginationStatusContinuation) = AsyncStream<PaginationStatus>.makeStream()
|
||||
self.backPaginationStatusContinuation = backPaginationStatusContinuation
|
||||
|
||||
Task { [weak self] in
|
||||
for await status in backPaginationStatusStream {
|
||||
switch status {
|
||||
case .idle(let hitStartOfTimeline):
|
||||
self?.backPaginationStateSubject.send(hitStartOfTimeline ? .endReached : .idle)
|
||||
case .paginating:
|
||||
self?.backPaginationStateSubject.send(.paginating)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func subscribeForUpdates() async {
|
||||
@@ -52,7 +72,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
await subscribeToPagination()
|
||||
|
||||
let provider = await TimelineItemProvider(timeline: timeline, kind: kind, paginationStatePublisher: paginationStatePublisher)
|
||||
let provider = TimelineItemProvider(timeline: timeline, kind: kind, paginationStatePublisher: paginationStatePublisher)
|
||||
|
||||
innerTimelineItemProvider = provider
|
||||
|
||||
@@ -74,7 +94,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
|
||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||
guard let content = await timelineItemProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
|
||||
guard let content = timelineItemProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
|
||||
case let .msgLike(messageLikeContent) = content,
|
||||
case let .message(messageContent) = messageLikeContent.kind else {
|
||||
return nil
|
||||
@@ -236,7 +256,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
inReplyTo: nil),
|
||||
audioInfo: audioInfo)
|
||||
|
||||
await requestHandle(handle)
|
||||
requestHandle(handle)
|
||||
|
||||
try await handle.join()
|
||||
MXLog.info("Finished sending audio")
|
||||
@@ -262,7 +282,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
inReplyTo: nil),
|
||||
fileInfo: fileInfo)
|
||||
|
||||
await requestHandle(handle)
|
||||
requestHandle(handle)
|
||||
|
||||
try await handle.join()
|
||||
MXLog.info("Finished sending file")
|
||||
@@ -290,7 +310,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
thumbnailSource: .file(filename: thumbnailURL.path(percentEncoded: false)),
|
||||
imageInfo: imageInfo)
|
||||
|
||||
await requestHandle(handle)
|
||||
requestHandle(handle)
|
||||
|
||||
try await handle.join()
|
||||
MXLog.info("Finished sending image")
|
||||
@@ -342,7 +362,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
thumbnailSource: .file(filename: thumbnailURL.path(percentEncoded: false)),
|
||||
videoInfo: videoInfo)
|
||||
|
||||
await requestHandle(handle)
|
||||
requestHandle(handle)
|
||||
|
||||
try await handle.join()
|
||||
MXLog.info("Finished sending video")
|
||||
@@ -369,7 +389,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
audioInfo: audioInfo,
|
||||
waveform: waveform)
|
||||
|
||||
await requestHandle(handle)
|
||||
requestHandle(handle)
|
||||
|
||||
try await handle.join()
|
||||
MXLog.info("Finished sending voice message")
|
||||
@@ -582,17 +602,8 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
private func subscribeToPagination() async {
|
||||
switch kind {
|
||||
case .live:
|
||||
let backPaginationListener = SDKListener<PaginationStatus> { [weak self] status in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
case .idle(let hitStartOfTimeline):
|
||||
backPaginationStateSubject.send(hitStartOfTimeline ? .endReached : .idle)
|
||||
case .paginating:
|
||||
backPaginationStateSubject.send(.paginating)
|
||||
}
|
||||
let backPaginationListener = SDKListener<PaginationStatus> { [backPaginationStatusContinuation] status in
|
||||
backPaginationStatusContinuation.yield(status)
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@@ -53,7 +53,7 @@ enum TimelineProxyError: Error {
|
||||
/// Element X proxies generally wrap the counterpart RustSDK objects while providing platform specific
|
||||
/// interfaces. In this case it composes methods for interacting with a room's timeline and should be used alongside
|
||||
/// the ``TimelineItemProviderProtocol`` which offers a reactive interface to timeline changes.
|
||||
protocol TimelineProxyProtocol {
|
||||
protocol TimelineProxyProtocol: Sendable {
|
||||
var timelineItemProvider: TimelineItemProviderProtocol { get }
|
||||
|
||||
func subscribeForUpdates() async
|
||||
|
||||
Reference in New Issue
Block a user