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:
Mauro
2026-06-18 17:55:31 +02:00
committed by GitHub
parent 7edca8b5a1
commit a520d94760
444 changed files with 3677 additions and 3361 deletions
+6
View File
@@ -9653,8 +9653,11 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(APP_NAME)"; PRODUCT_NAME = "$(APP_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_OBJC_BRIDGING_HEADER = "ElementX/SupportingFiles/ElementX-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "ElementX/SupportingFiles/ElementX-Bridging-Header.h";
SWIFT_OBJC_INTERFACE_HEADER_NAME = "GeneratedInterface-Swift.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "GeneratedInterface-Swift.h";
SWIFT_VERSION = 6.2;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
@@ -9685,8 +9688,11 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = "$(APP_NAME)"; PRODUCT_NAME = "$(APP_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_OBJC_BRIDGING_HEADER = "ElementX/SupportingFiles/ElementX-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "ElementX/SupportingFiles/ElementX-Bridging-Header.h";
SWIFT_OBJC_INTERFACE_HEADER_NAME = "GeneratedInterface-Swift.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "GeneratedInterface-Swift.h";
SWIFT_VERSION = 6.2;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
@@ -4,8 +4,7 @@
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES" buildImplicitDependencies = "YES">
runPostActionsOnFailure = "NO">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
@@ -30,12 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES" codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES"> onlyGenerateCoverageForSpecifiedTargets = "YES">
<TestPlans>
<TestPlanReference
default = "YES"
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan">
</TestPlanReference>
</TestPlans>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@@ -45,10 +38,6 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
<CodeCoverageTargets> <CodeCoverageTargets>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@@ -58,6 +47,12 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</CodeCoverageTargets> </CodeCoverageTargets>
<TestPlans>
<TestPlanReference
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@@ -79,8 +74,6 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
<EnvironmentVariables> <EnvironmentVariables>
<EnvironmentVariable <EnvironmentVariable
key = "HTTPS_PROXY" key = "HTTPS_PROXY"
@@ -115,8 +108,6 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">
@@ -4,8 +4,7 @@
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES" buildImplicitDependencies = "YES">
runPostActionsOnFailure = "NO">
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"
@@ -30,12 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES" codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES"> onlyGenerateCoverageForSpecifiedTargets = "YES">
<TestPlans>
<TestPlanReference
default = "YES"
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan">
</TestPlanReference>
</TestPlans>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@@ -45,10 +38,6 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
<CodeCoverageTargets> <CodeCoverageTargets>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@@ -58,6 +47,12 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</CodeCoverageTargets> </CodeCoverageTargets>
<TestPlans>
<TestPlanReference
reference = "container:UnitTests/SupportingFiles/UnitTests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@@ -78,8 +73,6 @@
ReferencedContainer = "container:ElementX.xcodeproj"> ReferencedContainer = "container:ElementX.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Release" buildConfiguration = "Release"
@@ -87,8 +80,6 @@
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
<CommandLineArguments>
</CommandLineArguments>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@@ -40,8 +40,6 @@ class AccessibilityTestsAppCoordinator: AppCoordinatorProtocol {
MXLog.configure(currentTarget: "accessibility-tests") MXLog.configure(currentTarget: "accessibility-tests")
let appSettings = AppSettings.volatile()
guard let name = ProcessInfo.accessibilityViewID, guard let name = ProcessInfo.accessibilityViewID,
let previewType = TestablePreviewsDictionary.dictionary[name] else { let previewType = TestablePreviewsDictionary.dictionary[name] else {
fatalError("Unable to launch with unknown screen.") fatalError("Unable to launch with unknown screen.")
@@ -125,7 +123,6 @@ struct PreviewsWrapperView: View {
self.previews = previews self.previews = previews
} }
@MainActor
func updateCurrentIndex() async { func updateCurrentIndex() async {
let newIndex = currentIndex + 1 let newIndex = currentIndex + 1
guard newIndex < previews.count else { guard newIndex < previews.count else {
@@ -142,14 +139,17 @@ struct PreviewsWrapperView: View {
switch fulfillmentSource { switch fulfillmentSource {
case .publisher(let publisher): case .publisher(let publisher):
_ = await publisher // Not sure why but some publishers seem to not properly communicate their completion,
// Not sure whye byt some publisher seem to not properly comunicate their completion, // so we added a timeout. Since we are going to migrate from publishers to streams,
// so we added a timeout. Since we are going to migrate from publishers to stream, // this is a temporary solution
// this is a temporary solution var iterator = publisher
.timeout(.seconds(1), scheduler: DispatchQueue.main) .timeout(.seconds(1), scheduler: DispatchQueue.main)
.values.first { $0 == true } .values
.makeAsyncIterator()
while let value = await iterator.next(isolation: #isolation), value == false { }
case .sequence(let sequence): case .sequence(let sequence):
_ = await sequence.first { $0 == true } var iterator = sequence.makeAsyncIterator()
while let value = await iterator.next(isolation: #isolation), value == false { }
case .none: case .none:
break break
} }
+3 -3
View File
@@ -9,7 +9,7 @@
import Foundation import Foundation
import Synchronization import Synchronization
final class AppHooks: AppHooksProtocol { final nonisolated class AppHooks: AppHooksProtocol {
#if IS_MAIN_APP #if IS_MAIN_APP
func configure(with userSession: UserSessionProtocol?) async { func configure(with userSession: UserSessionProtocol?) async {
await roomScreenHook.configure(with: userSession) await roomScreenHook.configure(with: userSession)
@@ -107,10 +107,10 @@ final class AppHooks: AppHooksProtocol {
} }
} }
protocol AppHooksProtocol: Sendable { nonisolated protocol AppHooksProtocol: Sendable {
func setUp() func setUp()
} }
extension AppHooksProtocol { nonisolated extension AppHooksProtocol {
func setUp() { } func setUp() { }
} }
@@ -8,8 +8,8 @@
import Foundation import Foundation
protocol AppSettingsHookProtocol { nonisolated protocol AppSettingsHookProtocol: Sendable {
func configure(_ appSettings: AppSettings) -> AppSettings @MainActor func configure(_ appSettings: AppSettings) -> AppSettings
} }
struct DefaultAppSettingsHook: AppSettingsHookProtocol { struct DefaultAppSettingsHook: AppSettingsHookProtocol {
@@ -8,7 +8,7 @@
import Foundation import Foundation
protocol BugReportHookProtocol { nonisolated protocol BugReportHookProtocol: Sendable {
func update(_ bugReport: BugReport) -> BugReport func update(_ bugReport: BugReport) -> BugReport
} }
@@ -8,11 +8,11 @@
import MatrixRustSDK import MatrixRustSDK
protocol ClientBuilderHookProtocol: Sendable { nonisolated protocol ClientBuilderHookProtocol: Sendable {
func configure(_ builder: ClientBuilder) -> ClientBuilder func configure(_ builder: ClientBuilder) -> ClientBuilder
} }
struct DefaultClientBuilderHook: ClientBuilderHookProtocol { nonisolated struct DefaultClientBuilderHook: ClientBuilderHookProtocol {
func configure(_ builder: ClientBuilder) -> ClientBuilder { func configure(_ builder: ClientBuilder) -> ClientBuilder {
builder builder
} }
@@ -8,7 +8,7 @@
import Compound import Compound
protocol CompoundHookProtocol { nonisolated protocol CompoundHookProtocol: Sendable {
@MainActor func override(colors: CompoundColors, uiColors: CompoundUIColors) @MainActor func override(colors: CompoundColors, uiColors: CompoundUIColors)
} }
@@ -7,7 +7,7 @@
import SwiftUI import SwiftUI
protocol DeveloperOptionsScreenHookProtocol { nonisolated protocol DeveloperOptionsScreenHookProtocol: Sendable {
func generalSectionRows() -> AnyView? func generalSectionRows() -> AnyView?
} }
@@ -8,8 +8,8 @@
import Foundation import Foundation
protocol OAuthPresenterHookProtocol { nonisolated protocol OAuthPresenterHookProtocol: Sendable {
func update(_ url: URL) -> URL @MainActor func update(_ url: URL) -> URL
} }
struct DefaultOAuthPresenterHook: OAuthPresenterHookProtocol { struct DefaultOAuthPresenterHook: OAuthPresenterHookProtocol {
@@ -12,7 +12,7 @@ import SwiftUI
var actions: AnyPublisher<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> { get } var actions: AnyPublisher<SecureBackupRecoveryKeyScreenCoordinatorAction, Never> { get }
} }
protocol RecoveryKeyScreenHookProtocol { nonisolated protocol RecoveryKeyScreenHookProtocol: Sendable {
@MainActor func makeCoordinator(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters, homeserver: String) -> any SecureBackupRecoveryKeyCoordinatorProtocol @MainActor func makeCoordinator(parameters: SecureBackupRecoveryKeyScreenCoordinatorParameters, homeserver: String) -> any SecureBackupRecoveryKeyCoordinatorProtocol
} }
@@ -8,9 +8,9 @@
import Foundation import Foundation
protocol RoomScreenHookProtocol { nonisolated protocol RoomScreenHookProtocol: Sendable {
func configure(with userSession: UserSessionProtocol?) async @MainActor func configure(with userSession: UserSessionProtocol?) async
func update(_ viewState: RoomScreenViewState) -> RoomScreenViewState @MainActor func update(_ viewState: RoomScreenViewState) -> RoomScreenViewState
} }
struct DefaultRoomScreenHook: RoomScreenHookProtocol { struct DefaultRoomScreenHook: RoomScreenHookProtocol {
@@ -8,10 +8,10 @@
import MatrixRustSDK import MatrixRustSDK
protocol TracingHookProtocol: Sendable { nonisolated protocol TracingHookProtocol: Sendable {
func update(_ configuration: TracingConfiguration, with rageshakeURL: RemotePreference<RageshakeConfiguration>) func update(_ configuration: TracingConfiguration, with rageshakeURL: RemotePreference<RageshakeConfiguration>)
} }
struct DefaultTracingHook: TracingHookProtocol { nonisolated struct DefaultTracingHook: TracingHookProtocol {
func update(_ configuration: TracingConfiguration, with rageshakeURL: RemotePreference<RageshakeConfiguration>) { } func update(_ configuration: TracingConfiguration, with rageshakeURL: RemotePreference<RageshakeConfiguration>) { }
} }
@@ -1262,6 +1262,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
// this does not crash on the main thread, even though the whole AppCoordinator is on the Main actor. As such, // this does not crash on the main thread, even though the whole AppCoordinator is on the Main actor. As such,
// we hop onto the main actor explicitly before pausing. // we hop onto the main actor explicitly before pausing.
// https://sentry.tools.element.io/organizations/element/issues/4477794/ // https://sentry.tools.element.io/organizations/element/issues/4477794/
// BGTask isn't annotated but is documented as thread-safe, with
// setTaskCompleted(success:) being callable from any thread.
nonisolated(unsafe) let task = task
task.expirationHandler = { @Sendable [weak self] in task.expirationHandler = { @Sendable [weak self] in
MXLog.info("Background app refresh task is about to expire.") MXLog.info("Background app refresh task is about to expire.")
@@ -8,7 +8,6 @@
import Foundation import Foundation
@MainActor
protocol AppCoordinatorProtocol: CoordinatorProtocol { protocol AppCoordinatorProtocol: CoordinatorProtocol {
var windowManager: SecureWindowManagerProtocol { get } var windowManager: SecureWindowManagerProtocol { get }
@@ -36,7 +36,7 @@ class AppMediator: AppMediatorProtocol {
} }
} }
func beginBackgroundTask(expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier { func beginBackgroundTask(expirationHandler handler: (@MainActor @Sendable () -> Void)?) -> UIBackgroundTaskIdentifier {
application.beginBackgroundTask(expirationHandler: handler) application.beginBackgroundTask(expirationHandler: handler)
} }
@@ -10,14 +10,13 @@ import Foundation
import UIKit import UIKit
// sourcery: AutoMockable // sourcery: AutoMockable
@MainActor
protocol AppMediatorProtocol { protocol AppMediatorProtocol {
var windowManager: WindowManagerProtocol { get } var windowManager: WindowManagerProtocol { get }
var networkMonitor: NetworkMonitorProtocol { get } var networkMonitor: NetworkMonitorProtocol { get }
var appState: UIApplication.State { get } var appState: UIApplication.State { get }
func beginBackgroundTask(expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier func beginBackgroundTask(expirationHandler handler: (@MainActor @Sendable () -> Void)?) -> UIBackgroundTaskIdentifier
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
@@ -14,6 +14,7 @@ struct Application: App {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(\.dismissWindow) private var dismissWindow @Environment(\.dismissWindow) private var dismissWindow
@Environment(\.displayScale) private var displayScale
private var appCoordinator: AppCoordinatorProtocol! private var appCoordinator: AppCoordinatorProtocol!
@@ -51,6 +52,7 @@ struct Application: App {
appCoordinator.handleUserActivity(userActivity) appCoordinator.handleUserActivity(userActivity)
} }
.task { .task {
UserAgentBuilder.displayScale = displayScale
appCoordinator.start() appCoordinator.start()
appCoordinator.windowManager.configure(withOpenWindowAction: openWindow, appCoordinator.windowManager.configure(withOpenWindowAction: openWindow,
dismissWindowAction: dismissWindow) dismissWindowAction: dismissWindow)
@@ -8,7 +8,6 @@
import SwiftUI import SwiftUI
@MainActor
protocol CoordinatorProtocol: AnyObject { protocol CoordinatorProtocol: AnyObject {
func start() func start()
func stop() func stop()
@@ -9,7 +9,6 @@
import Foundation import Foundation
// periphery:ignore - markdown protocol // periphery:ignore - markdown protocol
@MainActor
protocol FlowCoordinatorProtocol { protocol FlowCoordinatorProtocol {
func start(animated: Bool) func start(animated: Bool)
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) func handleAppRoute(_ appRoute: AppRoute, animated: Bool)
@@ -25,7 +24,6 @@ extension FlowCoordinatorProtocol {
/// Core parameters that are shared across the main flows for easy dependency injection. /// Core parameters that are shared across the main flows for easy dependency injection.
/// ///
/// Please do **not** pass this type directly to screen coordinators/view models. /// Please do **not** pass this type directly to screen coordinators/view models.
@MainActor
struct CommonFlowParameters { struct CommonFlowParameters {
let userSession: UserSessionProtocol let userSession: UserSessionProtocol
let bugReportService: BugReportServiceProtocol let bugReportService: BugReportServiceProtocol
@@ -10,7 +10,6 @@ import Foundation
/// A CoordinatorProtocol wrapper and type erasing component that allows /// A CoordinatorProtocol wrapper and type erasing component that allows
/// dynamically presenting arbitrary screens /// dynamically presenting arbitrary screens
@MainActor
class NavigationModule: Identifiable, Hashable { class NavigationModule: Identifiable, Hashable {
let id = UUID() let id = UUID()
@@ -17,7 +17,6 @@ import SwiftUI
var dismissalCallback: (() -> Void)? var dismissalCallback: (() -> Void)?
} }
@MainActor
@Observable class TabDetails { @Observable class TabDetails {
/// A unique tab that identifies the tab for selection. /// A unique tab that identifies the tab for selection.
let tag: Tag let tag: Tag
@@ -58,7 +57,7 @@ import SwiftUI
module.id module.id
} }
@MainActor var coordinator: CoordinatorProtocol? { var coordinator: CoordinatorProtocol? {
module.coordinator module.coordinator
} }
} }
@@ -9,7 +9,7 @@
import Foundation import Foundation
/// A type that represents how to set up the analytics module in the app. /// A type that represents how to set up the analytics module in the app.
struct AnalyticsConfiguration { nonisolated struct AnalyticsConfiguration {
/// The host to use for PostHog analytics. /// The host to use for PostHog analytics.
let host: String let host: String
/// The public key for submitting analytics. /// The public key for submitting analytics.
@@ -8,7 +8,7 @@
import Foundation import Foundation
enum AnalyticsConsentState: String, Codable { nonisolated enum AnalyticsConsentState: String, Codable {
case optedOut case optedOut
case optedIn case optedIn
case unknown case unknown
@@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
/// Used to specify the user's app specific appearance preference /// Used to specify the user's app specific appearance preference
enum AppAppearance: CaseIterable, Codable { nonisolated enum AppAppearance: CaseIterable, Codable {
case system case system
case dark case dark
case light case light
@@ -14,7 +14,7 @@ import Foundation
import SwiftUI import SwiftUI
/// Common settings between app and NSE /// Common settings between app and NSE
protocol CommonSettingsProtocol: AnyObject, Sendable { nonisolated protocol CommonSettingsProtocol: AnyObject, Sendable {
var lastNotificationBootTime: TimeInterval? { get set } var lastNotificationBootTime: TimeInterval? { get set }
var selectedNotificationTone: NotificationTone? { get set } var selectedNotificationTone: NotificationTone? { get set }
@@ -27,7 +27,7 @@ protocol CommonSettingsProtocol: AnyObject, Sendable {
var hideQuietNotificationAlerts: Bool { get } var hideQuietNotificationAlerts: Bool { get }
} }
enum AppBuildType { nonisolated enum AppBuildType {
case debug case debug
case nightly case nightly
case release case release
@@ -36,7 +36,7 @@ enum AppBuildType {
/// Store Element specific app settings. /// Store Element specific app settings.
/// ///
/// State is persisted in `UserDefaults`, which is thread-safe per Apple's documentation, hence `@unchecked`. /// State is persisted in `UserDefaults`, which is thread-safe per Apple's documentation, hence `@unchecked`.
final class AppSettings: @unchecked Sendable { final nonisolated class AppSettings: @unchecked Sendable {
fileprivate enum UserDefaultsKeys: String, PreferenceKeyable { fileprivate enum UserDefaultsKeys: String, PreferenceKeyable {
case lastVersionLaunched case lastVersionLaunched
case seenInvites case seenInvites
@@ -173,16 +173,16 @@ final class AppSettings: @unchecked Sendable {
/// used to detect when migrations should be run. When `nil` the app may have been /// used to detect when migrations should be run. When `nil` the app may have been
/// deleted between runs so should clear data in the shared container and keychain. /// deleted between runs so should clear data in the shared container and keychain.
@UserPreference @UserPreference
var lastVersionLaunched: String? nonisolated(unsafe) var lastVersionLaunched: String?
/// The Set of room identifiers of invites that the user already saw in the invites list. /// The Set of room identifiers of invites that the user already saw in the invites list.
/// This Set is being used to implement badges for unread invites. /// This Set is being used to implement badges for unread invites.
@UserPreference @UserPreference
var seenInvites: Set<String> nonisolated(unsafe) var seenInvites: Set<String>
/// Defaults to `true` for new users, and we use a migration to set it to `false` for existing users. /// Defaults to `true` for new users, and we use a migration to set it to `false` for existing users.
@UserPreference @UserPreference
var hasSeenNewSoundBanner: Bool nonisolated(unsafe) var hasSeenNewSoundBanner: Bool
/// The initial set of account providers shown to the user in the authentication flow. /// The initial set of account providers shown to the user in the authentication flow.
/// ///
@@ -227,7 +227,7 @@ final class AppSettings: @unchecked Sendable {
let elementProAppStoreURL: URL = "https://apps.apple.com/app/element-pro-for-work/id6502951615" let elementProAppStoreURL: URL = "https://apps.apple.com/app/element-pro-for-work/id6502951615"
@UserPreference @UserPreference
var appAppearance: AppAppearance nonisolated(unsafe) var appAppearance: AppAppearance
// MARK: - Security // MARK: - Security
@@ -239,7 +239,7 @@ final class AppSettings: @unchecked Sendable {
let appLockPINCodeBlockList = ["0000", "1234"] let appLockPINCodeBlockList = ["0000", "1234"]
/// The number of attempts the user has made to unlock the app with a PIN code (resets when unlocked). /// The number of attempts the user has made to unlock the app with a PIN code (resets when unlocked).
@UserPreference @UserPreference
var appLockNumberOfPINAttempts: Int nonisolated(unsafe) var appLockNumberOfPINAttempts: Int
// MARK: - Authentication // MARK: - Authentication
@@ -247,15 +247,17 @@ final class AppSettings: @unchecked Sendable {
let oAuthStaticRegistrations: [URL: String] = ["https://id.thirdroom.io/realms/thirdroom": "elementx"] let oAuthStaticRegistrations: [URL: String] = ["https://id.thirdroom.io/realms/thirdroom": "elementx"]
/// The redirect URL used for OAuth. For the normal case we don't actually need the bundle ID as the web authentication session handles the redirect internally. /// The redirect URL used for OAuth. For the normal case we don't actually need the bundle ID as the web authentication session handles the redirect internally.
/// However in the case where MAS sends the user to an external app, we need to make sure that the system will open the correct variant of the app (e.g. Nightly). /// However in the case where MAS sends the user to an external app, we need to make sure that the system will open the correct variant of the app (e.g. Nightly).
private(set) var oAuthRedirectURL: URL! = URL(string: "https://element.io/oauth/ios/\(InfoPlistReader.main.bundleIdentifier)") private(set) nonisolated(unsafe) var oAuthRedirectURL: URL! = URL(string: "https://element.io/oauth/ios/\(InfoPlistReader.main.bundleIdentifier)")
private(set) lazy var oAuthConfiguration = OAuthConfiguration(clientName: InfoPlistReader.main.bundleDisplayName, var oAuthConfiguration: OAuthConfiguration {
redirectURI: oAuthRedirectURL, OAuthConfiguration(clientName: InfoPlistReader.main.bundleDisplayName,
clientURI: websiteURL, redirectURI: oAuthRedirectURL,
logoURI: logoURL, clientURI: websiteURL,
tosURI: acceptableUseURL, logoURI: logoURL,
policyURI: privacyURL, tosURI: acceptableUseURL,
staticRegistrations: oAuthStaticRegistrations.mapKeys { $0.absoluteString }) policyURI: privacyURL,
staticRegistrations: oAuthStaticRegistrations.mapKeys { $0.absoluteString })
}
/// Whether or not the Create Account button is shown on the start screen. /// Whether or not the Create Account button is shown on the start screen.
/// ///
@@ -278,33 +280,33 @@ final class AppSettings: @unchecked Sendable {
} }
@UserPreference @UserPreference
var enableNotifications: Bool nonisolated(unsafe) var enableNotifications: Bool
@UserPreference @UserPreference
var enableInAppNotifications: Bool nonisolated(unsafe) var enableInAppNotifications: Bool
@UserPreference @UserPreference
var hideQuietNotificationAlerts: Bool nonisolated(unsafe) var hideQuietNotificationAlerts: Bool
/// Tag describing which set of device specific rules a pusher executes. /// Tag describing which set of device specific rules a pusher executes.
@UserPreference @UserPreference
var pusherProfileTag: String? nonisolated(unsafe) var pusherProfileTag: String?
/// The device's last boot time as recorded by the NSE. /// The device's last boot time as recorded by the NSE.
@UserPreference @UserPreference
var lastNotificationBootTime: TimeInterval? nonisolated(unsafe) var lastNotificationBootTime: TimeInterval?
/// The sound played when delivering noisy notifications. If nil, use the ElementX default /// The sound played when delivering noisy notifications. If nil, use the ElementX default
@UserPreference @UserPreference
var selectedNotificationTone: NotificationTone? nonisolated(unsafe) var selectedNotificationTone: NotificationTone?
// MARK: - Logging // MARK: - Logging
@UserPreference @UserPreference
var logLevel: LogLevel nonisolated(unsafe) var logLevel: LogLevel
@UserPreference @UserPreference
var traceLogPacks: Set<TraceLogPack> nonisolated(unsafe) var traceLogPacks: Set<TraceLogPack>
// MARK: - Bug report // MARK: - Bug report
@@ -332,46 +334,46 @@ final class AppSettings: @unchecked Sendable {
/// Whether the user has opted in to send analytics. /// Whether the user has opted in to send analytics.
@UserPreference @UserPreference
var analyticsConsentState: AnalyticsConsentState nonisolated(unsafe) var analyticsConsentState: AnalyticsConsentState
@UserPreference @UserPreference
var hasRunNotificationPermissionsOnboarding: Bool nonisolated(unsafe) var hasRunNotificationPermissionsOnboarding: Bool
@UserPreference @UserPreference
var hasRunIdentityConfirmationOnboarding: Bool nonisolated(unsafe) var hasRunIdentityConfirmationOnboarding: Bool
@UserPreference @UserPreference
var hasRequestedLocationAlwaysLocationAuthorization: Bool nonisolated(unsafe) var hasRequestedLocationAlwaysLocationAuthorization: Bool
@UserPreference @UserPreference
var frequentlyUsedSystemEmojis: [FrequentlyUsedEmoji] nonisolated(unsafe) var frequentlyUsedSystemEmojis: [FrequentlyUsedEmoji]
// MARK: - Live Location // MARK: - Live Location
@UserPreference @UserPreference
var liveLocationSharingSessionsByRoomID: [String: LiveLocationSession] nonisolated(unsafe) var liveLocationSharingSessionsByRoomID: [String: LiveLocationSession]
@UserPreference @UserPreference
var liveLocationMinimumDistanceUpdate: Int nonisolated(unsafe) var liveLocationMinimumDistanceUpdate: Int
@UserPreference @UserPreference
var liveLocationDisclaimerDisplayed: Bool nonisolated(unsafe) var liveLocationDisclaimerDisplayed: Bool
// MARK: - Home Screen // MARK: - Home Screen
@UserPreference @UserPreference
var roomListActivityVisibility: RoomListActivityVisibility nonisolated(unsafe) var roomListActivityVisibility: RoomListActivityVisibility
// MARK: - Room Screen // MARK: - Room Screen
@UserPreference @UserPreference
var viewSourceEnabled: Bool nonisolated(unsafe) var viewSourceEnabled: Bool
@UserPreference @UserPreference
var optimizeMediaUploads: Bool nonisolated(unsafe) var optimizeMediaUploads: Bool
@UserPreference @UserPreference
var voiceMessagePlaybackSpeed: AudioPlaybackSpeed nonisolated(unsafe) var voiceMessagePlaybackSpeed: AudioPlaybackSpeed
/// Whether or not to show a warning on the media caption composer so the user knows /// Whether or not to show a warning on the media caption composer so the user knows
/// that captions might not be visible to users who are using other Matrix clients. /// that captions might not be visible to users who are using other Matrix clients.
@@ -390,7 +392,7 @@ final class AppSettings: @unchecked Sendable {
let elementCallPosthogSentryDSN = "https://3bd2f95ba5554d4497da7153b552ffb5@sentry.tools.element.io/41" let elementCallPosthogSentryDSN = "https://3bd2f95ba5554d4497da7153b552ffb5@sentry.tools.element.io/41"
@UserPreference @UserPreference
var elementCallBaseURLOverride: URL? nonisolated(unsafe) var elementCallBaseURLOverride: URL?
// MARK: - Users // MARK: - Users
@@ -413,50 +415,50 @@ final class AppSettings: @unchecked Sendable {
// MARK: - Presence // MARK: - Presence
@UserPreference @UserPreference
var sharePresence: Bool nonisolated(unsafe) var sharePresence: Bool
// MARK: - Feature Flags // MARK: - Feature Flags
/// Others /// Others
@UserPreference @UserPreference
var fuzzyRoomListSearchEnabled: Bool nonisolated(unsafe) var fuzzyRoomListSearchEnabled: Bool
@UserPreference @UserPreference
var lowPriorityFilterEnabled: Bool nonisolated(unsafe) var lowPriorityFilterEnabled: Bool
/// Configuration to enable only signed device isolation mode for crypto. In this mode only devices signed by their owner will be considered in e2ee rooms. /// Configuration to enable only signed device isolation mode for crypto. In this mode only devices signed by their owner will be considered in e2ee rooms.
@UserPreference @UserPreference
var enableOnlySignedDeviceIsolationMode: Bool nonisolated(unsafe) var enableOnlySignedDeviceIsolationMode: Bool
@UserPreference @UserPreference
var knockingEnabled: Bool nonisolated(unsafe) var knockingEnabled: Bool
@UserPreference @UserPreference
var threadsEnabled: Bool nonisolated(unsafe) var threadsEnabled: Bool
@UserPreference @UserPreference
var roomThreadListEnabled: Bool nonisolated(unsafe) var roomThreadListEnabled: Bool
@UserPreference @UserPreference
var focusEventOnNotificationTap: Bool nonisolated(unsafe) var focusEventOnNotificationTap: Bool
@UserPreference @UserPreference
var linkPreviewsEnabled: Bool nonisolated(unsafe) var linkPreviewsEnabled: Bool
@UserPreference @UserPreference
var jumpToReadMarkerEnabled: Bool nonisolated(unsafe) var jumpToReadMarkerEnabled: Bool
@UserPreference @UserPreference
var linkNewDeviceEnabled: Bool nonisolated(unsafe) var linkNewDeviceEnabled: Bool
@UserPreference @UserPreference
var automaticBackPaginationEnabled: Bool nonisolated(unsafe) var automaticBackPaginationEnabled: Bool
@UserPreference @UserPreference
var clientPausingAndResumingEnabled: Bool nonisolated(unsafe) var clientPausingAndResumingEnabled: Bool
@UserPreference @UserPreference
var developerOptionsEnabled: Bool nonisolated(unsafe) var developerOptionsEnabled: Bool
init(store: UserDefaultsProtocol) { init(store: UserDefaultsProtocol) {
// UserDefaults to be used on reads and writes. // UserDefaults to be used on reads and writes.
@@ -509,9 +511,9 @@ final class AppSettings: @unchecked Sendable {
} }
} }
extension AppSettings: CommonSettingsProtocol { } nonisolated extension AppSettings: CommonSettingsProtocol { }
private extension UserPreference { private nonisolated extension UserPreference {
convenience init(key: AppSettings.UserDefaultsKeys, defaultValue: T, storage backingStorage: UserDefaultsProtocol, mode: Mode = .localOverRemote) { convenience init(key: AppSettings.UserDefaultsKeys, defaultValue: T, storage backingStorage: UserDefaultsProtocol, mode: Mode = .localOverRemote) {
self.init(key: key as any PreferenceKeyable, defaultValue: defaultValue, storage: backingStorage, mode: mode) self.init(key: key as any PreferenceKeyable, defaultValue: defaultValue, storage: backingStorage, mode: mode)
} }
@@ -5,7 +5,7 @@
// Please see LICENSE files in the repository root for full details. // Please see LICENSE files in the repository root for full details.
// //
enum AudioPlaybackSpeed: Float, CaseIterable, Codable { nonisolated enum AudioPlaybackSpeed: Float, CaseIterable, Codable {
case `default` = 1.0 case `default` = 1.0
case fast = 1.5 case fast = 1.5
case fastest = 2.0 case fastest = 2.0
@@ -6,7 +6,7 @@
// Please see LICENSE files in the repository root for full details. // Please see LICENSE files in the repository root for full details.
// //
struct FrequentlyUsedEmoji: Codable, Hashable { nonisolated struct FrequentlyUsedEmoji: Codable, Hashable {
let count: UInt let count: UInt
let key: String let key: String
@@ -7,7 +7,7 @@
import Foundation import Foundation
struct LiveLocationSession: Codable, Equatable { nonisolated struct LiveLocationSession: Codable, Equatable {
let eventID: String let eventID: String
let expirationDate: Date let expirationDate: Date
} }
@@ -12,7 +12,7 @@ import Foundation
/// ///
/// Either resolves to a locally-bundled MapTiler configuration, or to a remote /// Either resolves to a locally-bundled MapTiler configuration, or to a remote
/// `style.json` URL /// `style.json` URL
enum MapTilerSettings: Equatable { nonisolated enum MapTilerSettings: Equatable {
/// All of the configuration necessary to use MapTiler maps. /// All of the configuration necessary to use MapTiler maps.
/// ///
/// The style IDs need to be generated with the account that the API key belongs to. For more information read /// The style IDs need to be generated with the account that the API key belongs to. For more information read
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
struct OAuthConfiguration { nonisolated struct OAuthConfiguration {
let clientName: String let clientName: String
let redirectURI: URL let redirectURI: URL
let clientURI: URL let clientURI: URL
@@ -22,7 +22,7 @@ struct OAuthConfiguration {
#if canImport(MatrixRustSDK) #if canImport(MatrixRustSDK)
import MatrixRustSDK import MatrixRustSDK
extension OAuthConfiguration { nonisolated extension OAuthConfiguration {
var rustValue: MatrixRustSDK.OAuthConfiguration { var rustValue: MatrixRustSDK.OAuthConfiguration {
MatrixRustSDK.OAuthConfiguration(clientName: clientName, MatrixRustSDK.OAuthConfiguration(clientName: clientName,
redirectUri: redirectURI.absoluteString, redirectUri: redirectURI.absoluteString,
@@ -9,7 +9,7 @@
import Foundation import Foundation
/// A type that represents how rageshakes are collected by the app. /// A type that represents how rageshakes are collected by the app.
enum RageshakeConfiguration: Equatable, Codable { nonisolated enum RageshakeConfiguration: Equatable, Codable {
/// Rageshakes should be sent to the provided URL /// Rageshakes should be sent to the provided URL
case url(URL) case url(URL)
/// Rageshakes are disabled. /// Rageshakes are disabled.
@@ -13,7 +13,7 @@ import Combine
/// ///
/// Unlike ``UserPreference``, this type of setting isn't settable by the user, nor is the /// Unlike ``UserPreference``, this type of setting isn't settable by the user, nor is the
/// remote value persisted between app launches. /// remote value persisted between app launches.
class RemotePreference<T: Equatable> { nonisolated class RemotePreference<T: Equatable> {
private let defaultValue: T private let defaultValue: T
private let subject: CurrentValueSubject<T, Never> private let subject: CurrentValueSubject<T, Never>
var publisher: CurrentValuePublisher<T, Never> { var publisher: CurrentValuePublisher<T, Never> {
@@ -5,7 +5,7 @@
// Please see LICENSE files in the repository root for full details. // Please see LICENSE files in the repository root for full details.
// //
enum RoomListActivityVisibility: String, CaseIterable, Codable { nonisolated enum RoomListActivityVisibility: String, CaseIterable, Codable {
/// Show unread badges for all unread messages (the default). /// Show unread badges for all unread messages (the default).
case current case current
/// Don't show badges but bold unread room names and their latest message /// Don't show badges but bold unread room names and their latest message
@@ -7,7 +7,7 @@
import Foundation import Foundation
final class TrackedUserDefaults: UserDefaults, UserDefaultsProtocol { final nonisolated class TrackedUserDefaults: UserDefaults, UserDefaultsProtocol {
private let lock = NSLock() private let lock = NSLock()
private nonisolated(unsafe) var _trackedSuites: [String] = [] private nonisolated(unsafe) var _trackedSuites: [String] = []
@@ -8,7 +8,7 @@
import Foundation import Foundation
public protocol UserDefaultsProtocol: AnyObject { public nonisolated protocol UserDefaultsProtocol: AnyObject {
func data(forKey key: String) -> Data? func data(forKey key: String) -> Data?
func object(forKey key: String) -> Any? func object(forKey key: String) -> Any?
func removeObject(forKey key: String) func removeObject(forKey key: String)
@@ -13,7 +13,7 @@ import Foundation
/// to listen for value changes. The publisher does not skip consecutive duplicates, as there is no /// to listen for value changes. The publisher does not skip consecutive duplicates, as there is no
/// `Equatable` enforcement at this level. /// `Equatable` enforcement at this level.
@propertyWrapper @propertyWrapper
final class UserPreference<T: Codable> { final nonisolated class UserPreference<T: Codable> {
static var remotePrefix: String { static var remotePrefix: String {
"remote-" "remote-"
} }
@@ -94,7 +94,7 @@ final class UserPreference<T: Codable> {
// MARK: - UserPreference convenience initializers // MARK: - UserPreference convenience initializers
extension UserPreference { nonisolated extension UserPreference {
convenience init(key: any PreferenceKeyable, defaultValue: T, storage backingStorage: UserDefaultsProtocol, mode: Mode = .localOverRemote) { convenience init(key: any PreferenceKeyable, defaultValue: T, storage backingStorage: UserDefaultsProtocol, mode: Mode = .localOverRemote) {
let storage: any KeyedStorage<T> = UserDefaultsStorage(userDefaults: backingStorage) let storage: any KeyedStorage<T> = UserDefaultsStorage(userDefaults: backingStorage)
@@ -108,11 +108,11 @@ extension UserPreference {
// MARK: - Keys // MARK: - Keys
protocol PreferenceKeyable: RawRepresentable where RawValue == String { } nonisolated protocol PreferenceKeyable: RawRepresentable where RawValue == String { }
// MARK: - Storage // MARK: - Storage
protocol KeyedStorage<Value> { nonisolated protocol KeyedStorage<Value> {
associatedtype Value: Codable associatedtype Value: Codable
subscript(key: String) -> Value? { get set } subscript(key: String) -> Value? { get set }
@@ -122,7 +122,7 @@ protocol KeyedStorage<Value> {
/// ///
/// When used with a `Value` that conforms to `PlistRepresentable` the Codable encode/decode /// When used with a `Value` that conforms to `PlistRepresentable` the Codable encode/decode
/// phase is skipped, and values are stored natively in the plist. /// phase is skipped, and values are stored natively in the plist.
final class UserDefaultsStorage<Value: Codable>: KeyedStorage { final nonisolated class UserDefaultsStorage<Value: Codable>: KeyedStorage {
private let userDefaults: UserDefaultsProtocol private let userDefaults: UserDefaultsProtocol
init(userDefaults: UserDefaultsProtocol) { init(userDefaults: UserDefaultsProtocol) {
@@ -181,11 +181,11 @@ final class UserDefaultsStorage<Value: Codable>: KeyedStorage {
} }
} }
private protocol Nullable { private nonisolated protocol Nullable {
var isNil: Bool { get } var isNil: Bool { get }
} }
extension Optional: Nullable { nonisolated extension Optional: Nullable {
var isNil: Bool { var isNil: Bool {
switch self { switch self {
case .none: case .none:
@@ -198,13 +198,13 @@ extension Optional: Nullable {
} }
} }
extension Dictionary: KeyedStorage where Key == String, Value: Codable { } nonisolated extension Dictionary: KeyedStorage where Key == String, Value: Codable { }
// MARK: - PlistRepresentable // MARK: - PlistRepresentable
/// A protocol to mark types as being plist compliant. /// A protocol to mark types as being plist compliant.
/// UserDefaultsStorage uses this protocol to avoid to encode/decode with Codable plist compliant values. /// UserDefaultsStorage uses this protocol to avoid to encode/decode with Codable plist compliant values.
protocol PlistRepresentable { } nonisolated protocol PlistRepresentable { }
extension Bool: PlistRepresentable { } extension Bool: PlistRepresentable { }
extension String: PlistRepresentable { } extension String: PlistRepresentable { }
@@ -7,7 +7,7 @@
import Foundation import Foundation
public final class VolatileUserDefaults: UserDefaultsProtocol, Sendable { public final nonisolated class VolatileUserDefaults: UserDefaultsProtocol, Sendable {
private let lock = NSLock() private let lock = NSLock()
private nonisolated(unsafe) var _storage: [String: Any] private nonisolated(unsafe) var _storage: [String: Any]
@@ -11,7 +11,7 @@ import Foundation
import MapLibre import MapLibre
import MatrixRustSDK import MatrixRustSDK
enum Target: String { nonisolated enum Target: String {
case mainApp = "elementx" case mainApp = "elementx"
case nse case nse
case shareExtension = "shareextension" case shareExtension = "shareextension"
@@ -18,7 +18,6 @@ protocol SecureWindowManagerDelegate: AnyObject {
func windowManagerDidConfigureWindows(_ windowManager: SecureWindowManagerProtocol) func windowManagerDidConfigureWindows(_ windowManager: SecureWindowManagerProtocol)
} }
@MainActor
protocol SecureWindowManagerProtocol: WindowManagerProtocol { protocol SecureWindowManagerProtocol: WindowManagerProtocol {
var delegate: SecureWindowManagerDelegate? { get set } var delegate: SecureWindowManagerDelegate? { get set }
@@ -10,7 +10,6 @@ import Combine
import SwiftState import SwiftState
import SwiftUI import SwiftUI
@MainActor
protocol AuthenticationFlowCoordinatorDelegate: AnyObject { protocol AuthenticationFlowCoordinatorDelegate: AnyObject {
func authenticationFlowCoordinator(didLoginWithSession userSession: UserSessionProtocol) func authenticationFlowCoordinator(didLoginWithSession userSession: UserSessionProtocol)
} }
@@ -9,7 +9,7 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
internal enum UntranslatedL10n { internal nonisolated enum UntranslatedL10n {
/// Clear all data currently stored on this device? /// Clear all data currently stored on this device?
/// Sign in again to access your account data and messages. /// Sign in again to access your account data and messages.
internal static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") } internal static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") }
@@ -43,7 +43,7 @@ internal enum UntranslatedL10n {
// MARK: - Implementation Details // MARK: - Implementation Details
extension UntranslatedL10n { nonisolated extension UntranslatedL10n {
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
// No need to check languages, we always default to en for untranslated strings // No need to check languages, we always default to en for untranslated strings
guard let bundle = Bundle.lprojBundle(for: "en") else { return key } guard let bundle = Bundle.lprojBundle(for: "en") else { return key }
+2 -2
View File
@@ -9,7 +9,7 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
internal enum L10n { internal nonisolated enum L10n {
/// Add avatar /// Add avatar
internal static var a11yAddAvatar: String { return L10n.tr("Localizable", "a11y_add_avatar") } internal static var a11yAddAvatar: String { return L10n.tr("Localizable", "a11y_add_avatar") }
/// Add reaction: %1$@ /// Add reaction: %1$@
@@ -3973,7 +3973,7 @@ internal enum L10n {
// MARK: - Implementation Details // MARK: - Implementation Details
extension L10n { nonisolated extension L10n {
static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
// Use preferredLocalizations to get a language that is in the bundle and the user's preferred list of languages. // Use preferredLocalizations to get a language that is in the bundle and the user's preferred list of languages.
let languages = Bundle.overrideLocalizations ?? Bundle.app.preferredLocalizations let languages = Bundle.overrideLocalizations ?? Bundle.app.preferredLocalizations
+4 -4
View File
@@ -122,18 +122,18 @@ extension ClientProxyMock {
let joinedRoomIDs = configuration.overrides.joinedRoomIDs let joinedRoomIDs = configuration.overrides.joinedRoomIDs
switch room.joinRequestType { switch room.joinRequestType {
case .invite where !joinedRoomIDs.contains(room.id): case .invite where !joinedRoomIDs.contains(room.id):
let roomProxy = await InvitedRoomProxyMock(.init(id: room.id, name: room.name, isSpace: room.isSpace)) let roomProxy = InvitedRoomProxyMock(.init(id: room.id, name: room.name, isSpace: room.isSpace))
return .invited(roomProxy) return .invited(roomProxy)
case .knock where !joinedRoomIDs.contains(room.id): case .knock where !joinedRoomIDs.contains(room.id):
let roomProxy = await KnockedRoomProxyMock(.init(id: room.id, name: room.name)) let roomProxy = KnockedRoomProxyMock(.init(id: room.id, name: room.name))
return .knocked(roomProxy) return .knocked(roomProxy)
default: default:
let roomProxy = await JoinedRoomProxyMock(.init(id: room.id, name: room.name, isSpace: room.isSpace, members: configuration.defaultRoomMembers)) let roomProxy = JoinedRoomProxyMock(.init(id: room.id, name: room.name, isSpace: room.isSpace, members: configuration.defaultRoomMembers))
roomProxy.loadOrFetchEventDetailsForReturnValue = .success(TimelineEventSDKMock()) roomProxy.loadOrFetchEventDetailsForReturnValue = .success(TimelineEventSDKMock())
return .joined(roomProxy) return .joined(roomProxy)
} }
} else if let spaceServiceRoom = configuration.spaceServiceConfiguration.topLevelSpaces.first(where: { $0.id == identifier }) { } else if let spaceServiceRoom = configuration.spaceServiceConfiguration.topLevelSpaces.first(where: { $0.id == identifier }) {
let roomProxy = await JoinedRoomProxyMock(.init(id: spaceServiceRoom.id, name: spaceServiceRoom.name, isSpace: spaceServiceRoom.isSpace, members: configuration.defaultRoomMembers)) let roomProxy = JoinedRoomProxyMock(.init(id: spaceServiceRoom.id, name: spaceServiceRoom.name, isSpace: spaceServiceRoom.isSpace, members: configuration.defaultRoomMembers))
roomProxy.loadOrFetchEventDetailsForReturnValue = .success(TimelineEventSDKMock()) roomProxy.loadOrFetchEventDetailsForReturnValue = .success(TimelineEventSDKMock())
return .joined(roomProxy) return .joined(roomProxy)
} else { } else {
File diff suppressed because it is too large Load Diff
@@ -12,7 +12,7 @@ struct RoomLiveLocationServiceMockConfiguration {
var shares: [LiveLocationShare] = [] var shares: [LiveLocationShare] = []
} }
extension RoomLiveLocationServiceMock { @MainActor extension RoomLiveLocationServiceMock {
convenience init(_ configuration: RoomLiveLocationServiceMockConfiguration = .init()) { convenience init(_ configuration: RoomLiveLocationServiceMockConfiguration = .init()) {
self.init() self.init()
liveLocationsPublisher = CurrentValueSubject(configuration.shares).asCurrentValuePublisher() liveLocationsPublisher = CurrentValueSubject(configuration.shares).asCurrentValuePublisher()
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
struct RoomMemberProxyMockConfiguration { nonisolated struct RoomMemberProxyMockConfiguration {
var userID: String var userID: String
var displayName: String? var displayName: String?
var avatarURL: URL? var avatarURL: URL?
@@ -14,7 +14,7 @@ struct RoomNotificationSettingsProxyMockConfiguration {
var isDefault = true var isDefault = true
} }
extension RoomNotificationSettingsProxyMock { @MainActor extension RoomNotificationSettingsProxyMock {
convenience init(with configuration: RoomNotificationSettingsProxyMockConfiguration) { convenience init(with configuration: RoomNotificationSettingsProxyMockConfiguration) {
self.init() self.init()
@@ -8,7 +8,7 @@
import MatrixRustSDK import MatrixRustSDK
extension RoomPowerLevelsProxyMock { @MainActor extension RoomPowerLevelsProxyMock {
struct Configuration { struct Configuration {
var canUserSendMessage = true var canUserSendMessage = true
var canUserSendState = false var canUserSendState = false
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
extension RoomPreviewProxyMock { @MainActor extension RoomPreviewProxyMock {
struct Configuration { struct Configuration {
var roomID = "1" var roomID = "1"
var canonicalAlias = "#3-body-problem:matrix.org" var canonicalAlias = "#3-body-problem:matrix.org"
@@ -10,7 +10,7 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
import MatrixRustSDKMocks import MatrixRustSDKMocks
extension ClientSDKMock { nonisolated extension ClientSDKMock {
struct Configuration { struct Configuration {
// MARK: Authentication // MARK: Authentication
@@ -73,7 +73,7 @@ extension ClientSDKMock {
} }
} }
extension HomeserverLoginDetailsSDKMock { nonisolated extension HomeserverLoginDetailsSDKMock {
convenience init(configuration: ClientSDKMock.Configuration) { convenience init(configuration: ClientSDKMock.Configuration) {
self.init() self.init()
@@ -89,7 +89,7 @@ extension HomeserverLoginDetailsSDKMock {
} }
} }
extension OAuthAuthorizationDataSDKMock { nonisolated extension OAuthAuthorizationDataSDKMock {
convenience init(configuration: ClientSDKMock.Configuration) { convenience init(configuration: ClientSDKMock.Configuration) {
self.init() self.init()
@@ -11,7 +11,7 @@ import LoremSwiftum
import MatrixRustSDK import MatrixRustSDK
import MatrixRustSDKMocks import MatrixRustSDKMocks
struct EventTimelineItemSDKMockConfiguration { nonisolated struct EventTimelineItemSDKMockConfiguration {
var eventID: String = UUID().uuidString var eventID: String = UUID().uuidString
var sender = "" var sender = ""
var senderProfile: ProfileDetails? var senderProfile: ProfileDetails?
@@ -27,7 +27,7 @@ struct EventTimelineItemSDKMockConfiguration {
threadSummary: nil)) threadSummary: nil))
} }
extension EventTimelineItem { nonisolated extension EventTimelineItem {
init(configuration: EventTimelineItemSDKMockConfiguration) { init(configuration: EventTimelineItemSDKMockConfiguration) {
let lazyProvider = LazyTimelineItemProviderSDKMock() let lazyProvider = LazyTimelineItemProviderSDKMock()
lazyProvider.containsOnlyEmojisReturnValue = false lazyProvider.containsOnlyEmojisReturnValue = false
@@ -10,7 +10,7 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
import MatrixRustSDKMocks import MatrixRustSDKMocks
extension IdentityResetHandleSDKMock { nonisolated extension IdentityResetHandleSDKMock {
struct Configuration { } struct Configuration { }
convenience init(_ configuration: Configuration) { convenience init(_ configuration: Configuration) {
@@ -10,7 +10,7 @@ import Foundation
import MatrixRustSDK import MatrixRustSDK
import MatrixRustSDKMocks import MatrixRustSDKMocks
extension LeaveSpaceHandleSDKMock { nonisolated extension LeaveSpaceHandleSDKMock {
struct Configuration { struct Configuration {
var rooms: [LeaveSpaceRoom] = .mockRooms var rooms: [LeaveSpaceRoom] = .mockRooms
} }
@@ -22,7 +22,7 @@ extension LeaveSpaceHandleSDKMock {
} }
} }
extension [LeaveSpaceRoom] { nonisolated extension [LeaveSpaceRoom] {
static func mockRoomsWithSpace(spaceServiceRoom: SpaceServiceRoom, isLastOwner: Bool, areCreatorsPrivileged: Bool) -> [LeaveSpaceRoom] { static func mockRoomsWithSpace(spaceServiceRoom: SpaceServiceRoom, isLastOwner: Bool, areCreatorsPrivileged: Bool) -> [LeaveSpaceRoom] {
mockRooms + mockSingleSpace(spaceServiceRoom: spaceServiceRoom, isLastOwner: isLastOwner, areCreatorsPrivileged: areCreatorsPrivileged) mockRooms + mockSingleSpace(spaceServiceRoom: spaceServiceRoom, isLastOwner: isLastOwner, areCreatorsPrivileged: areCreatorsPrivileged)
} }
@@ -138,7 +138,7 @@ extension [LeaveSpaceRoom] {
} }
} }
private extension SpaceRoom { private nonisolated extension SpaceRoom {
init(id: String, init(id: String,
canonicalAlias: String? = nil, canonicalAlias: String? = nil,
name: String, name: String,
@@ -27,7 +27,7 @@ extension SessionVerificationControllerProxyMock {
mock.acknowledgeVerificationRequestDetailsReturnValue = .success(()) mock.acknowledgeVerificationRequestDetailsReturnValue = .success(())
mock.requestDeviceVerificationClosure = { [weak mock] in mock.requestDeviceVerificationClosure = { [weak mock] in
Task.detached { Task {
guard let mock else { return } guard let mock else { return }
try await Task.sleep(for: requestDelay) try await Task.sleep(for: requestDelay)
@@ -46,13 +46,13 @@ extension SessionVerificationControllerProxyMock {
} }
mock.startSasVerificationClosure = { [weak mock] in mock.startSasVerificationClosure = { [weak mock] in
Task.detached { Task {
guard let mock else { return } guard let mock else { return }
try await Task.sleep(for: requestDelay) try await Task.sleep(for: requestDelay)
mock.actions.send(.startedSasVerification) mock.actions.send(.startedSasVerification)
Task.detached { Task {
try await Task.sleep(for: requestDelay) try await Task.sleep(for: requestDelay)
mock.actions.send(.receivedVerificationData(emojis)) mock.actions.send(.receivedVerificationData(emojis))
} }
@@ -62,7 +62,7 @@ extension SessionVerificationControllerProxyMock {
} }
mock.approveVerificationClosure = { [weak mock] in mock.approveVerificationClosure = { [weak mock] in
Task.detached { Task {
guard let mock else { return } guard let mock else { return }
try await Task.sleep(for: requestDelay) try await Task.sleep(for: requestDelay)
@@ -73,7 +73,7 @@ extension SessionVerificationControllerProxyMock {
} }
mock.declineVerificationClosure = { [weak mock] in mock.declineVerificationClosure = { [weak mock] in
Task.detached { Task {
guard let mock else { return } guard let mock else { return }
try await Task.sleep(for: requestDelay) try await Task.sleep(for: requestDelay)
@@ -84,7 +84,7 @@ extension SessionVerificationControllerProxyMock {
} }
mock.cancelVerificationClosure = { [weak mock] in mock.cancelVerificationClosure = { [weak mock] in
Task.detached { Task {
guard let mock else { return } guard let mock else { return }
try await Task.sleep(for: requestDelay) try await Task.sleep(for: requestDelay)
@@ -14,7 +14,7 @@ import MatrixRustSDK
/// A ``TimelineItemProviderMock`` subclass that optionally listens for ``UITestsSignalling`` signals /// A ``TimelineItemProviderMock`` subclass that optionally listens for ``UITestsSignalling`` signals
/// to drive pagination and incoming message simulation. /// to drive pagination and incoming message simulation.
@MainActor @MainActor
class SignallingTimelineItemProviderMock: TimelineItemProviderMock { class SignallingTimelineItemProviderMock: TimelineItemProviderMock, @unchecked Sendable {
private let stateSubject: CurrentValueSubject<([TimelineItemProxy], TimelinePaginationState), Never> private let stateSubject: CurrentValueSubject<([TimelineItemProxy], TimelinePaginationState), Never>
/// An array of item arrays that will be prepended in order on each paginate signal. /// An array of item arrays that will be prepended in order on each paginate signal.
@@ -25,6 +25,10 @@ class SignallingTimelineItemProviderMock: TimelineItemProviderMock {
private var client: UITestsSignalling.Client? private var client: UITestsSignalling.Client?
private var signalCancellable: AnyCancellable? private var signalCancellable: AnyCancellable?
override nonisolated init() {
fatalError("Use init(itemProxies:paginationState:listenForSignals:) instead.")
}
init(itemProxies: [TimelineItemProxy] = [], init(itemProxies: [TimelineItemProxy] = [],
paginationState: TimelinePaginationState = .init(backward: .idle, forward: .endReached), paginationState: TimelinePaginationState = .init(backward: .idle, forward: .endReached),
listenForSignals: Bool = false) { listenForSignals: Bool = false) {
@@ -10,7 +10,7 @@ import Combine
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
extension SpaceRoomListProxyMock { @MainActor extension SpaceRoomListProxyMock {
class Configuration { class Configuration {
var spaceServiceRoom: SpaceServiceRoom var spaceServiceRoom: SpaceServiceRoom
var initialSpaceRooms: [SpaceServiceRoom] var initialSpaceRooms: [SpaceServiceRoom]
@@ -22,7 +22,7 @@ struct TimelineControllerMockConfiguration {
var paginationState: TimelinePaginationState = .initial var paginationState: TimelinePaginationState = .initial
} }
extension TimelineControllerMock { @MainActor extension TimelineControllerMock {
static var mediaGallery: TimelineControllerMock { static var mediaGallery: TimelineControllerMock {
TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in TimelineControllerMock(.init(timelineKind: .media(.mediaFilesScreen), timelineItems: (0..<5).reduce([]) { partialResult, _ in
partialResult + [TimelineFixtures.separator] + TimelineFixtures.mediaChunk partialResult + [TimelineFixtures.separator] + TimelineFixtures.mediaChunk
+1 -1
View File
@@ -13,7 +13,7 @@ struct UserSessionMockConfiguration {
var clientProxy: ClientProxyProtocol = ClientProxyMock(.init()) var clientProxy: ClientProxyProtocol = ClientProxyMock(.init())
} }
extension UserSessionMock { @MainActor extension UserSessionMock {
convenience init(_ configuration: UserSessionMockConfiguration) { convenience init(_ configuration: UserSessionMockConfiguration) {
self.init() self.init()
+2 -2
View File
@@ -29,11 +29,11 @@ enum Avatars {
} }
/// Value in pixels by using the scale of the main screen /// Value in pixels by using the scale of the main screen
var scaledValue: CGFloat { @MainActor var scaledValue: CGFloat {
value * UIScreen.main.scale value * UIScreen.main.scale
} }
var scaledSize: CGSize { @MainActor var scaledSize: CGSize {
CGSize(width: scaledValue, height: scaledValue) CGSize(width: scaledValue, height: scaledValue)
} }
} }
+2 -2
View File
@@ -8,11 +8,11 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum CallIntent: String, Codable, CaseIterable { nonisolated enum CallIntent: String, Codable, CaseIterable {
case video, audio case video, audio
} }
extension CallIntent { nonisolated extension CallIntent {
// periphery:ignore - Unused, but added to detect new cases when updating the SDK. // periphery:ignore - Unused, but added to detect new cases when updating the SDK.
init(rustCallIntent: MatrixRustSDK.RtcCallIntent) { init(rustCallIntent: MatrixRustSDK.RtcCallIntent) {
switch rustCallIntent { switch rustCallIntent {
@@ -12,7 +12,7 @@ import SwiftUI
/// on the same row (moving them both to a new row if necessary). /// on the same row (moving them both to a new row if necessary).
/// Each subview should be marked with the appropriate `ReactionLayoutItem` using the `reactionLayoutItem` modified /// Each subview should be marked with the appropriate `ReactionLayoutItem` using the `reactionLayoutItem` modified
/// so the layout can appropriately treat each type of item. /// so the layout can appropriately treat each type of item.
struct CollapsibleReactionLayout: Layout { nonisolated struct CollapsibleReactionLayout: Layout {
static let pointOffscreen = CGPoint(x: -10000, y: -10000) static let pointOffscreen = CGPoint(x: -10000, y: -10000)
/// The horizontal spacing between items /// The horizontal spacing between items
var itemSpacing: CGFloat = 0 var itemSpacing: CGFloat = 0
@@ -250,33 +250,33 @@ struct CollapsibleReactionLayout: Layout {
} }
} }
struct ReactionSubviews { nonisolated struct ReactionSubviews {
var reactions: [FlowLayoutSubview] var reactions: [FlowLayoutSubview]
var collapseButton: FlowLayoutSubview var collapseButton: FlowLayoutSubview
var addMoreButton: FlowLayoutSubview var addMoreButton: FlowLayoutSubview
} }
/// A protocol representing subviews so that we can inject mocks in unit tests. /// A protocol representing subviews so that we can inject mocks in unit tests.
protocol FlowLayoutSubviews: RandomAccessCollection where Element: FlowLayoutSubview, Index == Int, SubSequence == Self { } nonisolated protocol FlowLayoutSubviews: RandomAccessCollection where Element: FlowLayoutSubview, Index == Int, SubSequence == Self { }
extension LayoutSubviews: FlowLayoutSubviews { } nonisolated extension LayoutSubviews: FlowLayoutSubviews { }
/// A protocol representing a subview so that we can inject mocks in unit tests. /// A protocol representing a subview so that we can inject mocks in unit tests.
protocol FlowLayoutSubview { nonisolated protocol FlowLayoutSubview {
func sizeThatFits(_ proposal: ProposedViewSize) -> CGSize func sizeThatFits(_ proposal: ProposedViewSize) -> CGSize
func place(at position: CGPoint, anchor: UnitPoint, proposal: ProposedViewSize) func place(at position: CGPoint, anchor: UnitPoint, proposal: ProposedViewSize)
subscript<K>(key: K.Type) -> K.Value where K: LayoutValueKey { get } subscript<K>(key: K.Type) -> K.Value where K: LayoutValueKey { get }
} }
extension LayoutSubview: FlowLayoutSubview { } nonisolated extension LayoutSubview: FlowLayoutSubview { }
enum ReactionLayoutItem { nonisolated enum ReactionLayoutItem {
case reaction case reaction
case expandCollapse case expandCollapse
case addMore case addMore
} }
struct ReactionLayoutItemType: LayoutValueKey { nonisolated struct ReactionLayoutItemType: LayoutValueKey {
static let defaultValue: ReactionLayoutItem = .reaction static let defaultValue: ReactionLayoutItem = .reaction
} }
@@ -12,7 +12,7 @@ import Combine
/// The purpose of this type is to remove the possibility to send new values on the underlying subject. /// The purpose of this type is to remove the possibility to send new values on the underlying subject.
/// ///
/// `CurrentValueSubject` is documented as thread-safe but is not formally `Sendable`, hence `@unchecked`. /// `CurrentValueSubject` is documented as thread-safe but is not formally `Sendable`, hence `@unchecked`.
struct CurrentValuePublisher<Output, Failure: Error>: Publisher, @unchecked Sendable { nonisolated struct CurrentValuePublisher<Output, Failure: Error>: Publisher, @unchecked Sendable {
private let subject: CurrentValueSubject<Output, Failure> private let subject: CurrentValueSubject<Output, Failure>
init(_ subject: CurrentValueSubject<Output, Failure>) { init(_ subject: CurrentValueSubject<Output, Failure>) {
@@ -32,7 +32,7 @@ struct CurrentValuePublisher<Output, Failure: Error>: Publisher, @unchecked Send
} }
} }
extension CurrentValueSubject { nonisolated extension CurrentValueSubject {
func asCurrentValuePublisher() -> CurrentValuePublisher<Output, Failure> { func asCurrentValuePublisher() -> CurrentValuePublisher<Output, Failure> {
.init(self) .init(self)
} }
@@ -9,7 +9,7 @@
import SceneKit import SceneKit
import SwiftUI import SwiftUI
class EffectsScene: SCNScene { nonisolated class EffectsScene: SCNScene {
static let colors: [Color] = [ static let colors: [Color] = [
.blue, .blue,
.red, .red,
@@ -54,7 +54,7 @@ class EffectsScene: SCNScene {
} }
} }
private extension Color { private nonisolated extension Color {
/// The color's components as an array of floats in the extended linear sRGB colorspace. /// The color's components as an array of floats in the extended linear sRGB colorspace.
/// ///
/// SceneKit works in a colorspace with a linear gamma, which is why this conversion is necessary. /// SceneKit works in a colorspace with a linear gamma, which is why this conversion is necessary.
@@ -11,13 +11,13 @@
import AVKit import AVKit
import Foundation import Foundation
enum AVMetadataBinaryValueError: Error { nonisolated enum AVMetadataBinaryValueError: Error {
case unhandledQRSegmentMode(UInt8) case unhandledQRSegmentMode(UInt8)
case bitError(BitError) case bitError(BitError)
case unknown(Error) case unknown(Error)
} }
extension AVMetadataMachineReadableCodeObject { nonisolated extension AVMetadataMachineReadableCodeObject {
var qrBinaryValue: Data? { var qrBinaryValue: Data? {
get throws(AVMetadataBinaryValueError) { get throws(AVMetadataBinaryValueError) {
guard type == .qr else { return nil } guard type == .qr else { return nil }
@@ -66,11 +66,11 @@ enum BitError: Error {
case unhandledAlphanumericCharacter(UInt8) case unhandledAlphanumericCharacter(UInt8)
} }
private struct Bit { private nonisolated struct Bit {
let value: UInt8 let value: UInt8
} }
private extension [Bit] { private nonisolated extension [Bit] {
mutating func takeBits(_ count: Int) throws(BitError) -> UInt8 { mutating func takeBits(_ count: Int) throws(BitError) -> UInt8 {
guard count <= 8 else { throw .moreThan8BitsTaken } guard count <= 8 else { throw .moreThan8BitsTaken }
@@ -163,7 +163,7 @@ private extension [Bit] {
} }
} }
private extension Data { private nonisolated extension Data {
func bits() -> [Bit] { func bits() -> [Bit] {
var result = [Bit]() var result = [Bit]()
forEach { (byte: UInt8) in forEach { (byte: UInt8) in
@@ -173,7 +173,7 @@ private extension Data {
} }
} }
private extension UInt8 { private nonisolated extension UInt8 {
func bits() -> [Bit] { func bits() -> [Bit] {
var bits: [Bit] = [] var bits: [Bit] = []
for i in 0..<8 { for i in 0..<8 {
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension Array { nonisolated extension Array {
func groupBy(_ isGroupable: (Element) -> Bool) -> [[Element]] { func groupBy(_ isGroupable: (Element) -> Bool) -> [[Element]] {
var newItems = [[Element]]() var newItems = [[Element]]()
@@ -60,7 +60,7 @@ extension Array {
} }
} }
extension Array where Element == RoomTimelineItemProtocol { nonisolated extension Array where Element == RoomTimelineItemProtocol {
func firstUsingStableID(_ id: TimelineItemIdentifier) -> Element? { func firstUsingStableID(_ id: TimelineItemIdentifier) -> Element? {
first { $0.id.uniqueID == id.uniqueID } first { $0.id.uniqueID == id.uniqueID }
} }
@@ -6,8 +6,9 @@
// Please see LICENSE files in the repository root for full details. // Please see LICENSE files in the repository root for full details.
// //
extension AsyncSequence { nonisolated extension AsyncSequence {
func first() async rethrows -> Self.Element? { func first() async rethrows -> Self.Element? {
try await first { _ in true } var iterator = makeAsyncIterator()
return try await iterator.next(isolation: #isolation)
} }
} }
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension AttributedString { nonisolated extension AttributedString {
/// faster than doing `String(characters)`: https://forums.swift.org/t/attributedstring-to-string/61667 /// faster than doing `String(characters)`: https://forums.swift.org/t/attributedstring-to-string/61667
var string: String { var string: String {
String(characters[...]) String(characters[...])
@@ -9,7 +9,7 @@
import Foundation import Foundation
import Synchronization import Synchronization
public extension Bundle { public nonisolated extension Bundle {
/// The top-level bundle that contains the entire app. /// The top-level bundle that contains the entire app.
static var app: Bundle { static var app: Bundle {
var bundle = Bundle.main var bundle = Bundle.main
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension CharacterSet { nonisolated extension CharacterSet {
private static let urlAllowedSets: [CharacterSet] = [ private static let urlAllowedSets: [CharacterSet] = [
.urlUserAllowed, .urlUserAllowed,
.urlPasswordAllowed, .urlPasswordAllowed,
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
extension ClientBuilder { nonisolated extension ClientBuilder {
/// A helper method that applies the common builder modifiers needed for the app. /// A helper method that applies the common builder modifiers needed for the app.
static func baseBuilder(setupEncryption: Bool = true, static func baseBuilder(setupEncryption: Bool = true,
httpProxy: String? = nil, httpProxy: String? = nil,
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension Collection { nonisolated extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil. /// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript(safe index: Index) -> Element? { subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil indices.contains(index) ? self[index] : nil
+4 -4
View File
@@ -10,7 +10,7 @@ import Foundation
extension Date { extension Date {
/// The date formatted with the minimal necessary units given how long ago it occurred. /// The date formatted with the minimal necessary units given how long ago it occurred.
func formattedMinimal() -> String { nonisolated func formattedMinimal() -> String {
let calendar = Calendar.current let calendar = Calendar.current
if calendar.isDateInToday(self) { if calendar.isDateInToday(self) {
@@ -35,7 +35,7 @@ extension Date {
} }
/// The date of an expiration formatted with the minimal necessary units given how long in the future it is. /// The date of an expiration formatted with the minimal necessary units given how long in the future it is.
func formattedExpiration() -> String { nonisolated func formattedExpiration() -> String {
let calendar = Calendar.current let calendar = Calendar.current
let now = Date.now let now = Date.now
@@ -75,12 +75,12 @@ extension Date {
} }
/// The date formatted as just the time, for use in timeline items specifically. /// The date formatted as just the time, for use in timeline items specifically.
func formattedTime() -> String { nonisolated func formattedTime() -> String {
formatted(date: .omitted, time: .shortened) formatted(date: .omitted, time: .shortened)
} }
/// A fixed date used for mocks, previews etc. /// A fixed date used for mocks, previews etc.
static var mock: Date { nonisolated static var mock: Date {
DateComponents(calendar: .current, year: 2007, month: 1, day: 9, hour: 9, minute: 41).date ?? .now DateComponents(calendar: .current, year: 2007, month: 1, day: 9, hour: 9, minute: 41).date ?? .now
} }
} }
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension Dictionary { nonisolated extension Dictionary {
var jsonString: String? { var jsonString: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self, guard let data = try? JSONSerialization.data(withJSONObject: self,
options: [.fragmentsAllowed, .sortedKeys]) else { options: [.fragmentsAllowed, .sortedKeys]) else {
@@ -12,7 +12,7 @@ enum FileManagerError: Error {
case invalidFileSize case invalidFileSize
} }
extension FileManager { nonisolated extension FileManager {
func directoryExists(at url: URL) -> Bool { func directoryExists(at url: URL) -> Bool {
var isDirectory: ObjCBool = false var isDirectory: ObjCBool = false
guard fileExists(atPath: url.path(percentEncoded: false), isDirectory: &isDirectory) else { guard fileExists(atPath: url.path(percentEncoded: false), isDirectory: &isDirectory) else {
@@ -10,7 +10,6 @@ import Compound
import HTMLParser import HTMLParser
import SwiftUI import SwiftUI
@MainActor
extension HTMLParserStyle { extension HTMLParserStyle {
static let elementX = HTMLParserStyle(textColor: UIColor.label, static let elementX = HTMLParserStyle(textColor: UIColor.label,
linkColor: UIColor.link, linkColor: UIColor.link,
@@ -9,7 +9,7 @@
import Foundation import Foundation
import Kingfisher import Kingfisher
extension ImageCache { nonisolated extension ImageCache {
static var onlyInMemory: ImageCache { static var onlyInMemory: ImageCache {
let result = ImageCache.default let result = ImageCache.default
result.memoryStorage.config.keepWhenEnteringBackground = true result.memoryStorage.config.keepWhenEnteringBackground = true
@@ -9,7 +9,7 @@
import Foundation import Foundation
/// NSRegularExpressions work internally on NSStrings, we need to be careful how we build the ranges for extended grapheme clusters https://stackoverflow.com/a/27880748/730924 /// NSRegularExpressions work internally on NSStrings, we need to be careful how we build the ranges for extended grapheme clusters https://stackoverflow.com/a/27880748/730924
extension NSRegularExpression { nonisolated extension NSRegularExpression {
func matches(in string: String, options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] { func matches(in string: String, options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
matches(in: string, options: options, range: .init(location: 0, length: (string as NSString).length)) matches(in: string, options: options, range: .init(location: 0, length: (string as NSString).length))
} }
@@ -14,26 +14,34 @@ extension Observable {
/// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0475-observed.md /// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0475-observed.md
/// ///
/// - Parameter property: The key path to the property you would like to observe. /// - Parameter property: The key path to the property you would like to observe.
func observe<Value>(_ property: KeyPath<Self, Value>) -> AsyncStream<Value> { @MainActor func observe<Value: Sendable>(_ property: KeyPath<Self, Value>) -> AsyncStream<Value> {
AsyncStream { continuation in let (stream, continuation) = AsyncStream<Value>.makeStream()
let isActive = Mutex(true) let (changeSignals, changeSignaller) = AsyncStream<Void>.makeStream()
@Sendable func observe() { // Read and yield the initial value synchronously at subscription time,
// otherwise observers miss changes made right after creating the stream.
let initialValue = withObservationTracking {
self[keyPath: property]
} onChange: {
changeSignaller.yield()
}
continuation.yield(initialValue)
let task = Task { @MainActor in
// The signal is sent on willSet, but as this task runs on the next main
// executor job the new value will have been set by the time we read it.
for await _ in changeSignals {
let value = withObservationTracking { let value = withObservationTracking {
self[keyPath: property] self[keyPath: property]
} onChange: { } onChange: {
// Handle the update on the next run loop as this is willSet not didSet. changeSignaller.yield()
DispatchQueue.main.async {
guard isActive.withLock({ $0 }) else { return }
observe()
}
} }
continuation.yield(value) continuation.yield(value)
} }
continuation.onTermination = { _ in isActive.withLock { $0 = false } }
observe()
} }
continuation.onTermination = { _ in task.cancel() }
return stream
} }
} }
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension ProcessInfo { nonisolated extension ProcessInfo {
/// Flag indicating whether the app is running the unit tests. /// Flag indicating whether the app is running the unit tests.
static var isRunningUnitTests: Bool { static var isRunningUnitTests: Bool {
#if DEBUG #if DEBUG
@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
extension ProposedViewSize: @retroactive Hashable { nonisolated extension ProposedViewSize: @retroactive Hashable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(width) hasher.combine(width)
hasher.combine(height) hasher.combine(height)
@@ -46,8 +46,8 @@ extension Published.Publisher {
var iterator = values.makeAsyncIterator() var iterator = values.makeAsyncIterator()
// skips the publisher's current value // skips the publisher's current value
_ = await iterator.next() _ = await iterator.next(isolation: #isolation)
return await iterator.next() return await iterator.next(isolation: #isolation)
} }
} }
} }
@@ -9,7 +9,7 @@
import SwiftUI import SwiftUI
import UniformTypeIdentifiers import UniformTypeIdentifiers
extension String { nonisolated extension String {
/// Returns the string as an `AttributedString` with the specified character tinted in a different color. /// Returns the string as an `AttributedString` with the specified character tinted in a different color.
/// - Parameters: /// - Parameters:
/// - character: The character to be tinted. /// - character: The character to be tinted.
@@ -41,7 +41,7 @@ extension String {
} }
} }
extension String { nonisolated extension String {
func ellipsize(length: Int) -> String { func ellipsize(length: Int) -> String {
guard count > length else { guard count > length else {
return self return self
@@ -50,7 +50,7 @@ extension String {
} }
} }
extension String { nonisolated extension String {
func replacingHtmlBreaksOccurrences() -> String { func replacingHtmlBreaksOccurrences() -> String {
var result = self var result = self
let pattern = #"</p>(\n+)<p>"# let pattern = #"</p>(\n+)<p>"#
@@ -74,14 +74,14 @@ extension String {
} }
} }
extension String { nonisolated extension String {
/// detects if the string is empty or contains only whitespaces and newlines /// detects if the string is empty or contains only whitespaces and newlines
var isBlank: Bool { var isBlank: Bool {
trimmingCharacters(in: .whitespacesAndNewlines).isEmpty trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
} }
} }
extension String { nonisolated extension String {
static func makeCanonicalAlias(aliasLocalPart: Self?, serverName: Self?) -> Self? { static func makeCanonicalAlias(aliasLocalPart: Self?, serverName: Self?) -> Self? {
guard let aliasLocalPart, !aliasLocalPart.isEmpty, guard let aliasLocalPart, !aliasLocalPart.isEmpty,
let serverName, !serverName.isEmpty else { let serverName, !serverName.isEmpty else {
@@ -91,7 +91,7 @@ extension String {
} }
} }
extension String { nonisolated extension String {
var validatedFileExtension: String { var validatedFileExtension: String {
let fileExtension = (self as NSString).pathExtension let fileExtension = (self as NSString).pathExtension
guard !fileExtension.isEmpty else { guard !fileExtension.isEmpty else {
@@ -101,7 +101,7 @@ extension String {
} }
} }
extension String { nonisolated extension String {
/// Whether the first character with a strong BiDi direction is right-to-left. /// Whether the first character with a strong BiDi direction is right-to-left.
/// Mirrors the Unicode BiDi "first strong" rule used by TextKit to resolve /// Mirrors the Unicode BiDi "first strong" rule used by TextKit to resolve
/// paragraph direction when `baseWritingDirection` is `.natural`. /// paragraph direction when `baseWritingDirection` is `.natural`.
@@ -120,7 +120,7 @@ extension String {
} }
} }
extension String { nonisolated extension String {
/// To be used if the string is actually a URL /// To be used if the string is actually a URL
var asSanitizedLink: String { var asSanitizedLink: String {
var link = self var link = self
@@ -9,7 +9,7 @@
import Foundation import Foundation
import UserNotifications import UserNotifications
extension UNNotificationContent { nonisolated extension UNNotificationContent {
@objc var receiverID: String? { @objc var receiverID: String? {
userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] as? String userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] as? String
} }
@@ -35,7 +35,7 @@ extension UNNotificationContent {
} }
} }
extension UNMutableNotificationContent { nonisolated extension UNMutableNotificationContent {
override var receiverID: String? { override var receiverID: String? {
get { get {
userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] as? String userInfo[NotificationConstants.UserInfoKey.receiverIdentifier] as? String
+12 -12
View File
@@ -10,7 +10,7 @@ import Foundation
// MARK: - Custom URLs // MARK: - Custom URLs
extension URL { nonisolated extension URL {
/// The URL of the primary app group container. /// The URL of the primary app group container.
static var appGroupContainerDirectory: URL { static var appGroupContainerDirectory: URL {
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: InfoPlistReader.main.appGroupIdentifier) else { guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: InfoPlistReader.main.appGroupIdentifier) else {
@@ -135,34 +135,34 @@ extension URL {
// MARK: Mocks // MARK: Mocks
static var mockMXCAudio: URL { nonisolated static var mockMXCAudio: URL {
"mxc://matrix.org/1234567890AuDiO" "mxc://matrix.org/1234567890AuDiO"
} }
static var mockMXCFile: URL { nonisolated static var mockMXCFile: URL {
"mxc://matrix.org/1234567890FiLe" "mxc://matrix.org/1234567890FiLe"
} }
static var mockMXCImage: URL { nonisolated static var mockMXCImage: URL {
"mxc://matrix.org/1234567890ImAgE" "mxc://matrix.org/1234567890ImAgE"
} }
static var mockMXCVideo: URL { nonisolated static var mockMXCVideo: URL {
"mxc://matrix.org/1234567890ViDeO" "mxc://matrix.org/1234567890ViDeO"
} }
static var mockMXCAvatar: URL { nonisolated static var mockMXCAvatar: URL {
"mxc://matrix.org/1234567890AvAtAr" "mxc://matrix.org/1234567890AvAtAr"
} }
static var mockMXCUserAvatar: URL { nonisolated static var mockMXCUserAvatar: URL {
"mxc://matrix.org/1234567890AvAtArUsEr" "mxc://matrix.org/1234567890AvAtArUsEr"
} }
} }
// MARK: - Helpers // MARK: - Helpers
extension URL: @retroactive ExpressibleByStringLiteral { nonisolated extension URL: @retroactive ExpressibleByStringLiteral {
public init(stringLiteral value: StaticString) { public init(stringLiteral value: StaticString) {
guard let url = URL(string: "\(value)") else { guard let url = URL(string: "\(value)") else {
fatalError("The static string used to create this URL is invalid") fatalError("The static string used to create this URL is invalid")
@@ -177,7 +177,7 @@ extension URL: @retroactive ExpressibleByStringLiteral {
} }
} }
extension String { nonisolated extension String {
/// Assumes that the string is a URL and sanitises it for use as the name of a directory. /// Assumes that the string is a URL and sanitises it for use as the name of a directory.
func asURLDirectoryName() -> String { func asURLDirectoryName() -> String {
replacingOccurrences(of: "https://", with: "") replacingOccurrences(of: "https://", with: "")
@@ -188,7 +188,7 @@ extension String {
// MARK: - Phishing Confirmation URL // MARK: - Phishing Confirmation URL
extension URL { nonisolated extension URL {
static let confirmationScheme = "confirm" static let confirmationScheme = "confirm"
var requiresConfirmation: Bool { var requiresConfirmation: Bool {
@@ -204,7 +204,7 @@ extension URL {
} }
} }
struct ConfirmURLParameters { nonisolated struct ConfirmURLParameters {
static let internalURLKey = "internalURL" static let internalURLKey = "internalURL"
static let displayStringKey = "displayString" static let displayStringKey = "displayString"
@@ -217,7 +217,7 @@ struct ConfirmURLParameters {
} }
} }
extension ConfirmURLParameters { nonisolated extension ConfirmURLParameters {
init?(queryItems: [URLQueryItem]) { init?(queryItems: [URLQueryItem]) {
guard let internalURLString = queryItems.first(where: { $0.name == Self.internalURLKey })?.value, guard let internalURLString = queryItems.first(where: { $0.name == Self.internalURLKey })?.value,
let internalURL = URL(string: internalURLString), let internalURL = URL(string: internalURLString),
@@ -8,7 +8,7 @@
import Foundation import Foundation
extension URLSession { nonisolated extension URLSession {
/// The same as `data(for:delegate:)` but with an additional immediate retry if the first attempt fails. /// The same as `data(for:delegate:)` but with an additional immediate retry if the first attempt fails.
func dataWithRetry(for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse) { func dataWithRetry(for request: URLRequest, delegate: URLSessionTaskDelegate? = nil) async throws -> (Data, URLResponse) {
if let firstTryResult = try? await data(for: request, delegate: delegate) { if let firstTryResult = try? await data(for: request, delegate: delegate) {
@@ -8,7 +8,7 @@
import UniformTypeIdentifiers import UniformTypeIdentifiers
extension UTType { nonisolated extension UTType {
/// Creates a type based on an optional mime type, falling back to a filename when this type is missing or unknown. /// Creates a type based on an optional mime type, falling back to a filename when this type is missing or unknown.
init?(mimeType: String?, fallbackFilename: String) { init?(mimeType: String?, fallbackFilename: String) {
guard let mimeType, let type = UTType(mimeType: mimeType) else { guard let mimeType, let type = UTType(mimeType: mimeType) else {
@@ -13,7 +13,7 @@ import SwiftSoup
import Synchronization import Synchronization
import UIKit import UIKit
protocol MentionBuilderProtocol { nonisolated protocol MentionBuilderProtocol: Sendable {
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?) func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String, userDisplayName: String?)
func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String) func handleRoomIDMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomID: String)
func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String, roomDisplayName: String?) func handleRoomAliasMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, roomAlias: String, roomDisplayName: String?)
@@ -22,7 +22,7 @@ protocol MentionBuilderProtocol {
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange) func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange)
} }
extension NSAttributedString.Key { nonisolated extension NSAttributedString.Key {
static let MatrixBlockquote: NSAttributedString.Key = .init(rawValue: BlockquoteAttribute.name) static let MatrixBlockquote: NSAttributedString.Key = .init(rawValue: BlockquoteAttribute.name)
static let MatrixUserID: NSAttributedString.Key = .init(rawValue: UserIDAttribute.name) static let MatrixUserID: NSAttributedString.Key = .init(rawValue: UserIDAttribute.name)
static let MatrixUserDisplayName: NSAttributedString.Key = .init(rawValue: UserDisplayNameAttribute.name) static let MatrixUserDisplayName: NSAttributedString.Key = .init(rawValue: UserDisplayNameAttribute.name)
@@ -36,7 +36,7 @@ extension NSAttributedString.Key {
static let InlineCode: NSAttributedString.Key = .init(rawValue: InlineCodeAttribute.name) static let InlineCode: NSAttributedString.Key = .init(rawValue: InlineCodeAttribute.name)
} }
struct AttributedStringBuilder: AttributedStringBuilderProtocol { nonisolated struct AttributedStringBuilder: AttributedStringBuilderProtocol {
private static let defaultKey = "default" private static let defaultKey = "default"
private let cacheKey: String private let cacheKey: String
@@ -468,7 +468,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
} }
} }
private struct TextParsingMatch { private nonisolated struct TextParsingMatch {
enum MatchType { enum MatchType {
case userID(identifier: String) case userID(identifier: String)
case roomAlias(alias: String) case roomAlias(alias: String)
@@ -492,7 +492,7 @@ private struct TextParsingMatch {
} }
} }
private extension NSMutableAttributedString { private nonisolated extension NSMutableAttributedString {
func setFontPreservingSymbolicTraits(_ newFont: UIFont) { func setFontPreservingSymbolicTraits(_ newFont: UIFont) {
enumerateAttribute(.font, in: NSRange(location: 0, length: length)) { value, range, _ in enumerateAttribute(.font, in: NSRange(location: 0, length: length)) { value, range, _ in
if let oldFont = value as? UIFont { if let oldFont = value as? UIFont {
@@ -512,7 +512,7 @@ private extension NSMutableAttributedString {
} }
} }
private extension NSString { private nonisolated extension NSString {
func hasSuffixCharacter(from characterSet: CharacterSet) -> Bool { func hasSuffixCharacter(from characterSet: CharacterSet) -> Bool {
if length == 0 { if length == 0 {
return false return false
@@ -8,7 +8,7 @@
import Foundation import Foundation
struct AttributedStringBuilderComponent: Hashable, Identifiable { nonisolated struct AttributedStringBuilderComponent: Hashable, Identifiable {
enum ComponentType { enum ComponentType {
case plainText case plainText
case blockquote case blockquote
@@ -21,7 +21,7 @@ struct AttributedStringBuilderComponent: Hashable, Identifiable {
let type: ComponentType let type: ComponentType
} }
protocol AttributedStringBuilderProtocol { nonisolated protocol AttributedStringBuilderProtocol: Sendable {
func fromPlain(_ string: String?) -> AttributedString? func fromPlain(_ string: String?) -> AttributedString?
func fromHTML(_ htmlString: String?) -> AttributedString? func fromHTML(_ htmlString: String?) -> AttributedString?
@@ -74,7 +74,7 @@ enum InlineCodeAttribute: AttributedStringKey {
} }
// periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly // periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly
extension AttributeScopes { nonisolated extension AttributeScopes {
struct ElementXAttributes: AttributeScope { struct ElementXAttributes: AttributeScope {
let blockquote: BlockquoteAttribute let blockquote: BlockquoteAttribute
@@ -101,7 +101,7 @@ extension AttributeScopes {
} }
// periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly // periphery: ignore - required to make NSAttributedString to AttributedString conversion even if not used directly
extension AttributeDynamicLookup { nonisolated extension AttributeDynamicLookup {
subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<AttributeScopes.ElementXAttributes, T>) -> T { subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<AttributeScopes.ElementXAttributes, T>) -> T {
self[T.self] self[T.self]
} }
@@ -6,7 +6,7 @@
// Please see LICENSE files in the repository root for full details. // Please see LICENSE files in the repository root for full details.
// //
enum HTMLFixtures: String, CaseIterable { nonisolated enum HTMLFixtures: String, CaseIterable {
case plainText case plainText
case headers case headers
case paragraphs case paragraphs
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum PhishingDetector { nonisolated enum PhishingDetector {
static func isPhishingAttempt(displayString: String, internalURL: URL) -> Bool { static func isPhishingAttempt(displayString: String, internalURL: URL) -> Bool {
// Some phishing attempts can be hidden by using the unicode character "﹒" instead of "." // Some phishing attempts can be hidden by using the unicode character "﹒" instead of "."
let disambiguatedDisplayString = displayString.replacingOccurrences(of: "﹒", with: ".") let disambiguatedDisplayString = displayString.replacingOccurrences(of: "﹒", with: ".")
+1 -1
View File
@@ -8,7 +8,7 @@
import Foundation import Foundation
struct InfoPlistReader { nonisolated struct InfoPlistReader {
private enum Keys { private enum Keys {
static let appGroupIdentifier = "appGroupIdentifier" static let appGroupIdentifier = "appGroupIdentifier"
static let baseBundleIdentifier = "baseBundleIdentifier" static let baseBundleIdentifier = "baseBundleIdentifier"
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum ExtensionLogger { nonisolated enum ExtensionLogger {
/// Memory formatter, uses exact 2 fraction digits and no grouping /// Memory formatter, uses exact 2 fraction digits and no grouping
private static var numberFormatter: NumberFormatter { private static var numberFormatter: NumberFormatter {
let formatter = NumberFormatter() let formatter = NumberFormatter()
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum LogLevel: String, Codable, Hashable { nonisolated enum LogLevel: String, Codable, Hashable {
case error, warn, info, debug, trace case error, warn, info, debug, trace
var title: String { var title: String {
+1 -1
View File
@@ -12,7 +12,7 @@ import MatrixRustSDK
/// Logging utility that provies multiple logging levels as well as file output and rolling. /// Logging utility that provies multiple logging levels as well as file output and rolling.
/// Its purpose is to provide a common entry for customizing logging and should be used throughout the code. /// Its purpose is to provide a common entry for customizing logging and should be used throughout the code.
enum MXLog { nonisolated enum MXLog {
private nonisolated(unsafe) static var rootSpan: Span! private nonisolated(unsafe) static var rootSpan: Span!
private nonisolated(unsafe) static var currentTarget: String! private nonisolated(unsafe) static var currentTarget: String!
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum TraceLogPack: Codable, CaseIterable { nonisolated enum TraceLogPack: Codable, CaseIterable {
case eventCache, sendQueue, timeline, notificationClient, syncProfiling, latestEvents case eventCache, sendQueue, timeline, notificationClient, syncProfiling, latestEvents
var title: String { var title: String {
@@ -24,7 +24,7 @@ enum TraceLogPack: Codable, CaseIterable {
} }
} }
extension TraceLogPack { nonisolated extension TraceLogPack {
// periphery:ignore - Unused, but added to detect new cases when updating the SDK. // periphery:ignore - Unused, but added to detect new cases when updating the SDK.
init(rustLogPack: MatrixRustSDK.TraceLogPacks) { init(rustLogPack: MatrixRustSDK.TraceLogPacks) {
switch rustLogPack { switch rustLogPack {
+1 -1
View File
@@ -9,7 +9,7 @@
import Foundation import Foundation
import MatrixRustSDK import MatrixRustSDK
enum Tracing { nonisolated enum Tracing {
/// The directory that stores all of the log files. /// The directory that stores all of the log files.
static var logsDirectory: URL { static var logsDirectory: URL {
if ProcessInfo.isRunningIntegrationTests { if ProcessInfo.isRunningIntegrationTests {
@@ -233,7 +233,10 @@ extension MapLibreMapView {
// Mirror every animated coordinate update to keep the camera as smooth as the marker. // Mirror every animated coordinate update to keep the camera as smooth as the marker.
followedAnnotationObservation = annotation.observe(\.coordinate) { [weak mapView] annotation, _ in followedAnnotationObservation = annotation.observe(\.coordinate) { [weak mapView] annotation, _ in
mapView?.setCenter(annotation.coordinate, animated: false) // The coordinate is only ever updated by SwiftUI so KVO fires on the main actor.
MainActor.assumeIsolated {
mapView?.setCenter(annotation.coordinate, animated: false)
}
} }
} }
} }
@@ -303,7 +306,7 @@ extension MapLibreMapView {
// is an option set and gestures can come combined with other reasons, so check for // is an option set and gestures can come combined with other reasons, so check for
// containment instead of an exact match. // containment instead of an exact match.
let centerChangingGestures: MLNCameraChangeReason = [.gesturePan, .gesturePinch, .gestureRotate] let centerChangingGestures: MLNCameraChangeReason = [.gesturePan, .gesturePinch, .gestureRotate]
if !reason.intersection(centerChangingGestures).isEmpty { if !reason.isDisjoint(with: centerChangingGestures) {
// Stop following immediately, the camera would fight the gesture otherwise. // Stop following immediately, the camera would fight the gesture otherwise.
stopFollowingMarker() stopFollowingMarker()
mapLibreView.userDidPan?() mapLibreView.userDidPan?()

Some files were not shown because too many files have changed in this diff Show More