mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Compound - Swift 6.2 and Main actor isolation (#5109)
* compound is now using 6.2 and main actor isolation + some tweaks to make it build tests and EX * better handling of the nonisolated context for CompoundUIColors * improving docs * fix comment * remove unused Sendable conformance
This commit is contained in:
@@ -10,11 +10,11 @@ import SwiftUI
|
||||
|
||||
extension View {
|
||||
func progressMask(progress: CGFloat,
|
||||
trackColor: Color = .compound.iconSecondary,
|
||||
backgroundTrackColor: Color = .compound.iconQuaternary) -> some View {
|
||||
trackColor: @autoclosure @MainActor () -> Color = .compound.iconSecondary,
|
||||
backgroundTrackColor: @autoclosure @MainActor () -> Color = .compound.iconQuaternary) -> some View {
|
||||
modifier(ProgressMaskModifier(progress: progress,
|
||||
trackColor: trackColor,
|
||||
backgroundTrackColor: backgroundTrackColor))
|
||||
trackColor: trackColor(),
|
||||
backgroundTrackColor: backgroundTrackColor()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import Compound
|
||||
import SwiftUI
|
||||
import SwiftUIIntrospect
|
||||
|
||||
@MainActor
|
||||
extension TextFieldStyle where Self == ElementTextFieldStyle {
|
||||
static func element(labelText: String? = nil,
|
||||
footerText: String? = nil,
|
||||
@@ -34,6 +35,7 @@ extension TextFieldStyle where Self == ElementTextFieldStyle {
|
||||
}
|
||||
|
||||
/// The text field style used in authentication screens.
|
||||
@MainActor
|
||||
struct ElementTextFieldStyle: @MainActor TextFieldStyle {
|
||||
enum State {
|
||||
case success
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct RoomInviterDetails: Equatable {
|
||||
let id: String
|
||||
let displayName: String?
|
||||
|
||||
@@ -9,6 +9,7 @@ import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct ToolbarButton: View {
|
||||
@MainActor
|
||||
enum Role {
|
||||
static let cancel = Role.cancel(title: L10n.actionCancel)
|
||||
static let done = Role.confirm(title: L10n.actionDone)
|
||||
|
||||
@@ -128,6 +128,7 @@ struct RoomMembersListScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private enum MembersSection {
|
||||
case joined
|
||||
case invited
|
||||
|
||||
@@ -38,6 +38,7 @@ struct FormattingToolbar: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension FormatItem {
|
||||
var foregroundColor: Color {
|
||||
switch state {
|
||||
|
||||
@@ -109,6 +109,7 @@ enum RoomScreenFooterViewDetails {
|
||||
case verificationViolation(member: RoomMemberProxyProtocol, learnMoreURL: URL)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum PinnedEventsBannerState: Equatable {
|
||||
case loading(numbersOfEvents: Int)
|
||||
case loaded(state: PinnedEventsState)
|
||||
@@ -213,6 +214,7 @@ enum PinnedEventsBannerState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct PinnedEventsState: Equatable {
|
||||
var pinnedEventContents: OrderedDictionary<String, AttributedString> = [:] {
|
||||
didSet {
|
||||
|
||||
@@ -243,6 +243,7 @@ private struct VerifiedUserSendFailureView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension EncryptionAuthenticity {
|
||||
var foregroundStyle: SwiftUI.Color {
|
||||
switch color {
|
||||
|
||||
@@ -16,10 +16,10 @@ extension View {
|
||||
/// - color: self explanatory, defaults to subtle secondary
|
||||
func bubbleBackground(isOutgoing: Bool = true,
|
||||
insets: EdgeInsets = .init(top: 8, leading: 12, bottom: 8, trailing: 12),
|
||||
color: Color? = .compound.bgSubtleSecondary) -> some View {
|
||||
color: @autoclosure @MainActor () -> Color? = .compound.bgSubtleSecondary) -> some View {
|
||||
modifier(TimelineItemBubbleBackgroundModifier(isOutgoing: isOutgoing,
|
||||
insets: insets,
|
||||
color: color))
|
||||
color: color()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,6 +238,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension EventBasedTimelineItemProtocol {
|
||||
var bubbleBackgroundColor: Color? {
|
||||
let defaultColor: Color = isOutgoing ? .compound._bgBubbleOutgoing : .compound._bgBubbleIncoming
|
||||
|
||||
@@ -112,6 +112,7 @@ private struct TimelineItemSendInfoLabel: View {
|
||||
}
|
||||
|
||||
/// All the data needed to render a timeline item's send info label.
|
||||
@MainActor
|
||||
private struct TimelineItemSendInfo {
|
||||
enum Status {
|
||||
case sendingFailed
|
||||
@@ -185,6 +186,7 @@ private extension TimelineItemSendInfo {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private extension EncryptionAuthenticity {
|
||||
var foregroundStyle: SwiftUI.Color {
|
||||
switch color {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@testable import ElementX
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
class PinnedEventsBannerStateTests: XCTestCase {
|
||||
func testEmpty() {
|
||||
var state = PinnedEventsBannerState.loading(numbersOfEvents: 0)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version: 5.9
|
||||
// swift-tools-version: 6.2
|
||||
|
||||
import PackageDescription
|
||||
|
||||
@@ -22,6 +22,9 @@ let package = Package(
|
||||
.product(name: "CompoundDesignTokens", package: "compound-design-tokens"),
|
||||
.product(name: "SwiftUIIntrospect", package: "SwiftUI-Introspect"),
|
||||
.product(name: "SFSafeSymbols", package: "SFSafeSymbols")
|
||||
],
|
||||
swiftSettings: [
|
||||
.defaultIsolation(MainActor.self)
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
|
||||
@@ -42,7 +42,7 @@ public class CompoundColors {
|
||||
|
||||
/// Customise the colour at the specified key path with the supplied colour.
|
||||
/// Supplying `nil` as the colour will remove any existing customisation.
|
||||
@MainActor public func override(_ keyPath: KeyPath<CompoundColorTokens, Color>, with color: Color?) {
|
||||
public func override(_ keyPath: KeyPath<CompoundColorTokens, Color>, with color: Color?) {
|
||||
overrides[keyPath] = color
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ public extension UIColor {
|
||||
}
|
||||
|
||||
/// The colours used by Element as defined in Compound Design Tokens.
|
||||
/// This struct contains only the colour tokens in a more usable form.
|
||||
/// This class contains only the colour tokens in a more usable form.
|
||||
/// Since this can be used by attributed strings which may run in non isolated concurrent contexts,
|
||||
/// The object needs to be nonisolated.
|
||||
@Observable
|
||||
@dynamicMemberLookup
|
||||
public class CompoundUIColors {
|
||||
public final nonisolated class CompoundUIColors {
|
||||
/// The base colour tokens that form the palette of available colours.
|
||||
///
|
||||
/// Normally these shouldn't be necessary, however in practice we may need
|
||||
@@ -35,7 +37,7 @@ public class CompoundUIColors {
|
||||
|
||||
/// Customise the colour at the specified key path with the supplied colour.
|
||||
/// Supplying `nil` as the colour will remove any existing customisation.
|
||||
@MainActor public func override(_ keyPath: KeyPath<CompoundUIColorTokens, UIColor>, with color: UIColor?) {
|
||||
public func override(_ keyPath: KeyPath<CompoundUIColorTokens, UIColor>, with color: UIColor?) {
|
||||
overrides[keyPath] = color
|
||||
}
|
||||
|
||||
@@ -44,7 +46,10 @@ public class CompoundUIColors {
|
||||
// swiftformat:disable numberFormatting
|
||||
/// This token is a placeholder and hasn't been finalised.
|
||||
@available(iOS, deprecated: 100000.0, message: "This token should be generated by now.")
|
||||
public let _bgCodeBlock = coreTokens.gray100
|
||||
public var _bgCodeBlock: UIColor {
|
||||
CompoundUIColors.coreTokens.gray100
|
||||
}
|
||||
|
||||
/// This token is a placeholder and hasn't been finalised.
|
||||
@available(iOS, deprecated: 100000.0, message: "This token should be generated by now.")
|
||||
public let _bgSubtleSecondaryAlpha = coreTokens.alphaGray300
|
||||
|
||||
@@ -11,7 +11,6 @@ import SwiftUIIntrospect
|
||||
public extension View {
|
||||
/// Styles a search bar text field using the Compound design tokens.
|
||||
/// This modifier is to be used in combination with `.searchable`.
|
||||
@MainActor
|
||||
func compoundSearchField() -> some View {
|
||||
introspect(.navigationStack, on: .supportedVersions, scope: .ancestor) { navigationController in
|
||||
// Uses the navigation stack as .searchField is unreliable when pushing the second search bar, during the create rooms flow.
|
||||
|
||||
@@ -11,6 +11,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class DecorativeColorsTests: XCTestCase {
|
||||
struct TestCase {
|
||||
let input: String
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import SwiftUI
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class FontSizeTests: XCTestCase {
|
||||
/// Test all system text styles to assert mapping between `Font` and `UIFont`.
|
||||
func testTextStyle() {
|
||||
@@ -103,3 +104,4 @@ final class FontSizeTests: XCTestCase {
|
||||
XCTAssertEqual(styledCustomFootnote15FontSize?.style, .footnote)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,20 +10,25 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
class OverrideColorTests: XCTestCase {
|
||||
func testSwiftUI() {
|
||||
let colors = CompoundColors()
|
||||
let tokens = CompoundColorTokens()
|
||||
XCTAssertEqual(colors.textPrimary, tokens.textPrimary)
|
||||
|
||||
colors.override(\.textPrimary, with: .pink)
|
||||
XCTAssertEqual(colors.textPrimary, .pink)
|
||||
|
||||
colors.override(\.textPrimary, with: nil)
|
||||
XCTAssertEqual(colors.textPrimary, tokens.textPrimary)
|
||||
final class OverrideColorTests: XCTestCase {
|
||||
func testSwiftUI() async {
|
||||
// For some very weird reason we need this to be async, `@MainActor` is not enough
|
||||
// it will compile but when running it will crash at the end of the run due to some deinit problems.
|
||||
// The other solution would be to make CompoundColors nonisolated but we don't really need that.
|
||||
await MainActor.run {
|
||||
let colors = CompoundColors()
|
||||
let tokens = CompoundColorTokens()
|
||||
XCTAssertEqual(colors.textPrimary, tokens.textPrimary)
|
||||
|
||||
colors.override(\.textPrimary, with: .pink)
|
||||
XCTAssertEqual(colors.textPrimary, .pink)
|
||||
|
||||
colors.override(\.textPrimary, with: nil)
|
||||
XCTAssertEqual(colors.textPrimary, tokens.textPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
/// UIColors are nonisolated, so this is fine.
|
||||
func testUIKit() {
|
||||
let colors = CompoundUIColors()
|
||||
let tokens = CompoundUIColorTokens()
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Combine
|
||||
@testable import Compound
|
||||
@testable import SnapshotTesting
|
||||
@preconcurrency @testable import SnapshotTesting
|
||||
import SwiftUI
|
||||
import XCTest
|
||||
|
||||
@@ -28,15 +28,17 @@ class PreviewTests: XCTestCase {
|
||||
.init(name: "iPad", device: "iPad")]
|
||||
private var recordMode: SnapshotTestingConfiguration.Record = .missing
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
if ProcessInfo().environment["RECORD_FAILURES"].map(Bool.init) == true {
|
||||
recordMode = .failed
|
||||
}
|
||||
await MainActor.run {
|
||||
if ProcessInfo().environment["RECORD_FAILURES"].map(Bool.init) == true {
|
||||
recordMode = .failed
|
||||
}
|
||||
|
||||
checkEnvironments()
|
||||
UIView.setAnimationsEnabled(false)
|
||||
checkEnvironments()
|
||||
UIView.setAnimationsEnabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check environments to avoid problems with snapshots on different devices or OS.
|
||||
@@ -183,6 +185,7 @@ private extension PreviewDevice {
|
||||
}
|
||||
|
||||
private extension Snapshotting where Value: SwiftUI.View, Format == UIImage {
|
||||
@MainActor
|
||||
static func prefireImage(drawHierarchyInKeyWindow: Bool = false,
|
||||
preferences: SnapshotPreferences,
|
||||
layout: SwiftUISnapshotLayout = .sizeThatFits,
|
||||
@@ -204,7 +207,7 @@ private extension Snapshotting where Value: SwiftUI.View, Format == UIImage {
|
||||
}
|
||||
|
||||
return SimplySnapshotting<UIImage>(pathExtension: "png", diffing: .prefireImage(preferences: preferences, scale: traits.displayScale))
|
||||
.asyncPullback { view in
|
||||
.asyncPullback { @MainActor view in
|
||||
var config = config
|
||||
|
||||
let controller: UIViewController
|
||||
|
||||
Reference in New Issue
Block a user