From 89d9a821a6e0d2ebb4d582615934b3f66feb80e4 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Tue, 15 Oct 2024 18:13:14 +0200 Subject: [PATCH 01/17] Prepare core and backports --- .../FocusState/FocusCoordinator.swift | 1 + .../Backports/FocusState/View+Focused.swift | 19 +- .../Backports/OnSubmit/View+OnSubmit.swift | 47 +++-- .../Sources/Backports/POBackport.swift | 7 +- .../Backports/ScaledMetric/ScaledMetric.swift | 3 +- .../SubmitLabel/View+SubmitLabel.swift | 15 +- .../AttributedStringBuilder.swift | 2 +- .../AttributedStringMarkdownVisitor.swift | 6 +- .../HorizontalSizeReader.swift | 2 +- .../Core/Markdown/MarkdownNodeFactory.swift | 181 ++++++++++++++++++ .../Markdown/Nodes/MarkdownBlockQuote.swift | 15 ++ .../Markdown/Nodes/MarkdownCodeBlock.swift | 20 ++ .../Markdown/Nodes/MarkdownCodeSpan.swift | 20 ++ .../Markdown/Nodes/MarkdownDocument.swift | 15 ++ .../Markdown/Nodes/MarkdownEmphasis.swift | 15 ++ .../Core/Markdown/Nodes/MarkdownHeading.swift | 20 ++ .../Markdown/Nodes/MarkdownLinebreak.swift | 15 ++ .../Core/Markdown/Nodes/MarkdownLink.swift | 19 ++ .../Core/Markdown/Nodes/MarkdownList.swift | 32 ++++ .../Markdown/Nodes/MarkdownListItem.swift | 15 ++ .../Core/Markdown/Nodes/MarkdownNode.swift | 15 ++ .../Markdown/Nodes/MarkdownParagraph.swift | 15 ++ .../Markdown/Nodes/MarkdownSoftbreak.swift | 15 ++ .../Core/Markdown/Nodes/MarkdownStrong.swift | 15 ++ .../Core/Markdown/Nodes/MarkdownText.swift | 19 ++ .../Nodes/MarkdownThematicBreak.swift | 15 ++ .../MarkdownDebugDescriptionPrinter.swift | 22 +-- .../Visitor/MarkdownVisitor.swift | 3 - .../MarkdownParser/MarkdownNodeFactory.swift | 52 ----- .../Core/MarkdownParser/MarkdownParser.swift | 22 --- .../Nodes/MarkdownBlockQuote.swift | 19 -- .../Nodes/MarkdownCodeBlock.swift | 34 ---- .../Nodes/MarkdownCodeSpan.swift | 29 --- .../Nodes/MarkdownDocument.swift | 25 --- .../Nodes/MarkdownEmphasis.swift | 21 -- .../Nodes/MarkdownHeading.swift | 25 --- .../Nodes/MarkdownLinebreak.swift | 19 -- .../MarkdownParser/Nodes/MarkdownLink.swift | 25 --- .../MarkdownParser/Nodes/MarkdownList.swift | 58 ------ .../Nodes/MarkdownListItem.swift | 21 -- .../MarkdownParser/Nodes/MarkdownNode.swift | 43 ----- .../Nodes/MarkdownParagraph.swift | 21 -- .../Nodes/MarkdownSoftbreak.swift | 19 -- .../MarkdownParser/Nodes/MarkdownStrong.swift | 21 -- .../MarkdownParser/Nodes/MarkdownText.swift | 29 --- .../Nodes/MarkdownThematicBreak.swift | 19 -- .../Nodes/MarkdownUnknown.swift | 20 -- .../Core/Modifiers/Blink/View+Blink.swift | 3 +- .../KeyboardType/View+KeyboardType.swift | 16 +- .../Core/Modifiers/Modify/View+Modify.swift | 4 +- .../OnSizeChange/View+OnSizeChange.swift | 2 +- .../View+TextContentType.swift | 16 +- .../Core/Typography/POTypography.swift | 44 +++++ .../FontNumberSpacing.swift | 2 +- .../POFontFeatureSetting.swift | 2 +- .../POFontFeaturesSettings.swift | 2 +- .../Typography/POTypography+Symbols.swift | 36 ++++ .../Typography/POTypography.swift | 71 ------- 58 files changed, 612 insertions(+), 696 deletions(-) create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownDocument.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownHeading.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLink.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownList.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownListItem.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownNode.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownStrong.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownText.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift rename Sources/ProcessOutCoreUI/Sources/Core/{MarkdownParser => Markdown}/Visitor/MarkdownDebugDescriptionPrinter.swift (86%) rename Sources/ProcessOutCoreUI/Sources/Core/{MarkdownParser => Markdown}/Visitor/MarkdownVisitor.swift (94%) delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownNodeFactory.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/Core/Typography/POTypography.swift create mode 100644 Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift index ceffb827c..bbcfb0b2e 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift @@ -7,6 +7,7 @@ import SwiftUI +@MainActor final class FocusCoordinator: ObservableObject { /// Holds boolean value indicating whether tracked control is currently being edited. diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift index b1d5006b6..73000ed70 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift @@ -32,10 +32,14 @@ extension POBackport where Wrapped: View { @available(iOS 14, *) private struct FocusModifier: ViewModifier { - init(binding: Binding, value: Value) { - self._binding = binding - self.value = value - } + /// The state binding to register. + @Binding + private(set) var binding: Value? + + /// The value to match against when determining whether the binding should change. + let value: Value + + // MARK: - ViewModifier func body(content: Content) -> some View { content @@ -63,13 +67,6 @@ private struct FocusModifier: ViewModifier { // MARK: - Private Properties - /// The value to match against when determining whether the binding should change. - private let value: Value - - /// The state binding to register. - @Binding - private var binding: Value? - /// Indicates whether @State private var isVisible = false diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift b/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift index 23c2927dd..b96b9ba7f 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift @@ -7,34 +7,41 @@ import SwiftUI +extension POBackport where Wrapped: Any { + + @MainActor + struct SubmitAction { + + nonisolated init() { + // Nothing to do + } + + mutating func append(action: @escaping () -> Void) { + actions.append(action) + } + + func callAsFunction() { + actions.forEach { $0() } + } + + // MARK: - Private Properties + + private var actions: [() -> Void] = [] + } +} + extension POBackport where Wrapped: View { /// Adds an action to perform when the user submits a value to this view. /// - NOTE: Only works with `POTextField`. public func onSubmit(_ action: @escaping () -> Void) -> some View { - wrapped.environment(\.backportSubmitAction, action) + wrapped.transformEnvironment(\.backportSubmitAction) { $0.append(action: action) } } } extension EnvironmentValues { - var backportSubmitAction: (() -> Void)? { - get { - self[Key.self] - } - set { - let oldValue = backportSubmitAction - let box = { - oldValue?() - newValue?() - } - self[Key.self] = box - } - } - - // MARK: - Private Properties - - private struct Key: EnvironmentKey { - static let defaultValue: (() -> Void)? = nil - } + /// Submit action. + @Entry + var backportSubmitAction = POBackport.SubmitAction() } diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift b/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift index c20bce60b..f3e2d8809 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift @@ -8,7 +8,9 @@ import SwiftUI /// Provides a convenient method for backporting API. -@_spi(PO) public struct POBackport { +@_spi(PO) +@MainActor +public struct POBackport { /// The underlying content this backport represents. public let wrapped: Wrapped @@ -23,7 +25,8 @@ import SwiftUI extension View { /// Wraps a SwiftUI `View` that can be extended to provide backport functionality. - @_spi(PO) public var backport: POBackport { + @_spi(PO) + public var backport: POBackport { .init(self) } } diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/ScaledMetric/ScaledMetric.swift b/Sources/ProcessOutCoreUI/Sources/Backports/ScaledMetric/ScaledMetric.swift index 5c9e0cfb8..7af19f5bc 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/ScaledMetric/ScaledMetric.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/ScaledMetric/ScaledMetric.swift @@ -42,6 +42,7 @@ extension POBackport where Wrapped == Any { private let baseValue: Value private let textStyle: UIFont.TextStyle? - @Environment(\.sizeCategory) private var sizeCategory + @Environment(\.sizeCategory) + private var sizeCategory } } diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift b/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift index d45c0ad8c..d8eea444b 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift @@ -10,7 +10,7 @@ import SwiftUI extension POBackport where Wrapped == Any { /// A semantic label describing the label of submission within a view hierarchy. - public struct SubmitLabel: Equatable { + public struct SubmitLabel: Equatable, Sendable { let returnKeyType: UIReturnKeyType @@ -66,14 +66,7 @@ extension POBackport where Wrapped: View { extension EnvironmentValues { - var backportSubmitLabel: POBackport.SubmitLabel { - get { self[LabelKey.self] } - set { self[LabelKey.self] = newValue } - } - - // MARK: - Private Properties - - private struct LabelKey: EnvironmentKey { - static let defaultValue = POBackport.SubmitLabel.default - } + /// Submit label. + @Entry + var backportSubmitLabel = POBackport.SubmitLabel.default } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringBuilder.swift b/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringBuilder.swift index 6095e8c47..0fb1bac1a 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringBuilder.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringBuilder.swift @@ -41,7 +41,7 @@ struct AttributedStringBuilder { func build(markdown: String) -> NSAttributedString { let visitor = AttributedStringMarkdownVisitor(builder: self) - let document = MarkdownParser.parse(string: markdown) + let document = MarkdownParser().parse(string: markdown) return document.accept(visitor: visitor) } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringMarkdownVisitor.swift b/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringMarkdownVisitor.swift index 74cbe349a..0ce4ed31f 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringMarkdownVisitor.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/AttributedStringBuilder/AttributedStringMarkdownVisitor.swift @@ -18,10 +18,6 @@ final class AttributedStringMarkdownVisitor: MarkdownVisitor { // MARK: - MarkdownVisitor - func visit(node: MarkdownUnknown) -> NSAttributedString { - node.children.map { $0.accept(visitor: self) }.joined() - } - func visit(document: MarkdownDocument) -> NSAttributedString { let separator = NSAttributedString(string: Constants.paragraphSeparator) return document.children.map { $0.accept(visitor: self) }.joined(separator: separator) @@ -135,7 +131,7 @@ final class AttributedStringMarkdownVisitor: MarkdownVisitor { private enum Constants { static let listMarkerWidthIncrement: CGFloat = 12 - static let listMarkerSpacing = POSpacing.extraSmall + static let listMarkerSpacing: CGFloat = 4 static let lineSeparator = "\u{2028}" static let paragraphSeparator = "\u{2029}" static let tab = "\t" diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift b/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift index 43d25a0ab..640018fca 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift @@ -33,7 +33,7 @@ struct HorizontalSizeReader: View { private struct WidthPreferenceKey: PreferenceKey, Equatable { - static var defaultValue: CGFloat = 0 + static let defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { // An empty reduce implementation takes the first value diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift new file mode 100644 index 000000000..5b5fce162 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift @@ -0,0 +1,181 @@ +// +// MarkdownParser.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 13.06.2023. +// + +import cmark_gfm + +final class MarkdownParser { + + func parse(string: String) -> MarkdownNode { + let pDocumentNode = string.withCString { pointer in + cmark_parse_document(pointer, strlen(pointer), CMARK_OPT_SMART) + } + guard let pDocumentNode else { + return MarkdownDocument(children: []) + } + defer { + cmark_node_free(pDocumentNode) + } + guard let documentNode = node(from: pDocumentNode) else { + return MarkdownDocument(children: []) + } + return documentNode + } + + // MARK: - Utils + + private func children(of pNode: UnsafeMutablePointer) -> [MarkdownNode] { + var pChildNode = pNode.pointee.first_child + var children: [MarkdownNode] = [] + while let ptrChildNode = pChildNode { + if let markdownNode = node(from: ptrChildNode) { + children.append(markdownNode) + } + pChildNode = ptrChildNode.pointee.next + } + return children + } + + // MARK: - Bridging + + private func node(from pNode: UnsafeMutablePointer) -> MarkdownNode? { + let nodeType = cmark_node_type( + UInt32(pNode.pointee.type) + ) + // HTML and images are intentionally not supported. + switch nodeType { + case CMARK_NODE_DOCUMENT: + return documentNode(from: pNode) + case CMARK_NODE_BLOCK_QUOTE: + return blockQuoteNode(from: pNode) + case CMARK_NODE_CODE_BLOCK: + return codeBlockNode(from: pNode) + case CMARK_NODE_CODE: + return codeSpanNode(from: pNode) + case CMARK_NODE_EMPH: + return emphasisNode(from: pNode) + case CMARK_NODE_HEADING: + return headingNode(from: pNode) + case CMARK_NODE_LINEBREAK: + return linebreakNode(from: pNode) + case CMARK_NODE_LINK: + return linkNode(from: pNode) + case CMARK_NODE_LIST: + return listNode(from: pNode) + case CMARK_NODE_ITEM: + return listItemNode(from: pNode) + case CMARK_NODE_PARAGRAPH: + return paragraphNode(from: pNode) + case CMARK_NODE_SOFTBREAK: + return softbreakNode(from: pNode) + case CMARK_NODE_STRONG: + return strongNode(from: pNode) + case CMARK_NODE_TEXT: + return textNode(from: pNode) + case CMARK_NODE_THEMATIC_BREAK: + return thematicBreakNode(from: pNode) + default: + return nil + } + } + + // MARK: - Nodes Bridging + + private func documentNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + return MarkdownDocument(children: children(of: pNode)) + } + + private func blockQuoteNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + return MarkdownBlockQuote(children: children(of: pNode)) + } + + private func codeBlockNode(from pNode: UnsafeMutablePointer) -> MarkdownNode? { + guard let literal = cmark_node_get_literal(pNode) else { + return nil + } + let code = String(cString: literal) + return MarkdownCodeBlock(code: code, children: children(of: pNode)) + } + + private func codeSpanNode(from pNode: UnsafeMutablePointer) -> MarkdownNode? { + guard let literal = cmark_node_get_literal(pNode) else { + return nil + } + let code = String(cString: literal) + return MarkdownCodeSpan(code: code, children: children(of: pNode)) + } + + private func emphasisNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownEmphasis(children: children(of: pNode)) + } + + private func headingNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + let level = Int(pNode.pointee.as.heading.level) + return MarkdownHeading(level: level, children: children(of: pNode)) + } + + private func linebreakNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownLinebreak(children: children(of: pNode)) + } + + private func linkNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + let url = String(cString: pNode.pointee.as.link.url.data) + return MarkdownLink(url: url, children: children(of: pNode)) + } + + private func listNode(from pNode: UnsafeMutablePointer) -> MarkdownNode? { + let type: MarkdownList.Kind, listNode = pNode.pointee.as.list + switch listNode.list_type { + case CMARK_BULLET_LIST: + let marker = Character(Unicode.Scalar(listNode.bullet_char)) + type = .bullet(marker: marker) + case CMARK_ORDERED_LIST: + let delimiter: Character + switch cmark_node_get_list_delim(pNode) { + case CMARK_PERIOD_DELIM: + delimiter = "." + case CMARK_PAREN_DELIM: + delimiter = ")" + default: + return nil + } + let startIndex = Int(listNode.start) + type = .ordered(delimiter: delimiter, startIndex: startIndex) + default: + return nil + } + let isTight = pNode.pointee.as.list.tight + return MarkdownList(type: type, isTight: isTight, children: children(of: pNode)) + } + + private func listItemNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownListItem(children: children(of: pNode)) + } + + private func paragraphNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownParagraph(children: children(of: pNode)) + } + + private func softbreakNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownSoftbreak(children: children(of: pNode)) + } + + private func strongNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownStrong(children: children(of: pNode)) + } + + private func textNode(from pNode: UnsafeMutablePointer) -> MarkdownNode? { + guard let literal = cmark_node_get_literal(pNode) else { + return nil + } + let value = String(cString: literal) + return MarkdownText(value: value, children: children(of: pNode)) + } + + private func thematicBreakNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { + MarkdownThematicBreak(children: children(of: pNode)) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift new file mode 100644 index 000000000..594bb0a56 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift @@ -0,0 +1,15 @@ +// +// MarkdownBlockQuote.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownBlockQuote: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(blockQuote: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift new file mode 100644 index 000000000..71d70ef45 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift @@ -0,0 +1,20 @@ +// +// MarkdownCodeBlock.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownCodeBlock: MarkdownNode { + + /// Code. + let code: String + + // MARK: - MarkdownNode + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(codeBlock: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift new file mode 100644 index 000000000..aa9c902cc --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift @@ -0,0 +1,20 @@ +// +// MarkdownCodeSpan.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownCodeSpan: MarkdownNode { + + /// Code. + let code: String + + // MARK: - MarkdownNode + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(codeSpan: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownDocument.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownDocument.swift new file mode 100644 index 000000000..e5229c4b3 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownDocument.swift @@ -0,0 +1,15 @@ +// +// MarkdownDocument.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 11.06.2023. +// + +struct MarkdownDocument: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(document: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift new file mode 100644 index 000000000..ed5e6701e --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift @@ -0,0 +1,15 @@ +// +// MarkdownEmphasis.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 12.06.2023. +// + +struct MarkdownEmphasis: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(emphasis: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownHeading.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownHeading.swift new file mode 100644 index 000000000..61390e6f3 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownHeading.swift @@ -0,0 +1,20 @@ +// +// MarkdownHeading.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownHeading: MarkdownNode { + + /// Heading level. + let level: Int + + // MARK: - MarkdownNode + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(heading: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift new file mode 100644 index 000000000..2ee201847 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift @@ -0,0 +1,15 @@ +// +// MarkdownLinebreak.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownLinebreak: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(linebreak: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLink.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLink.swift new file mode 100644 index 000000000..b9d05165b --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownLink.swift @@ -0,0 +1,19 @@ +// +// MarkdownLink.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownLink: MarkdownNode { + + let url: String? + + // MARK: - MarkdownNode + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(link: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownList.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownList.swift new file mode 100644 index 000000000..02fb6b979 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownList.swift @@ -0,0 +1,32 @@ +// +// MarkdownList.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 12.06.2023. +// + +struct MarkdownList: MarkdownNode { + + enum Kind { + + /// Ordered + case ordered(delimiter: Character, startIndex: Int) + + /// Bullet aka unordered list. + case bullet(marker: Character) + } + + /// List type. + let type: Kind + + /// Indicates whether list is tight. + let isTight: Bool + + // MARK: - MarkdownNode + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(list: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownListItem.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownListItem.swift new file mode 100644 index 000000000..92704b396 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownListItem.swift @@ -0,0 +1,15 @@ +// +// MarkdownListItem.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 12.06.2023. +// + +struct MarkdownListItem: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(listItem: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownNode.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownNode.swift new file mode 100644 index 000000000..dc2b831c8 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownNode.swift @@ -0,0 +1,15 @@ +// +// MarkdownNode.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 13.06.2023. +// + +protocol MarkdownNode: Sendable { + + /// Returns node children. + var children: [MarkdownNode] { get } + + /// Accepts given visitor. + func accept(visitor: V) -> V.Result +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift new file mode 100644 index 000000000..4ff55635d --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift @@ -0,0 +1,15 @@ +// +// MarkdownParagraph.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 12.06.2023. +// + +struct MarkdownParagraph: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(paragraph: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift new file mode 100644 index 000000000..b9c5a71f1 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift @@ -0,0 +1,15 @@ +// +// MarkdownSoftbreak.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownSoftbreak: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(softbreak: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownStrong.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownStrong.swift new file mode 100644 index 000000000..0ddef5343 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownStrong.swift @@ -0,0 +1,15 @@ +// +// MarkdownStrong.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 12.06.2023. +// + +struct MarkdownStrong: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(strong: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownText.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownText.swift new file mode 100644 index 000000000..136cd8f6f --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownText.swift @@ -0,0 +1,19 @@ +// +// MarkdownText.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 12.06.2023. +// + +struct MarkdownText: MarkdownNode { + + let value: String + + // MARK: - MarkdownNode + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(text: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift new file mode 100644 index 000000000..4e25590cb --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift @@ -0,0 +1,15 @@ +// +// MarkdownThematicBreak.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 15.06.2023. +// + +struct MarkdownThematicBreak: MarkdownNode { + + let children: [MarkdownNode] + + func accept(visitor: V) -> V.Result { + visitor.visit(thematicBreak: self) + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift similarity index 86% rename from Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift rename to Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift index a1460bf98..5c6b965be 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift @@ -9,15 +9,7 @@ import Foundation -extension MarkdownBaseNode: CustomDebugStringConvertible { - - var debugDescription: String { - let visitor = MarkdownDebugDescriptionPrinter() - return self.accept(visitor: visitor) - } -} - -private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { +final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { init(level: Int = 0) { self.level = level @@ -25,10 +17,6 @@ private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { // MARK: - MarkdownVisitor - func visit(node: MarkdownUnknown) -> String { - description(node: node, nodeName: "Unknown") - } - func visit(document: MarkdownDocument) -> String { description(node: document, nodeName: "Document") } @@ -81,11 +69,7 @@ private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { } func visit(codeBlock: MarkdownCodeBlock) -> String { - var attributes: [String: CustomStringConvertible] = [:] - if let info = codeBlock.info { - attributes["info"] = info - } - return description(node: codeBlock, nodeName: "Code Block", attributes: attributes, content: codeBlock.code) + return description(node: codeBlock, nodeName: "Code Block", content: codeBlock.code) } func visit(thematicBreak: MarkdownThematicBreak) -> String { @@ -131,7 +115,7 @@ private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { } private func description( - node: MarkdownBaseNode, + node: MarkdownNode, nodeName: String, attributes: [String: CustomStringConvertible] = [:], content: String? = nil diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownVisitor.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownVisitor.swift similarity index 94% rename from Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownVisitor.swift rename to Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownVisitor.swift index 6bbe22239..ca3444aff 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownVisitor.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownVisitor.swift @@ -9,9 +9,6 @@ protocol MarkdownVisitor { associatedtype Result - /// Visits unknown node. - func visit(node: MarkdownUnknown) -> Result - /// Visits document. func visit(document: MarkdownDocument) -> Result diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownNodeFactory.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownNodeFactory.swift deleted file mode 100644 index 5cf7a4014..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownNodeFactory.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// MarkdownNodeFactory.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 13.06.2023. -// - -import cmark_gfm - -final class MarkdownNodeFactory { - - init(cmarkNode: MarkdownBaseNode.CmarkNode) { - self.cmarkNode = cmarkNode - } - - func create() -> MarkdownBaseNode { - let nodeType = UInt32(cmarkNode.pointee.type) - guard nodeType != CMARK_NODE_NONE.rawValue else { - preconditionFailure("Invalid node") - } - // HTML and images are intentionally not supported. - guard let nodeClass = Self.supportedNodes[nodeType] else { - return MarkdownUnknown(cmarkNode: cmarkNode) - } - return nodeClass.init(cmarkNode: cmarkNode) - } - - // MARK: - Private Properties - - private static let supportedNodes: [UInt32: MarkdownBaseNode.Type] = { - let supportedNodes = [ - MarkdownDocument.self, - MarkdownText.self, - MarkdownParagraph.self, - MarkdownList.self, - MarkdownListItem.self, - MarkdownStrong.self, - MarkdownEmphasis.self, - MarkdownBlockQuote.self, - MarkdownCodeBlock.self, - MarkdownCodeSpan.self, - MarkdownHeading.self, - MarkdownLinebreak.self, - MarkdownSoftbreak.self, - MarkdownThematicBreak.self, - MarkdownLink.self - ] - return Dictionary(grouping: supportedNodes) { $0.cmarkNodeType.rawValue }.compactMapValues(\.first) - }() - - private let cmarkNode: MarkdownBaseNode.CmarkNode -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift deleted file mode 100644 index 1f0478a09..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// MarkdownParser.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 13.06.2023. -// - -import Foundation -import cmark_gfm - -enum MarkdownParser { - - static func parse(string: String) -> MarkdownDocument { - let document = string.withCString { pointer in - cmark_parse_document(pointer, strlen(pointer), CMARK_OPT_SMART) - } - guard let document else { - preconditionFailure("Failed to parse markdown document") - } - return MarkdownDocument(cmarkNode: document) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift deleted file mode 100644 index 7cc69ffe1..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// MarkdownBlockQuote.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownBlockQuote: MarkdownBaseNode { - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_BLOCK_QUOTE - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(blockQuote: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift deleted file mode 100644 index 484990cbf..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// MarkdownCodeBlock.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownCodeBlock: MarkdownBaseNode { - - /// Returns the info string from a fenced code block. - private(set) lazy var info: String? = { - String(cString: cmarkNode.pointee.as.code.info.data) - }() - - private(set) lazy var code: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_CODE_BLOCK - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(codeBlock: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift deleted file mode 100644 index 8b25455f5..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// MarkdownCodeSpan.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownCodeSpan: MarkdownBaseNode { - - private(set) lazy var code: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_CODE - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(codeSpan: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift deleted file mode 100644 index a19aa1cb6..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// MarkdownDocument.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 11.06.2023. -// - -import cmark_gfm - -final class MarkdownDocument: MarkdownBaseNode { - - deinit { - cmark_node_free(cmarkNode) - } - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_DOCUMENT - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(document: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift deleted file mode 100644 index 854c5397f..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// MarkdownEmphasis.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -final class MarkdownEmphasis: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_EMPH - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(emphasis: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift deleted file mode 100644 index 8115ace24..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// MarkdownHeading.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownHeading: MarkdownBaseNode { - - private(set) lazy var level: Int = { - Int(cmarkNode.pointee.as.heading.level) - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_HEADING - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(heading: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift deleted file mode 100644 index a8b0df8b7..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// MarkdownLinebreak.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownLinebreak: MarkdownBaseNode { - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_LINEBREAK - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(linebreak: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift deleted file mode 100644 index c0e07b1c1..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// MarkdownLink.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownLink: MarkdownBaseNode { - - private(set) lazy var url: String? = { - String(cString: cmarkNode.pointee.as.link.url.data) - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_LINK - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(link: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift deleted file mode 100644 index 1b02b6197..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// MarkdownList.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -final class MarkdownList: MarkdownBaseNode { - - enum ListType { - - /// Ordered - case ordered(delimiter: Character, startIndex: Int) - - /// Bullet aka unordered list. - case bullet(marker: Character) - } - - private(set) lazy var type: ListType = { - let listNode = cmarkNode.pointee.as.list - switch listNode.list_type { - case CMARK_BULLET_LIST: - let marker = Character(Unicode.Scalar(listNode.bullet_char)) - return .bullet(marker: marker) - case CMARK_ORDERED_LIST: - let delimiter: Character - switch cmark_node_get_list_delim(cmarkNode) { - case CMARK_PERIOD_DELIM: - delimiter = "." - case CMARK_PAREN_DELIM: - delimiter = ")" - default: - assertionFailure("Unexpected delimiter type") - delimiter = "." - } - let startIndex = Int(listNode.start) - return .ordered(delimiter: delimiter, startIndex: startIndex) - default: - preconditionFailure("Unsupported list type: \(listNode.list_type)") - } - }() - - private(set) lazy var isTight: Bool = { - cmarkNode.pointee.as.list.tight - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_LIST - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(list: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift deleted file mode 100644 index dd43dc37a..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// MarkdownListItem.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -final class MarkdownListItem: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_ITEM - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(listItem: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift deleted file mode 100644 index cead992b5..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// MarkdownBaseNode.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -class MarkdownBaseNode { - - typealias CmarkNode = UnsafeMutablePointer - - class var cmarkNodeType: cmark_node_type { - fatalError("Must be implemented by subclass.") - } - - required init(cmarkNode: CmarkNode, validatesType: Bool = true) { - if validatesType { - assert(cmarkNode.pointee.type == Self.cmarkNodeType.rawValue) - } - self.cmarkNode = cmarkNode - } - - /// Returns node children. - private(set) lazy var children: [MarkdownBaseNode] = { - var cmarkChild = cmarkNode.pointee.first_child - var children: [MarkdownBaseNode] = [] - while let cmarkNode = cmarkChild { - let child = MarkdownNodeFactory(cmarkNode: cmarkNode).create() - children.append(child) - cmarkChild = cmarkNode.pointee.next - } - return children - }() - - let cmarkNode: CmarkNode - - /// Accepts given visitor. - func accept(visitor: V) -> V.Result { // swiftlint:disable:this unavailable_function - fatalError("Must be implemented by subclass.") - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift deleted file mode 100644 index 1d98a070d..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// MarkdownParagraph.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -final class MarkdownParagraph: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_PARAGRAPH - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(paragraph: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift deleted file mode 100644 index cda0c53df..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// MarkdownSoftbreak.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownSoftbreak: MarkdownBaseNode { - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_SOFTBREAK - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(softbreak: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift deleted file mode 100644 index cc86ae389..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// MarkdownStrong.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -final class MarkdownStrong: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_STRONG - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(strong: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift deleted file mode 100644 index da9d0e65d..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// MarkdownText.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 12.06.2023. -// - -import cmark_gfm - -final class MarkdownText: MarkdownBaseNode { - - private(set) lazy var value: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_TEXT - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(text: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift deleted file mode 100644 index 1da83b521..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// MarkdownThematicBreak.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -import cmark_gfm - -final class MarkdownThematicBreak: MarkdownBaseNode { - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_THEMATIC_BREAK - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(thematicBreak: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift deleted file mode 100644 index 2fefc5028..000000000 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// MarkdownUnknown.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 15.06.2023. -// - -/// Unknown node. -final class MarkdownUnknown: MarkdownBaseNode { - - required init(cmarkNode: CmarkNode, validatesType: Bool = false) { - super.init(cmarkNode: cmarkNode, validatesType: false) - } - - // MARK: - MarkdownBaseNode - - override func accept(visitor: V) -> V.Result { - visitor.visit(node: self) - } -} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift index 6563f3c25..65df4394e 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift @@ -31,5 +31,6 @@ private struct BlinkViewModifier: ViewModifier { // MARK: - Private Properties - @State private var isVisible = true + @State + private var isVisible = true } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift index 501489096..dfe557a3e 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift @@ -11,21 +11,15 @@ extension View { /// Sets the keyboard type for this view. In addition to calling the native counterpart, /// the implementation also exposes given type as an environment so works with `POTextField`. - @_spi(PO) public func poKeyboardType(_ type: UIKeyboardType) -> some View { + @_spi(PO) + public func poKeyboardType(_ type: UIKeyboardType) -> some View { environment(\.poKeyboardType, type).keyboardType(type) } } extension EnvironmentValues { - var poKeyboardType: UIKeyboardType { - get { self[Key.self] } - set { self[Key.self] = newValue } - } - - // MARK: - Private Properties - - private struct Key: EnvironmentKey { - static let defaultValue = UIKeyboardType.default - } + /// Keyboard type. + @Entry + var poKeyboardType = UIKeyboardType.default } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift index 5836eabc5..839925c08 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift @@ -7,7 +7,8 @@ import SwiftUI -@_spi(PO) extension View { +@_spi(PO) +extension View { @ViewBuilder public func modify(when condition: Bool, @ViewBuilder _ transform: (Self) -> some View) -> some View { @@ -18,7 +19,6 @@ import SwiftUI } } - @ViewBuilder public func modify(@ViewBuilder _ transform: (Self) -> some View) -> some View { transform(self) } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift index 99d6ca981..1428f4939 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift @@ -35,7 +35,7 @@ private struct SizeModifier: ViewModifier { private struct SizePreferenceKey: PreferenceKey { - static var defaultValue: CGSize = .zero + static let defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift index 0a666047b..67271394d 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift @@ -11,21 +11,15 @@ extension View { /// Sets the text content type for this view. In addition to calling the native counterpart, /// the implementation also exposes given type as an environment so works with `POTextField`. - @_spi(PO) public func poTextContentType(_ type: UITextContentType?) -> some View { + @_spi(PO) + public func poTextContentType(_ type: UITextContentType?) -> some View { environment(\.poTextContentType, type).textContentType(type) } } extension EnvironmentValues { - var poTextContentType: UITextContentType? { - get { self[Key.self] } - set { self[Key.self] = newValue } - } - - // MARK: - Private Properties - - private struct Key: EnvironmentKey { - static let defaultValue: UITextContentType? = nil - } + /// Text content type. + @Entry + var poTextContentType: UITextContentType? } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Typography/POTypography.swift b/Sources/ProcessOutCoreUI/Sources/Core/Typography/POTypography.swift new file mode 100644 index 000000000..c3c12e3b5 --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/Core/Typography/POTypography.swift @@ -0,0 +1,44 @@ +// +// POTypography.swift +// ProcessOutCoreUI +// +// Created by Andrii Vysotskyi on 21.11.2022. +// + +import UIKit + +/// Holds typesetting information that could be applied to displayed text. +public struct POTypography: Sendable { + + /// Font assosiated with given typography. + public let font: UIFont + + /// A dynamic text style to use for font. Pass `nil` to opt out from Dynamic Type and use fixed font size. + /// Default value is `.body`. + public let textStyle: UIFont.TextStyle? + + /// Line height. If not set explicitly equals to font's line height. + public let lineHeight: CGFloat + + /// This property contains the space (measured in points) added at the end of the paragraph to separate + /// it from the following paragraph. This value must be nonnegative. Default value is `0`. + public let paragraphSpacing: CGFloat + + /// Creates typography with provided information. + public init( + font: UIFont, + textStyle: UIFont.TextStyle? = .body, + lineHeight: CGFloat? = nil, + paragraphSpacing: CGFloat = 0 + ) { + self.font = font + self.textStyle = textStyle + if let lineHeight { + assert(lineHeight >= font.lineHeight, "Line height less than font's will cause clipping") + self.lineHeight = max(lineHeight, font.lineHeight) + } else { + self.lineHeight = font.lineHeight + } + self.paragraphSpacing = paragraphSpacing + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/FontNumberSpacing.swift b/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/FontNumberSpacing.swift index a12f1c118..b8519c22a 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/FontNumberSpacing.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/FontNumberSpacing.swift @@ -8,7 +8,7 @@ import CoreText @_spi(PO) -public struct POFontNumberSpacing { +public struct POFontNumberSpacing: Sendable { let rawValue: Int } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeatureSetting.swift b/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeatureSetting.swift index 7d827113d..b78921a26 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeatureSetting.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeatureSetting.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 30.07.2024. // -protocol FontFeatureSetting { +protocol FontFeatureSetting: Sendable { /// Indicates a general class of effect (e.g., ligatures). var featureType: Int { get } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeaturesSettings.swift b/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeaturesSettings.swift index f74f1c9e6..c93c90413 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeaturesSettings.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/UIKit+Extensions/UIFont+FontFeatures/POFontFeaturesSettings.swift @@ -6,7 +6,7 @@ // @_spi(PO) -public struct POFontFeaturesSettings { +public struct POFontFeaturesSettings: Sendable { /// The number spacing feature type specifies a choice for the appearance of digits. public var numberSpacing: POFontNumberSpacing = .proportional diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift new file mode 100644 index 000000000..dbb07f0cc --- /dev/null +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift @@ -0,0 +1,36 @@ +// +// POTypography+Symbols.swift +// ProcessOut +// +// Created by Andrii Vysotskyi on 15.10.2024. +// + +@_spi(PO) +extension POTypography { + + /// Use for titles. + public static let title = POTypography(font: UIFont(.WorkSans.medium, size: 20), lineHeight: 24) + + /// Subheading typography. + public static let subheading = POTypography(font: UIFont(.WorkSans.medium, size: 18), lineHeight: 24) + + /// Primary body text for readability and consistency. + public static let body1 = POTypography(font: UIFont(.WorkSans.medium, size: 16), lineHeight: 24) + + /// Secondary body text for supporting content. + public static let body2 = POTypography(font: UIFont(.WorkSans.regular, size: 14), lineHeight: 18) + + /// Text used on buttons or interactive elements. + public static let button = POTypography(font: UIFont(.WorkSans.medium, size: 14), lineHeight: 18) + + /// Large text for prominent labels or headings. + public static let label1 = POTypography(font: UIFont(.WorkSans.medium, size: 14), lineHeight: 18) + + /// Smaller text for secondary labels or headings. + public static let label2 = POTypography(font: UIFont(.WorkSans.regular, size: 14), lineHeight: 18) + + /// Registers all custom fonts. + public static func registerFonts() { + FontResource.register() + } +} diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift deleted file mode 100644 index dc37fcf41..000000000 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// POTypography.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 21.11.2022. -// - -import UIKit - -/// Holds typesetting information that could be applied to displayed text. -public struct POTypography { - - /// Font assosiated with given typography. - public let font: UIFont - - /// A dynamic text style to use for font. Pass `nil` to opt out from Dynamic Type and use fixed font size. - /// Default value is `.body`. - public let textStyle: UIFont.TextStyle? - - /// Line height. If not set explicitly equals to font's line height. - public let lineHeight: CGFloat - - /// This property contains the space (measured in points) added at the end of the paragraph to separate - /// it from the following paragraph. This value must be nonnegative. Default value is `0`. - public let paragraphSpacing: CGFloat - - /// Creates typography with provided information. - public init( - font: UIFont, textStyle: UIFont.TextStyle? = .body, lineHeight: CGFloat? = nil, paragraphSpacing: CGFloat = 0 - ) { - self.font = font - self.textStyle = textStyle - if let lineHeight { - assert(lineHeight >= font.lineHeight, "Line height less than font's will cause clipping") - self.lineHeight = max(lineHeight, font.lineHeight) - } else { - self.lineHeight = font.lineHeight - } - self.paragraphSpacing = paragraphSpacing - } -} - -@_spi(PO) -extension POTypography { - - /// Use for titles. - public static let title = POTypography(font: UIFont(.WorkSans.medium, size: 20), lineHeight: 24) - - /// Subheading typography. - public static let subheading = POTypography(font: UIFont(.WorkSans.medium, size: 18), lineHeight: 24) - - /// Primary body text for readability and consistency. - public static let body1 = POTypography(font: UIFont(.WorkSans.medium, size: 16), lineHeight: 24) - - /// Secondary body text for supporting content. - public static let body2 = POTypography(font: UIFont(.WorkSans.regular, size: 14), lineHeight: 18) - - /// Text used on buttons or interactive elements. - public static let button = POTypography(font: UIFont(.WorkSans.medium, size: 14), lineHeight: 18) - - /// Large text for prominent labels or headings. - public static let label1 = POTypography(font: UIFont(.WorkSans.medium, size: 14), lineHeight: 18) - - /// Smaller text for secondary labels or headings. - public static let label2 = POTypography(font: UIFont(.WorkSans.regular, size: 14), lineHeight: 18) - - /// Registers all custom fonts. - public static func registerFonts() { - FontResource.register() - } -} From 513a239069d8fabae210d0940a93056262d277d0 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Tue, 15 Oct 2024 18:13:34 +0200 Subject: [PATCH 02/17] Prepare symbols --- .../ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift b/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift index ff134fc52..ab279ba46 100644 --- a/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift +++ b/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift @@ -10,7 +10,7 @@ import UIKit /// A font resource. -struct FontResource { +struct FontResource: Sendable { /// Font resource name. fileprivate let weight: UIFont.Weight From bb09ebb12308883738cce6c9f7bb2aebdbc5ed05 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 11:36:19 +0200 Subject: [PATCH 03/17] Prepare CoreUI module --- .../POActionsContainerActionViewModel.swift | 3 +- .../POActionsContainerStyle.swift | 2 ++ .../POActionsContainerView.swift | 2 +- .../View+ActionsContainerStyle.swift | 7 +++-- .../AsyncImage/POAsyncImage.swift | 3 -- .../AsyncImage/POAsyncImagePhase.swift | 2 +- .../DesignSystem/Border/POBorderStyle.swift | 2 +- .../Button/Any/POAnyButtonStyle.swift | 3 +- .../Button/Brand/View+BrandColor.swift | 3 +- .../PassKit/POPassKitPaymentButton.swift | 3 +- .../PassKit/POPassKitPaymentButtonStyle.swift | 20 ++++++------- .../View+POPassKitPaymentButtonStyle.swift | 7 +++-- .../Button/Regular/POButtonStateStyle.swift | 2 +- .../Button/Regular/View+ButtonLoading.swift | 3 +- .../Checkbox/POCheckboxToggleStateStyle.swift | 4 +-- .../CodeField/CodeFieldViewCoordinator.swift | 1 + .../DesignSystem/CodeField/POCodeField.swift | 5 ++-- .../CodeField/Style/AnyCodeFieldStyle.swift | 25 ----------------- .../CodeField/Style/CodeFieldStyle.swift | 3 +- .../CodeField/Style/View+CodeFieldStyle.swift | 9 +++--- .../Control/View+ControlInvalid.swift | 3 +- .../InputStyle/POInputStateStyle.swift | 2 +- .../InputStyle/POInputStyle.swift | 4 ++- .../InputStyle/View+InputStyle.swift | 8 ++++-- .../Message/AnyMessageViewStyle.swift | 25 ----------------- .../Message/MessageView+Style.swift | 9 +++--- .../DesignSystem/Message/POMessage.swift | 4 +-- .../DesignSystem/Message/POMessageView.swift | 4 +-- .../Message/POMessageViewStyle.swift | 4 ++- .../Message/POToastMessageStyle.swift | 2 +- .../DesignSystem/Picker/POPicker.swift | 14 ++++++---- .../DesignSystem/Picker/POPickerStyle.swift | 28 ++++++------------- .../Picker/Styles/POMenuPickerStyle.swift | 2 +- .../Styles/PORadioGroupPickerStyle.swift | 2 +- .../Picker/View+PickerStyle.swift | 9 +++--- .../PORadioButtonKnobStateStyle.swift | 2 +- .../RadioButton/PORadioButtonStateStyle.swift | 2 +- .../RadioButton/PORadioButtonStyle.swift | 2 ++ .../View+RadioButtonSelected.swift | 3 +- .../DesignSystem/Shadow/POShadowStyle.swift | 2 +- .../DesignSystem/Spacing/POSpacing.swift | 3 +- .../DesignSystem/Text/POTextStyle.swift | 2 +- .../DesignSystem/Text/View+TextStyle.swift | 2 +- .../DesignSystem/TextField/POTextField.swift | 4 +-- .../Typography/POTypography+Symbols.swift | 2 ++ 45 files changed, 109 insertions(+), 144 deletions(-) delete mode 100644 Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift delete mode 100644 Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerActionViewModel.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerActionViewModel.swift index 39c583946..23b938edb 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerActionViewModel.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerActionViewModel.swift @@ -7,7 +7,8 @@ import Foundation -@_spi(PO) public struct POActionsContainerActionViewModel: Identifiable { +@_spi(PO) +public struct POActionsContainerActionViewModel: Identifiable { /// Creates view model with given parameters. public init( diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift index 8bb2a79d3..2fc73259a 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift @@ -8,6 +8,8 @@ import SwiftUI /// Actions container style. +@MainActor +@preconcurrency public struct POActionsContainerStyle { /// Style for primary button. diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerView.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerView.swift index 368bd4508..eefd5ab37 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerView.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerView.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14, *) @_spi(PO) +@available(iOS 14, *) public struct POActionsContainerView: View { public init(actions: [POActionsContainerActionViewModel]) { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift index ce6ca39ca..70188d495 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift @@ -9,8 +9,8 @@ import SwiftUI extension View { - @available(iOS 14, *) @_spi(PO) + @available(iOS 14, *) public func actionsContainerStyle(_ style: POActionsContainerStyle) -> some View { environment(\.actionsContainerStyle, style) } @@ -19,14 +19,15 @@ extension View { @available(iOS 14, *) extension EnvironmentValues { + @MainActor var actionsContainerStyle: POActionsContainerStyle { - get { self[Key.self] } + get { self[Key.self] ?? .default } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue = POActionsContainerStyle.default + static let defaultValue: POActionsContainerStyle? = nil } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift index 292e1c9cb..a197d1395 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift @@ -8,7 +8,6 @@ import SwiftUI @_spi(PO) -@MainActor @available(iOS 14, *) public struct POAsyncImage: View { @@ -55,8 +54,6 @@ public struct POAsyncImage: View { // MARK: - Private Methods /// Implementation resolves image and updates phase. - @Sendable - @MainActor private func resolveImage() async { guard !Task.isCancelled, case .empty = phase else { return diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImagePhase.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImagePhase.swift index 17696ae25..0ff55125f 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImagePhase.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImagePhase.swift @@ -8,7 +8,7 @@ import SwiftUI @_spi(PO) -public enum POAsyncImagePhase { +public enum POAsyncImagePhase: Sendable { /// No image is loaded. case empty diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift index 1e15bbac9..d4e715615 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Style that defines border appearance. Border is always a solid line. -public struct POBorderStyle { +public struct POBorderStyle: Sendable { /// Corner radius. public let radius: CGFloat diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Any/POAnyButtonStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Any/POAnyButtonStyle.swift index e7c17f56d..43863e956 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Any/POAnyButtonStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Any/POAnyButtonStyle.swift @@ -9,7 +9,8 @@ import SwiftUI /// The `POAnyButtonStyle` type forwards body creation to an underlying style value, /// hiding the type of the wrapped value. -@_spi(PO) public struct POAnyButtonStyle: ButtonStyle { +@_spi(PO) +public struct POAnyButtonStyle: ButtonStyle { public init(erasing style: any ButtonStyle) { self.style = style diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Brand/View+BrandColor.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Brand/View+BrandColor.swift index c88763ddf..28d21280f 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Brand/View+BrandColor.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Brand/View+BrandColor.swift @@ -10,7 +10,8 @@ import SwiftUI extension View { /// Adds a condition that controls whether button with a `POButtonStyle` should show loading indicator. - @_spi(PO) public func buttonBrandColor(_ color: UIColor) -> some View { + @_spi(PO) + public func buttonBrandColor(_ color: UIColor) -> some View { environment(\.poButtonBrandColor, color) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift index f23ce30c2..a9e3376c6 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift @@ -8,8 +8,8 @@ import SwiftUI import PassKit -@available(iOS 14.0, *) @_spi(PO) +@available(iOS 14.0, *) public struct POPassKitPaymentButton: View { public init(type: PKPaymentButtonType, action: @escaping () -> Void) { @@ -67,6 +67,7 @@ private struct ButtonRepresentable: UIViewRepresentable { } } +@MainActor private final class ButtonCoordinator { init(action: @escaping () -> Void) { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift index 51f71ee25..f5b07eba5 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift @@ -9,7 +9,9 @@ import PassKit /// PassKit button style. @available(iOS 14.0, *) -public struct POPassKitPaymentButtonStyle { +@MainActor +@preconcurrency +public struct POPassKitPaymentButtonStyle: Sendable { /// Native style value. public let style: PKPaymentButtonStyle @@ -18,16 +20,14 @@ public struct POPassKitPaymentButtonStyle { public let cornerRadius: CGFloat /// Creates style instance. - public init(native: PKPaymentButtonStyle = .automatic, cornerRadius: CGFloat = defaultCornerRadius) { + public init(native: PKPaymentButtonStyle = .automatic) { self.style = native - self.cornerRadius = cornerRadius + self.cornerRadius = POSpacing.extraSmall } -} - -@available(iOS 14.0, *) -extension POPassKitPaymentButtonStyle { - /// Default value of corner radius. - @usableFromInline - static let defaultCornerRadius = POSpacing.extraSmall + /// Creates style instance. + public init(native: PKPaymentButtonStyle = .automatic, cornerRadius: CGFloat) { + self.style = native + self.cornerRadius = cornerRadius + } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/View+POPassKitPaymentButtonStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/View+POPassKitPaymentButtonStyle.swift index d73962b41..158acaf85 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/View+POPassKitPaymentButtonStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/View+POPassKitPaymentButtonStyle.swift @@ -10,8 +10,8 @@ import SwiftUI extension View { /// Changes PassKit button style. - @available(iOS 14.0, *) @_spi(PO) + @available(iOS 14.0, *) public func passKitPaymentButtonStyle(_ style: POPassKitPaymentButtonStyle) -> some View { environment(\.passKitPaymentButtonStyle, style) } @@ -21,14 +21,15 @@ extension View { extension EnvironmentValues { /// PassKit button style. + @MainActor var passKitPaymentButtonStyle: POPassKitPaymentButtonStyle { - get { self[Key.self] } + get { self[Key.self] ?? .init() } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue = POPassKitPaymentButtonStyle() + static let defaultValue: POPassKitPaymentButtonStyle? = nil } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift index 72a639b39..520045c2a 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Defines button's styling information in a specific state. -public struct POButtonStateStyle { +public struct POButtonStateStyle: Sendable { /// Title typography. public let title: POTextStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/View+ButtonLoading.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/View+ButtonLoading.swift index 7627dbb20..e37c219a3 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/View+ButtonLoading.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/View+ButtonLoading.swift @@ -10,7 +10,8 @@ import SwiftUI extension View { /// Adds a condition that controls whether button with a `POButtonStyle` should show loading indicator. - @_spi(PO) public func buttonLoading(_ isLoading: Bool) -> some View { + @_spi(PO) + public func buttonLoading(_ isLoading: Bool) -> some View { environment(\.isButtonLoading, isLoading) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Checkbox/POCheckboxToggleStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Checkbox/POCheckboxToggleStateStyle.swift index a21a79080..308cfeda5 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Checkbox/POCheckboxToggleStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Checkbox/POCheckboxToggleStateStyle.swift @@ -8,9 +8,9 @@ import SwiftUI /// Describes checkbox style in a particular state. -public struct POCheckboxToggleStateStyle { +public struct POCheckboxToggleStateStyle: Sendable { - public struct Checkmark { + public struct Checkmark: Sendable { /// Checkmark color. public let color: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift index fa0793b18..4156277d9 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift @@ -7,6 +7,7 @@ import Foundation +@MainActor final class CodeFieldViewCoordinator { var representable: CodeFieldRepresentable! // swiftlint:disable:this implicitly_unwrapped_optional diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift index f00b5a66b..b00384b7f 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14.0, *) @_spi(PO) +@available(iOS 14.0, *) public struct POCodeField: View { public init(text: Binding, length: Int) { @@ -24,8 +24,7 @@ public struct POCodeField: View { focusCoordinator.beginEditing() textIndex = newIndex } - style - .makeBody(configuration: configuration) + AnyView(style.makeBody(configuration: configuration)) .background( CodeFieldRepresentable( length: length, text: $text, textIndex: $textIndex, isMenuVisible: $isMenuVisible diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift deleted file mode 100644 index 86df19d78..000000000 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AnyCodeFieldStyle.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 13.06.2024. -// - -import SwiftUI - -struct AnyCodeFieldStyle: CodeFieldStyle { - - init(erasing style: some CodeFieldStyle) { - _makeBody = { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - func makeBody(configuration: CodeFieldStyleConfiguration) -> some View { - _makeBody(configuration) - } - - // MARK: - Private Properties - - private let _makeBody: (CodeFieldStyleConfiguration) -> AnyView -} diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift index 0ef91e704..2eefb3242 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift @@ -7,7 +7,8 @@ import SwiftUI -protocol CodeFieldStyle { +@MainActor +protocol CodeFieldStyle: Sendable { /// A view that represents the body of a button. associatedtype Body: View diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift index f448589d5..624f3bea9 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift @@ -12,21 +12,22 @@ extension View { /// Sets the style for picker views within this view. @available(iOS 14.0, *) func codeFieldStyle(_ style: some CodeFieldStyle) -> some View { - environment(\.codeFieldStyle, AnyCodeFieldStyle(erasing: style)) + environment(\.codeFieldStyle, style) } } @available(iOS 14.0, *) extension EnvironmentValues { - var codeFieldStyle: AnyCodeFieldStyle { - get { self[Key.self] } + @MainActor + var codeFieldStyle: any CodeFieldStyle { + get { self[Key.self] ?? .default } set { self[Key.self] = newValue } } // MARK: - Private Properties private struct Key: EnvironmentKey { - static let defaultValue = AnyCodeFieldStyle(erasing: .default) + static let defaultValue: (any CodeFieldStyle)? = nil } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Control/View+ControlInvalid.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Control/View+ControlInvalid.swift index 3a49f1603..aafbefbd5 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Control/View+ControlInvalid.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Control/View+ControlInvalid.swift @@ -9,7 +9,8 @@ import SwiftUI extension View { - @_spi(PO) public func controlInvalid(_ isInvalid: Bool) -> some View { + @_spi(PO) + public func controlInvalid(_ isInvalid: Bool) -> some View { environment(\.isControlInvalid, isInvalid) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift index 849f5b784..4b74457b8 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Defines input's styling information in a specific state. -public struct POInputStateStyle { +public struct POInputStateStyle: Sendable { /// Text style. public let text: POTextStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift index c8b42e502..b8ff59d30 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift @@ -8,7 +8,9 @@ import SwiftUI /// Defines input control style in both normal and error states. -public struct POInputStyle { +@MainActor +@preconcurrency +public struct POInputStyle: Sendable { /// Style for normal state. public let normal: POInputStateStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift index df5d74fc9..5b49c4987 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift @@ -9,21 +9,23 @@ import SwiftUI extension View { - @_spi(PO) public func inputStyle(_ style: POInputStyle) -> some View { + @_spi(PO) + public func inputStyle(_ style: POInputStyle) -> some View { environment(\.inputStyle, style) } } extension EnvironmentValues { + @MainActor var inputStyle: POInputStyle { - get { self[Key.self] } + get { self[Key.self] ?? .medium } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue: POInputStyle = .medium + static let defaultValue: POInputStyle? = nil } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift deleted file mode 100644 index bcf912062..000000000 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AnyMessageViewStyle.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 03.06.2024. -// - -import SwiftUI - -struct AnyMessageViewStyle: POMessageViewStyle { - - init(erasing style: any POMessageViewStyle) { - _makeBody = { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - func makeBody(configuration: Configuration) -> AnyView { - _makeBody(configuration) - } - - // MARK: - Private Properties - - private let _makeBody: (Configuration) -> AnyView -} diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift index 3e70f3d77..e052ec293 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift @@ -13,21 +13,22 @@ extension View { @_spi(PO) @available(iOS 14.0, *) public func messageViewStyle(_ style: any POMessageViewStyle) -> some View { - environment(\.messageViewStyle, AnyMessageViewStyle(erasing: style)) + environment(\.messageViewStyle, style) } } @available(iOS 14.0, *) extension EnvironmentValues { - var messageViewStyle: AnyMessageViewStyle { - get { self[Key.self] } + @MainActor + var messageViewStyle: any POMessageViewStyle { + get { self[Key.self] ?? .toast /* Workaround to allow use of MainActor isolated default value. */ } set { self[Key.self] = newValue } } // MARK: - Private Properties private struct Key: EnvironmentKey { - static let defaultValue = AnyMessageViewStyle(erasing: .toast) + static let defaultValue: (any POMessageViewStyle)? = nil } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessage.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessage.swift index eae1bac39..8cc444ba3 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessage.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessage.swift @@ -8,7 +8,7 @@ import Foundation @_spi(PO) -public struct POMessage: Identifiable { +public struct POMessage: Identifiable, Sendable { /// Message ID. public let id: String @@ -27,7 +27,7 @@ public struct POMessage: Identifiable { } /// Message severity. -public enum POMessageSeverity { +public enum POMessageSeverity: Sendable { /// An error. case error diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift index df735f4fb..d382541b3 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14.0, *) @_spi(PO) +@available(iOS 14.0, *) public struct POMessageView: View { public init(message: POMessage) { @@ -19,7 +19,7 @@ public struct POMessageView: View { public var body: some View { let configuration = POMessageViewStyleConfiguration(label: Text(message.text), severity: message.severity) - style.makeBody(configuration: configuration) + AnyView(style.makeBody(configuration: configuration)) } // MARK: - Private Properties diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift index c35adaaa4..916397dc3 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift @@ -7,7 +7,9 @@ import SwiftUI -public protocol POMessageViewStyle { +@MainActor +@preconcurrency +public protocol POMessageViewStyle: Sendable { /// A view that represents the body of a message. associatedtype Body: View diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift index 0449e4a7a..7076e0720 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift @@ -11,7 +11,7 @@ import SwiftUI public struct POToastMessageStyle: POMessageViewStyle { /// Style for specific severity. - public struct Severity { + public struct Severity: Sendable { /// Icon image. public let icon: Image? diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift index 1b07b1032..d5b58f9a6 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14, *) @_spi(PO) +@available(iOS 14, *) public struct POPicker: View { // swiftlint:disable:next line_length @@ -23,7 +23,7 @@ public struct POPicker: View { let configuration = POPickerStyleConfiguration( elements: data.map(createConfigurationElement), isInvalid: isInvalid ) - style.makeBody(configuration: configuration) + AnyView(style.makeBody(configuration: configuration)) } // MARK: - Private Properties @@ -32,10 +32,14 @@ public struct POPicker: View { private let id: KeyPath private let content: (Data.Element) -> Text - @Binding private var selection: Id? + @Binding + private var selection: Id? + + @Environment(\.pickerStyle) + private var style - @Environment(\.pickerStyle) private var style - @Environment(\.isControlInvalid) private var isInvalid + @Environment(\.isControlInvalid) + private var isInvalid // MARK: - Private Methods diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift index 66d4e2eea..8c115932a 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift @@ -9,7 +9,10 @@ import SwiftUI /// A type that specifies the appearance and interaction of all pickers /// within a view hierarchy. -@_spi(PO) public protocol POPickerStyle { +@_spi(PO) +@MainActor +@preconcurrency +public protocol POPickerStyle: Sendable { /// A view representing the appearance and interaction of a `POPicker`. associatedtype Body: View @@ -20,7 +23,8 @@ import SwiftUI @ViewBuilder func makeBody(configuration: POPickerStyleConfiguration) -> Self.Body } -@_spi(PO) public struct POPickerStyleConfiguration { +@_spi(PO) +public struct POPickerStyleConfiguration { /// Picker elements. public let elements: [POPickerStyleConfigurationElement] @@ -29,7 +33,8 @@ import SwiftUI public let isInvalid: Bool } -@_spi(PO) public struct POPickerStyleConfigurationElement: Identifiable { +@_spi(PO) +public struct POPickerStyleConfigurationElement: Identifiable { /// The stable identity of the element. public let id: AnyHashable @@ -43,20 +48,3 @@ import SwiftUI /// Invoke to select element. public let select: () -> Void } - -struct AnyPickerStyle: POPickerStyle { - - init(erasing style: Style) { - _makeBody = { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - func makeBody(configuration: POPickerStyleConfiguration) -> some View { - _makeBody(configuration) - } - - // MARK: - Private Properties - - private let _makeBody: (POPickerStyleConfiguration) -> AnyView -} diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/POMenuPickerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/POMenuPickerStyle.swift index 279a5734a..bc01a1feb 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/POMenuPickerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/POMenuPickerStyle.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14, *) @_spi(PO) +@available(iOS 14, *) public struct POMenuPickerStyle: POPickerStyle { public init(inputStyle: POInputStyle) { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/PORadioGroupPickerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/PORadioGroupPickerStyle.swift index e9c5147d2..8c9992fcd 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/PORadioGroupPickerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/Styles/PORadioGroupPickerStyle.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14, *) @_spi(PO) +@available(iOS 14, *) public struct PORadioGroupPickerStyle: POPickerStyle { public init(radioButtonStyle: RadioButtonStyle = PORadioButtonStyle.radio) { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift index 0462a10e0..aebf66e17 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift @@ -13,21 +13,22 @@ extension View { @_spi(PO) @available(iOS 14, *) public func pickerStyle(_ style: Style) -> some View { - environment(\.pickerStyle, AnyPickerStyle(erasing: style)) + environment(\.pickerStyle, style) } } @available(iOS 14, *) extension EnvironmentValues { - var pickerStyle: AnyPickerStyle { - get { self[Key.self] } + @MainActor + var pickerStyle: any POPickerStyle { + get { self[Key.self] ?? .radioGroup } set { self[Key.self] = newValue } } // MARK: - Private Properties private struct Key: EnvironmentKey { - static let defaultValue = AnyPickerStyle(erasing: .radioGroup) + static let defaultValue: (any POPickerStyle)? = nil } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift index 8e287ec57..2562521c2 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Describes radio button knob style in a particular state. -public struct PORadioButtonKnobStateStyle { +public struct PORadioButtonKnobStateStyle: Sendable { /// Background color. public let backgroundColor: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift index ab7501801..81eb5dc08 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift @@ -8,7 +8,7 @@ import UIKit /// Describes radio button style in a particular state, for example when selected. -public struct PORadioButtonStateStyle { +public struct PORadioButtonStateStyle: Sendable { /// Styling of the radio button knob not including value. public let knob: PORadioButtonKnobStateStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStyle.swift index bf5cd454d..87e7772c6 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStyle.swift @@ -9,6 +9,8 @@ import SwiftUI /// Describes radio button style in different states. @available(iOS 14, *) +@MainActor +@preconcurrency public struct PORadioButtonStyle: ButtonStyle { /// Style to use when radio button is in default state ie enabled and not selected. diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift index a494136de..4622c0c85 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift @@ -9,7 +9,8 @@ import SwiftUI extension View { - @_spi(PO) public func radioButtonSelected(_ isSelected: Bool) -> some View { + @_spi(PO) + public func radioButtonSelected(_ isSelected: Bool) -> some View { environment(\.isRadioButtonSelected, isSelected) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift index aa82aa14a..046333494 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Style that defines shadow appearance. -public struct POShadowStyle { +public struct POShadowStyle: Sendable { /// The color of the shadow. public let color: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift index c4d858a3b..41ab25f69 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift @@ -7,7 +7,8 @@ import Foundation -@_spi(PO) public enum POSpacing { +@_spi(PO) +public enum POSpacing { /// Extra small spacing. public static let extraSmall: CGFloat = 4 diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift index 205beb3ba..a050991a4 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Text style. -public struct POTextStyle { +public struct POTextStyle: Sendable { /// Text foreground color. public let color: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/View+TextStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/View+TextStyle.swift index 7fe965f3d..32c63a8e8 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/View+TextStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/View+TextStyle.swift @@ -12,8 +12,8 @@ extension View { /// Applies given `style` to text. /// /// - NOTE: When `addPadding` is set to true this method has a cumulative effect. - @available(iOS 14.0, *) @_spi(PO) + @available(iOS 14.0, *) public func textStyle(_ style: POTextStyle, addPadding: Bool = true) -> some View { modifier(ContentModifier(style: style, addPadding: addPadding)) } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift index 877a08149..6a129fa90 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift @@ -7,8 +7,8 @@ import SwiftUI -@available(iOS 14, *) @_spi(PO) +@available(iOS 14, *) public struct POTextField: View { /// - Parameters: @@ -123,7 +123,7 @@ private struct TextFieldRepresentable: UIViewRepresentable { // MARK: - func willReturn() { - submitAction?() + submitAction() } // MARK: - Private Nested Types diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift index dbb07f0cc..754aa0c9a 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography+Symbols.swift @@ -5,6 +5,8 @@ // Created by Andrii Vysotskyi on 15.10.2024. // +import UIKit + @_spi(PO) extension POTypography { From 147df076e08a2b87d39a76c3d11a03ed33f5360d Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 11:39:37 +0200 Subject: [PATCH 04/17] Add preconcurrency --- .../ProcessOut/Sources/Api/ProcessOut.swift | 27 +++++++++---------- .../Api/Utils/Test3DS/POTest3DSService.swift | 3 ++- .../Services/Cards/POCardsService.swift | 3 +++ ...ledWebAuthenticationSessionDecorator.swift | 5 ++-- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Sources/ProcessOut/Sources/Api/ProcessOut.swift b/Sources/ProcessOut/Sources/Api/ProcessOut.swift index d0c632597..4cd3c8e87 100644 --- a/Sources/ProcessOut/Sources/Api/ProcessOut.swift +++ b/Sources/ProcessOut/Sources/Api/ProcessOut.swift @@ -334,23 +334,22 @@ extension ProcessOut { /// - force: When set to `false` (the default) only the first invocation takes effect, all /// subsequent calls to this method are ignored. Pass `true` to allow existing shared instance /// reconfiguration (if any). + @MainActor + @preconcurrency public static func configure(configuration: ProcessOutConfiguration, force: Bool = false) { - // todo(andrii-vysotskyi): isolate method to main actor when releasing 5.0.0 - MainActor.assumeIsolated { - if isConfigured { - if force { - shared.replace(configuration: configuration) - shared.logger.debug("Did change ProcessOut configuration") - } else { - shared.logger.debug("ProcessOut can be configured only once, ignored") - } + if isConfigured { + if force { + shared.replace(configuration: configuration) + shared.logger.debug("Did change ProcessOut configuration") } else { - Self.prewarm() - _shared.withLock { instance in - instance = ProcessOut(configuration: configuration) - } - shared.logger.debug("Did complete ProcessOut configuration") + shared.logger.debug("ProcessOut can be configured only once, ignored") + } + } else { + Self.prewarm() + _shared.withLock { instance in + instance = ProcessOut(configuration: configuration) } + shared.logger.debug("Did complete ProcessOut configuration") } } diff --git a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift index 34c3ba396..8f86b4115 100644 --- a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift @@ -10,6 +10,8 @@ import UIKit /// Service that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access /// Control Server (ACS). Should be used only for testing purposes in sandbox environment. @available(*, deprecated, message: "Use ProcessOutUI.POTest3DSService instead.") +@MainActor +@preconcurrency public final class POTest3DSService: PO3DS2Service { /// Creates service instance. @@ -40,7 +42,6 @@ public final class POTest3DSService: PO3DS2Service { ) } - @MainActor public func performChallenge(with parameters: PO3DS2ChallengeParameters) async throws -> PO3DS2ChallengeResult { await withCheckedContinuation { continuation in let alertController = UIAlertController( diff --git a/Sources/ProcessOut/Sources/Services/Cards/POCardsService.swift b/Sources/ProcessOut/Sources/Services/Cards/POCardsService.swift index a592bc744..820bc6692 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/POCardsService.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/POCardsService.swift @@ -28,10 +28,12 @@ public protocol POCardsService: POService { // sourcery: AutoCompletion /// Tokenize previously authorized payment. @MainActor + @preconcurrency func tokenize(request: POApplePayPaymentTokenizationRequest) async throws -> POCard /// Authorize given payment request and tokenize it. @MainActor + @preconcurrency func tokenize( request: POApplePayTokenizationRequest, delegate: POApplePayTokenizationDelegate? ) async throws -> POCard @@ -41,6 +43,7 @@ extension POCardsService { /// Authorize given payment request and tokenize it. @MainActor + @preconcurrency public func tokenize(request: POApplePayTokenizationRequest) async throws -> POCard { try await tokenize(request: request, delegate: nil) } diff --git a/Sources/ProcessOut/Sources/Sessions/WebAuthentication/ThrottledWebAuthenticationSessionDecorator.swift b/Sources/ProcessOut/Sources/Sessions/WebAuthentication/ThrottledWebAuthenticationSessionDecorator.swift index db137f3d4..0db78956a 100644 --- a/Sources/ProcessOut/Sources/Sessions/WebAuthentication/ThrottledWebAuthenticationSessionDecorator.swift +++ b/Sources/ProcessOut/Sources/Sessions/WebAuthentication/ThrottledWebAuthenticationSessionDecorator.swift @@ -7,10 +7,9 @@ import Foundation -@MainActor -final class ThrottledWebAuthenticationSessionDecorator: WebAuthenticationSession { +actor ThrottledWebAuthenticationSessionDecorator: WebAuthenticationSession { - nonisolated init(session: WebAuthenticationSession) { + init(session: WebAuthenticationSession) { self.session = session semaphore = AsyncSemaphore(value: 1) } From c855946fe28237e87a0f022fc87dbf482b183a34 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 13:53:28 +0200 Subject: [PATCH 05/17] Prepare UI --- ...tiveAlternativePaymentMethodDelegate.swift | 2 + ...ONativeAlternativePaymentMethodEvent.swift | 6 +-- .../Sources/Api/ProcessOutUI.swift | 4 +- .../Api/Test3DS/POTest3DSService.swift | 6 +-- .../AddressSpecification.swift | 12 ++--- .../AddressSpecificationProvider.swift | 48 +++++++++++++------ .../CardScheme/CardSchemeProvider.swift | 4 +- .../CardSchemeImageProvider.swift | 2 +- .../PresentingViewControllerProvider.swift | 1 + .../POConfirmationDialogConfiguration.swift | 2 +- .../POBillingAddressConfiguration.swift | 2 +- .../POCardTokenizationConfiguration.swift | 2 +- .../Delegate/POCardTokenizationDelegate.swift | 10 ++++ .../Delegate/POCardTokenizationEvent.swift | 2 +- .../Style/POCardTokenizationStyle.swift | 2 + .../Style/View+CardTokenizationStyle.swift | 5 +- .../POCardUpdateConfiguration.swift | 2 +- .../Delegate/POCardUpdateDelegate.swift | 6 +++ .../Delegate/POCardUpdateEvent.swift | 2 +- .../Delegate/POCardUpdateInformation.swift | 2 +- .../CardUpdate/Style/POCardUpdateStyle.swift | 2 + .../Style/View+CardUpdateStyle.swift | 5 +- ...ckoutAlternativePaymentConfiguration.swift | 8 ++-- .../PODynamicCheckoutCardConfiguration.swift | 4 +- .../PODynamicCheckoutConfiguration.swift | 6 +-- .../Delegate/PODynamicCheckoutDelegate.swift | 19 +++++++- .../Delegate/PODynamicCheckoutEvent.swift | 2 +- .../DynamicCheckoutDefaultInteractor.swift | 3 +- .../Style/PODynamicCheckoutStyle.swift | 5 +- .../Style/View+DynamicCheckoutStyle.swift | 5 +- ...namicCheckoutInteractorChildProvider.swift | 3 +- .../PODynamicCheckoutViewController.swift | 2 +- .../DefaultDynamicCheckoutViewModel.swift | 2 +- .../DynamicCheckoutViewModelState.swift | 4 +- ...iveAlternativePaymentBackgroundStyle.swift | 1 + .../PONativeAlternativePaymentStyle.swift | 2 + ...+NativeAlternativePaymentMethodStyle.swift | 5 +- .../NativeAlternativePaymentContentView.swift | 2 +- ...tiveAlternativePaymentViewModelState.swift | 4 +- 39 files changed, 141 insertions(+), 65 deletions(-) diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift index a9af17071..dfddd8570 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift @@ -9,12 +9,14 @@ public protocol PONativeAlternativePaymentMethodDelegate: AnyObject { /// Invoked when module emits event. + @MainActor func nativeAlternativePaymentMethodDidEmitEvent(_ event: PONativeAlternativePaymentMethodEvent) /// Method provides an ability to supply default values for given parameters. Completion expects dictionary /// where key is a parameter key, and value is desired default. It is not mandatory to provide defaults for /// all parameters. /// - NOTE: completion must be called on `main` thread. + @MainActor func nativeAlternativePaymentMethodDefaultValues( for parameters: [PONativeAlternativePaymentMethodParameter], completion: @escaping ([String: String]) -> Void ) diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/PONativeAlternativePaymentMethodEvent.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/PONativeAlternativePaymentMethodEvent.swift index bb9d53927..a018df585 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/PONativeAlternativePaymentMethodEvent.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/PONativeAlternativePaymentMethodEvent.swift @@ -6,9 +6,9 @@ // /// Describes events that could happen during native alternative payment module lifecycle. -public enum PONativeAlternativePaymentMethodEvent { +public enum PONativeAlternativePaymentMethodEvent: Sendable { - public struct WillSubmitParameters { + public struct WillSubmitParameters: Sendable { /// Available parameters. public let parameters: [PONativeAlternativePaymentMethodParameter] @@ -25,7 +25,7 @@ public enum PONativeAlternativePaymentMethodEvent { } } - public struct ParametersChanged { + public struct ParametersChanged: Sendable { /// Parameter definition that the user changed. public let parameter: PONativeAlternativePaymentMethodParameter diff --git a/Sources/ProcessOutUI/Sources/Api/ProcessOutUI.swift b/Sources/ProcessOutUI/Sources/Api/ProcessOutUI.swift index 77835ea8f..cb8b2caea 100644 --- a/Sources/ProcessOutUI/Sources/Api/ProcessOutUI.swift +++ b/Sources/ProcessOutUI/Sources/Api/ProcessOutUI.swift @@ -11,9 +11,11 @@ /// /// This method should be called when the application starts to ensure that all resources are loaded and available /// for SDK to use. It also allows to avoid potential UI hangs during runtime. -public enum ProcessOutUI { +public enum ProcessOutUI: Sendable { /// Configures UI package and preloads needed resources. + @MainActor + @preconcurrency public static func configure() { POTypography.registerFonts() AddressSpecificationProvider.shared.prewarm() diff --git a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift index d55fc9c55..1fb7b4adb 100644 --- a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift @@ -10,15 +10,16 @@ import UIKit /// Service that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access /// Control Server (ACS). Should be used only for testing purposes in sandbox environment. +@MainActor public final class POTest3DSService: PO3DS2Service { /// Creates service instance. @available(*, deprecated, message: "Use init that doesn't accept parameters.") - public init(returnUrl: URL) { + public nonisolated init(returnUrl: URL) { // Ignored } - public init() { + public nonisolated init() { // Ignored } @@ -36,7 +37,6 @@ public final class POTest3DSService: PO3DS2Service { ) } - @MainActor public func performChallenge(with parameters: PO3DS2ChallengeParameters) async throws -> PO3DS2ChallengeResult { guard let presentingViewController = PresentingViewControllerProvider.find() else { throw POFailure(message: "Unable to present 3DS challenge.", code: .generic(.mobile)) diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift index 9146443c0..c86aeba78 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift @@ -5,25 +5,25 @@ // Created by Andrii Vysotskyi on 26.10.2023. // -struct AddressSpecification { +struct AddressSpecification: Sendable { - enum Unit: String, CaseIterable, Decodable { + enum Unit: String, CaseIterable, Decodable, Sendable { case street, city, state, postcode } - enum CityUnit: String, Decodable { + enum CityUnit: String, Decodable, Sendable { case city, district, postTown, suburb } - enum StateUnit: String, Decodable { + enum StateUnit: String, Decodable, Sendable { case area, county, department, doSi, emirate, island, oblast, parish, prefecture, province, state } - enum PostcodeUnit: String, Decodable { + enum PostcodeUnit: String, Decodable, Sendable { case postcode, eircode, pin, zip } - struct State: Decodable { + struct State: Decodable, Sendable { let abbreviation, name: String } diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift index 064ce9e42..b32e35f08 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift @@ -8,25 +8,25 @@ import Foundation @_spi(PO) import ProcessOut -final class AddressSpecificationProvider { +final class AddressSpecificationProvider: Sendable { static let shared = AddressSpecificationProvider() /// Preloads specifications. func prewarm() { - DispatchQueue.global(qos: .userInitiated).async { self.loadSpecifications() } + DispatchQueue.global(qos: .userInitiated).async { _ = self.storage } } // MARK: - AddressSpecificationProvider /// Returns supported country codes. - private(set) lazy var countryCodes: [String] = { - Array(loadSpecifications().keys) - }() + var countryCodes: [String] { + storage.countryCodes + } /// Returns address spec for given country code or default if country is unknown. func specification(for countryCode: String) -> AddressSpecification { - loadSpecifications()[countryCode] ?? .default + storage.specifications[countryCode] ?? .default } // MARK: - Private Nested Types @@ -37,7 +37,7 @@ final class AddressSpecificationProvider { // MARK: - Private Properties - private let specifications = POUnfairlyLocked<[String: AddressSpecification]?>(wrappedValue: nil) + private let _storage = POUnfairlyLocked(wrappedValue: nil) // MARK: - Private Methods @@ -45,23 +45,41 @@ final class AddressSpecificationProvider { // NOP } - @discardableResult - private func loadSpecifications() -> [String: AddressSpecification] { - specifications.withLock { specifications in - if let specifications { - return specifications + private var storage: Storage { + _storage.withLock { storage in + if let storage { + return storage } guard let url = BundleLocator.bundle.url(forResource: Constants.resource, withExtension: nil) else { preconditionFailure("Unable to find resource.") } + let newStorage: Storage do { let data = try Data(contentsOf: url) - specifications = try JSONDecoder().decode([String: AddressSpecification].self, from: data) + let specifications = try JSONDecoder().decode([String: AddressSpecification].self, from: data) + newStorage = Storage( + specifications: specifications, countryCodes: Array(specifications.keys) + ) } catch { assertionFailure("Failed to load metadata: \(error)") - specifications = [:] + newStorage = Storage(specifications: [:], countryCodes: []) } - return specifications ?? [:] + storage = newStorage + return newStorage } } } + +private final class Storage { + + init(specifications: [String: AddressSpecification], countryCodes: [String]) { + self.specifications = specifications + self.countryCodes = countryCodes + } + + /// Country codes. + var countryCodes: [String] + + /// Specifications. + var specifications: [String: AddressSpecification] +} diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift index e72b28ee6..d774ca4a0 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift @@ -9,9 +9,9 @@ import Foundation @_spi(PO) import ProcessOut // todo(andrii-vysotskyi): support more schemes -final class CardSchemeProvider { +final class CardSchemeProvider: Sendable { - struct Issuer { + struct Issuer: Sendable { let scheme: POCardScheme let numbers: IssuerNumbers let length: Int diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift index d13cb6489..f79f99941 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift @@ -9,7 +9,7 @@ import SwiftUI import ProcessOut @_spi(PO) import ProcessOutCoreUI -final class CardSchemeImageProvider { +final class CardSchemeImageProvider: Sendable { static let shared = CardSchemeImageProvider() diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift index b57e46664..4216b3e76 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift @@ -10,6 +10,7 @@ import UIKit enum PresentingViewControllerProvider { /// Attempts to find view controller that can modally present other view controller. + @MainActor static func find() -> UIViewController? { let rootViewController = UIApplication.shared .connectedScenes diff --git a/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift b/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift index 502a3e84b..51a4ea39f 100644 --- a/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift @@ -6,7 +6,7 @@ // /// Confirmation dialog configuration. -public struct POConfirmationDialogConfiguration { +public struct POConfirmationDialogConfiguration: Sendable { /// Confirmation title. Use empty string to hide title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift index 822e7d611..31e8c4cf0 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift @@ -8,7 +8,7 @@ import ProcessOut /// Billing address collection configuration. -public struct POBillingAddressConfiguration { +public struct POBillingAddressConfiguration: Sendable { @available(*, deprecated, message: "Use POBillingAddressCollectionMode directly.") public typealias CollectionMode = POBillingAddressCollectionMode diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift index 72e28b532..852d8c16a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift @@ -10,7 +10,7 @@ import ProcessOut /// A configuration object that defines a card tokenization module behaves. /// Use `nil` as a value for a nullable property to indicate that default value should be used. -public struct POCardTokenizationConfiguration { +public struct POCardTokenizationConfiguration: Sendable { /// Custom title. Use empty string to hide title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift index 4b972ea7c..6192f58dc 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift @@ -11,6 +11,7 @@ import ProcessOut public protocol POCardTokenizationDelegate: AnyObject { /// Invoked when module emits event. + @MainActor func cardTokenizationDidEmitEvent(_ event: POCardTokenizationEvent) /// Allows delegate to additionally process tokenized card before ending module's lifecycle. For example @@ -21,6 +22,7 @@ public protocol POCardTokenizationDelegate: AnyObject { /// - shouldSaveCard: A Boolean value indicating whether the user has requested card to be saved. /// /// - NOTE: When possible please prefer throwing `POFailure` instead of other error types. + @MainActor func cardTokenization(didTokenizeCard card: POCard, shouldSaveCard: Bool) async throws /// Allows delegate to additionally process tokenized card before ending module's lifecycle. For example @@ -28,36 +30,44 @@ public protocol POCardTokenizationDelegate: AnyObject { /// /// - NOTE: When possible please prefer throwing `POFailure` instead of other error types. @available(*, deprecated, message: "Implement cardTokenization(didTokenizeCard:save:) method instead.") + @MainActor func processTokenizedCard(card: POCard) async throws /// Allows to choose preferred scheme that will be selected by default based on issuer information. Default /// implementation returns primary scheme. + @MainActor func preferredScheme(issuerInformation: POCardIssuerInformation) -> String? /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. + @MainActor func shouldContinueTokenization(after failure: POFailure) -> Bool } extension POCardTokenizationDelegate { + @MainActor public func cardTokenizationDidEmitEvent(_ event: POCardTokenizationEvent) { // Ignored } + @MainActor public func cardTokenization(didTokenizeCard card: POCard, shouldSaveCard: Bool) async throws { // Ignored } @available(*, deprecated, message: "Implement cardTokenization(didTokenizeCard:save:) method instead.") + @MainActor public func processTokenizedCard(card: POCard) async throws { // Ignored } + @MainActor public func preferredScheme(issuerInformation: POCardIssuerInformation) -> String? { issuerInformation.scheme } + @MainActor public func shouldContinueTokenization(after failure: POFailure) -> Bool { true } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift index d544d05f9..421dc74cb 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift @@ -8,7 +8,7 @@ import ProcessOut /// Describes events that could happen during card tokenization lifecycle. -public enum POCardTokenizationEvent { +public enum POCardTokenizationEvent: Sendable { /// Initial event that is sent prior any other event. case willStart diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift index fcf0affcb..261515810 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift @@ -13,6 +13,8 @@ import SwiftUI /// For more information about styling specific components, see /// [the dedicated documentation.](https://swiftpackageindex.com/processout/processout-ios/documentation/processoutcoreui) @available(iOS 14, *) +@MainActor +@preconcurrency public struct POCardTokenizationStyle { /// Title style. diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift index 22808e69a..ce185d231 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift @@ -19,14 +19,15 @@ extension View { @available(iOS 14, *) extension EnvironmentValues { + @MainActor var cardTokenizationStyle: POCardTokenizationStyle { - get { self[Key.self] } + get { self[Key.self] ?? .default } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue = POCardTokenizationStyle.default + static let defaultValue: POCardTokenizationStyle? = nil } } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift index 741b5b5b7..576450318 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift @@ -7,7 +7,7 @@ /// A configuration object that defines how a card update module behaves. /// Use `nil` as a value for a nullable property to indicate that default value should be used. -public struct POCardUpdateConfiguration { +public struct POCardUpdateConfiguration: Sendable { /// Card id that needs to be updated. public let cardId: String diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift index 4bd63feba..806a692e1 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift @@ -11,26 +11,32 @@ import ProcessOut public protocol POCardUpdateDelegate: AnyObject { /// Invoked when module emits event. + @MainActor func cardUpdateDidEmitEvent(_ event: POCardUpdateEvent) /// Asks delegate to resolve card information based on card id. + @MainActor func cardInformation(cardId: String) async -> POCardUpdateInformation? /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. + @MainActor func shouldContinueUpdate(after failure: POFailure) -> Bool } extension POCardUpdateDelegate { + @MainActor public func cardUpdateDidEmitEvent(_ event: POCardUpdateEvent) { // Ignored } + @MainActor public func cardInformation(cardId: String) async -> POCardUpdateInformation? { nil } + @MainActor public func shouldContinueUpdate(after failure: POFailure) -> Bool { true } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift index 008e8a205..07246e657 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift @@ -6,7 +6,7 @@ // /// Describes events that could happen during card update lifecycle. -public enum POCardUpdateEvent { +public enum POCardUpdateEvent: Sendable { /// Initial event that is sent prior any other event. case willStart diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift index 7284ca7ff..c52b52dd3 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift @@ -8,7 +8,7 @@ import ProcessOut /// Short card information necessary for CVC update. -public struct POCardUpdateInformation { +public struct POCardUpdateInformation: Sendable { /// Masked card number displayed to user as is if set. public let maskedNumber: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/POCardUpdateStyle.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/POCardUpdateStyle.swift index bc4ef58ad..99f5c1ede 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/POCardUpdateStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/POCardUpdateStyle.swift @@ -13,6 +13,8 @@ import SwiftUI /// For more information about styling specific components, see /// [the dedicated documentation.](https://swiftpackageindex.com/processout/processout-ios/documentation/processoutcoreui) @available(iOS 14, *) +@MainActor +@preconcurrency public struct POCardUpdateStyle { /// Title style. diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/View+CardUpdateStyle.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/View+CardUpdateStyle.swift index 6b766f24c..9381a5269 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/View+CardUpdateStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Style/View+CardUpdateStyle.swift @@ -19,14 +19,15 @@ extension View { @available(iOS 14, *) extension EnvironmentValues { + @MainActor var cardUpdateStyle: POCardUpdateStyle { - get { self[Key.self] } + get { self[Key.self] ?? .default } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue = POCardUpdateStyle.default + static let defaultValue: POCardUpdateStyle? = nil } } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift index f6adacd14..5e4008eb9 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift @@ -9,12 +9,12 @@ import Foundation /// Alternative payment specific dynamic checkout configuration. @_spi(PO) -public struct PODynamicCheckoutAlternativePaymentConfiguration { +public struct PODynamicCheckoutAlternativePaymentConfiguration: Sendable { - public struct PaymentConfirmation { + public struct PaymentConfirmation: Sendable { /// Confirmation button configuration. - public struct ConfirmButton { // swiftlint:disable:this nesting + public struct ConfirmButton: Sendable { // swiftlint:disable:this nesting /// Button title. public let title: String? @@ -57,7 +57,7 @@ public struct PODynamicCheckoutAlternativePaymentConfiguration { } } - public struct CancelButton { + public struct CancelButton: Sendable { /// Cancel button title. Use `nil` for default title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift index 989df6a93..4cb624ac7 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift @@ -9,10 +9,10 @@ import ProcessOut /// Card specific dynamic checkout configuration. @_spi(PO) -public struct PODynamicCheckoutCardConfiguration { +public struct PODynamicCheckoutCardConfiguration: Sendable { /// Billing address collection configuration. - public struct BillingAddress { + public struct BillingAddress: Sendable { /// Default address information. public let defaultAddress: POContact? diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift index 422b97c0c..676e06623 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift @@ -10,9 +10,9 @@ import ProcessOut /// Dynamic checkout configuration. @_spi(PO) -public struct PODynamicCheckoutConfiguration { +public struct PODynamicCheckoutConfiguration: Sendable { - public struct PaymentSuccess { + public struct PaymentSuccess: Sendable { /// Custom success message to display user when payment completes. public let message: String? @@ -27,7 +27,7 @@ public struct PODynamicCheckoutConfiguration { } } - public struct CancelButton { + public struct CancelButton: Sendable { /// Cancel button title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift index 7887ffaf1..0ff8d6152 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift @@ -14,25 +14,29 @@ public protocol PODynamicCheckoutDelegate: AnyObject { /// Invoked when module emits dynamic checkout event. /// - NOTE: default implementation does nothing. + @MainActor func dynamicCheckout(didEmitEvent event: PODynamicCheckoutEvent) /// Called when dynamic checkout is about to authorize invoice with given request. /// /// Your implementation may alter request parameters and return new request but make /// sure that invoice id and source stay the same. + @MainActor func dynamicCheckout( willAuthorizeInvoiceWith request: inout POInvoiceAuthorizationRequest, using paymentMethod: PODynamicCheckoutPaymentMethod - ) async -> PO3DS2Service + ) async -> sending PO3DS2Service /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. + @MainActor func dynamicCheckout(shouldContinueAfter failure: POFailure) -> Bool /// Your implementation could return a request that will be used to fetch new invoice to replace existing one /// to be able to recover from normally unrecoverable payment failure. /// /// - NOTE: Please make sure to invalidate old invoice if you decide to create new. + @MainActor func dynamicCheckout( newInvoiceFor invoice: POInvoice, invalidationReason: PODynamicCheckoutInvoiceInvalidationReason ) async -> POInvoiceRequest? @@ -40,21 +44,25 @@ public protocol PODynamicCheckoutDelegate: AnyObject { // MARK: - Card Payment /// Invoked when module emits event. + @MainActor func dynamicCheckout(didEmitCardTokenizationEvent event: POCardTokenizationEvent) /// Allows to choose preferred scheme that will be selected by default based on issuer information. Default /// implementation returns primary scheme. + @MainActor func dynamicCheckout(preferredSchemeFor issuerInformation: POCardIssuerInformation) -> String? // MARK: - Alternative Payment /// Invoked when module emits alternative payment event. + @MainActor func dynamicCheckout(didEmitAlternativePaymentEvent event: PONativeAlternativePaymentEvent) /// Method provides an ability to supply default values for given parameters. /// /// - Returns: dictionary where key is a parameter key, and value is desired default. Please note that it is not /// mandatory to provide defaults for all parameters. + @MainActor func dynamicCheckout( alternativePaymentDefaultsFor parameters: [PONativeAlternativePaymentMethodParameter] ) async -> [String: String] @@ -62,43 +70,52 @@ public protocol PODynamicCheckoutDelegate: AnyObject { // MARK: - Pass Kit /// Gives implementation an opportunity to modify payment request before it is used to authorize invoice. + @MainActor func dynamicCheckout(willAuthorizeInvoiceWith request: PKPaymentRequest) async } extension PODynamicCheckoutDelegate { + @MainActor public func dynamicCheckout(didEmitEvent event: PODynamicCheckoutEvent) { // Ignored } + @MainActor public func dynamicCheckout(shouldContinueAfter failure: POFailure) -> Bool { true } + @MainActor public func dynamicCheckout( newInvoiceFor invoice: POInvoice, invalidationReason: PODynamicCheckoutInvoiceInvalidationReason ) async -> POInvoiceRequest? { nil } + @MainActor public func dynamicCheckout(didEmitCardTokenizationEvent event: POCardTokenizationEvent) { // Ignored } + @MainActor public func dynamicCheckout(preferredSchemeFor issuerInformation: POCardIssuerInformation) -> String? { issuerInformation.scheme } + @MainActor public func dynamicCheckout(didEmitAlternativePaymentEvent event: PONativeAlternativePaymentEvent) { // Ignored } + @MainActor public func dynamicCheckout( alternativePaymentDefaultsFor parameters: [PONativeAlternativePaymentMethodParameter] ) async -> [String: String] { [:] } + @MainActor public func dynamicCheckout(willAuthorizeInvoiceWith request: PKPaymentRequest) async { // Ignored } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift index 7192777eb..3403212e7 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift @@ -9,7 +9,7 @@ import ProcessOut /// Events emitted by dynamic checkout module during its lifecycle. @_spi(PO) -public enum PODynamicCheckoutEvent { +public enum PODynamicCheckoutEvent: Sendable { /// Initial event that is sent prior any other event. case willStart diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckoutDefaultInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckoutDefaultInteractor.swift index eb6b26f77..fd4a06d50 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckoutDefaultInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckoutDefaultInteractor.swift @@ -149,7 +149,8 @@ final class DynamicCheckoutDefaultInteractor: logger.debug("Unable to set started state from unsupported state: \(state).") return } - if let paymentMethods = invoice.paymentMethods?.filter(isSupported), !paymentMethods.isEmpty { + // swiftlint:disable:next line_length + if let paymentMethods = invoice.paymentMethods?.filter({ isSupported(paymentMethod: $0) }), !paymentMethods.isEmpty { let startedState = DynamicCheckoutInteractorState.Started( paymentMethods: paymentMethods, isCancellable: configuration.cancelButton?.title.map { !$0.isEmpty } ?? true, diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift index 1ef702a6e..e4ede3a7e 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift @@ -13,10 +13,12 @@ import SwiftUI /// /// For more information about styling specific components, see /// [the dedicated documentation.](https://swiftpackageindex.com/processout/processout-ios/documentation/processoutcoreui) -@available(iOS 14, *) @_spi(PO) +@available(iOS 14, *) +@MainActor public struct PODynamicCheckoutStyle { + @MainActor public struct RegularPaymentMethod { /// Payment method title. @@ -35,6 +37,7 @@ public struct PODynamicCheckoutStyle { } } + @MainActor public struct PaymentSuccess { /// Success message style. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift index bd5513eb7..93bc3320c 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift @@ -20,14 +20,15 @@ extension View { @available(iOS 14, *) extension EnvironmentValues { + @MainActor var dynamicCheckoutStyle: PODynamicCheckoutStyle { - get { self[Key.self] } + get { self[Key.self] ?? .default } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue = PODynamicCheckoutStyle.default + static let defaultValue: PODynamicCheckoutStyle? = nil } } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Utils/ChildProvider/DynamicCheckoutInteractorChildProvider.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Utils/ChildProvider/DynamicCheckoutInteractorChildProvider.swift index 16922623e..6c35ae915 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Utils/ChildProvider/DynamicCheckoutInteractorChildProvider.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Utils/ChildProvider/DynamicCheckoutInteractorChildProvider.swift @@ -9,15 +9,16 @@ /// - NOTE: Your implementation should expect that instances created /// by provider are going to be different every time you call a method. -@MainActor protocol DynamicCheckoutInteractorChildProvider { /// Creates and returns card tokenization interactor. + @MainActor func cardTokenizationInteractor( invoiceId: String, configuration: PODynamicCheckoutPaymentMethod.CardConfiguration ) -> any CardTokenizationInteractor /// Creates and returns native APM interactor.. + @MainActor func nativeAlternativePaymentInteractor( invoiceId: String, gatewayConfigurationId: String ) -> any NativeAlternativePaymentInteractor diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutViewController.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutViewController.swift index e34ec2b01..07912a098 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutViewController.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutViewController.swift @@ -15,7 +15,7 @@ public final class PODynamicCheckoutViewController: UIHostingController /// Creates dynamic checkout view controller. public init( - style: PODynamicCheckoutStyle = PODynamicCheckoutStyle.default, + style: PODynamicCheckoutStyle = .default, configuration: PODynamicCheckoutConfiguration, delegate: PODynamicCheckoutDelegate, completion: @escaping (Result) -> Void diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift index e3a2fd305..51fb802c9 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift @@ -147,7 +147,7 @@ final class DefaultDynamicCheckoutViewModel: ViewModel { private func createExpressMethodsSection( state: DynamicCheckoutInteractorState.Started ) -> DynamicCheckoutViewModelState.Section? { - let expressItems = state.paymentMethods.filter(isExpress).compactMap { paymentMethod in + let expressItems = state.paymentMethods.filter({ isExpress(paymentMethod: $0) }).compactMap { paymentMethod in createExpressPaymentItem(for: paymentMethod, state: state) } guard !expressItems.isEmpty else { diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift index 7815bc0bd..eb0b2e82d 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift @@ -45,7 +45,9 @@ extension DynamicCheckoutViewModelState: AnimationIdentityProvider { [sections.map(\.animationIdentity), actions.map(\.id)] } - static let idle = DynamicCheckoutViewModelState(sections: [], actions: [], isCompleted: false) + static var idle: DynamicCheckoutViewModelState { + .init(sections: [], actions: [], isCompleted: false) + } } extension DynamicCheckoutViewModelState.Section: AnimationIdentityProvider { diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift index 721f48c90..6a3d48794 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift @@ -9,6 +9,7 @@ import SwiftUI @_spi(PO) import ProcessOutCoreUI /// Native alternative payment method screen background style. +@MainActor public struct PONativeAlternativePaymentBackgroundStyle { /// Regular background color. diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift index 4f95a21a9..e4295a901 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift @@ -10,6 +10,8 @@ import SwiftUI /// Defines style for native alternative payment module. @available(iOS 14, *) +@MainActor +@preconcurrency public struct PONativeAlternativePaymentStyle { /// Title style. diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift index 4d0233e1f..713dc845b 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift @@ -19,14 +19,15 @@ extension View { @available(iOS 14, *) extension EnvironmentValues { + @MainActor var nativeAlternativePaymentStyle: PONativeAlternativePaymentStyle { - get { self[Key.self] } + get { self[Key.self] ?? .default } set { self[Key.self] = newValue } } // MARK: - Private Nested Types private struct Key: EnvironmentKey { - static let defaultValue = PONativeAlternativePaymentStyle.default + static let defaultValue: PONativeAlternativePaymentStyle? = nil } } diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/NativeAlternativePaymentContentView.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/NativeAlternativePaymentContentView.swift index d3eaba5e5..3056cc660 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/NativeAlternativePaymentContentView.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/NativeAlternativePaymentContentView.swift @@ -69,7 +69,7 @@ struct NativeAlternativePaymentContentView: View { return (top: sections, center: []) } let index = sections.firstIndex { section in - section.items.contains(where: shouldCenter) + section.items.contains(where: { shouldCenter(item: $0) }) } guard let index else { return (top: sections, center: []) diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift index c76e60cda..edbaa0390 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift @@ -35,5 +35,7 @@ extension NativeAlternativePaymentViewModelState: AnimationIdentityProvider { extension NativeAlternativePaymentViewModelState { /// Idle state. - static let idle = Self(sections: [], actions: [], isCaptured: false, focusedItemId: nil, confirmationDialog: nil) + static var idle: NativeAlternativePaymentViewModelState { + .init(sections: [], actions: [], isCaptured: false, focusedItemId: nil, confirmationDialog: nil) + } } From 5388114db12eee7102bac27b8f888aa7bd9b4897 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 13:59:52 +0200 Subject: [PATCH 06/17] Partially resolve warnings --- .../ProcessOut/Sources/Services/Cards/DefaultCardsService.swift | 2 ++ .../Sources/DesignSystem/AsyncImage/POAsyncImage.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift index 987d12ddc..b0c3aa3a4 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift @@ -38,11 +38,13 @@ final class DefaultCardsService: POCardsService { try await repository.updateCard(request: request) } + @MainActor func tokenize(request: POApplePayPaymentTokenizationRequest) async throws -> POCard { let request = try applePayCardTokenizationRequestMapper.tokenizationRequest(from: request) return try await repository.tokenize(request: request) } + @MainActor func tokenize( request: POApplePayTokenizationRequest, delegate: POApplePayTokenizationDelegate? ) async throws -> POCard { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift index a197d1395..bde77466f 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift @@ -35,7 +35,7 @@ public struct POAsyncImage: View { phase = .empty } } - .backport.task(id: id, priority: .userInitiated, resolveImage) + .backport.task(id: id, priority: .userInitiated) { await resolveImage() } } // MARK: - Private Properties From 8682739e50b3d949ae744403b2193931181d5967 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 14:12:04 +0200 Subject: [PATCH 07/17] Resolve build issues --- .../Generated/Sourcery+Generated.swift | 3 ++ ...veAlternativePaymentMethodInteractor.swift | 50 ++++++++----------- .../Core/Markdown/MarkdownNodeFactory.swift | 5 +- .../MarkdownDebugDescriptionPrinter.swift | 2 +- .../PresentingViewControllerProvider.swift | 1 + 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift b/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift index 2ac99bff5..f619d2723 100644 --- a/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift +++ b/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift @@ -156,6 +156,7 @@ extension POCardsService { /// Tokenize previously authorized payment. @MainActor + @preconcurrency @available(*, deprecated, message: "Use the async method instead.") @discardableResult public func tokenize( @@ -169,6 +170,7 @@ extension POCardsService { /// Authorize given payment request and tokenize it. @MainActor + @preconcurrency @available(*, deprecated, message: "Use the async method instead.") @discardableResult public func tokenize( @@ -183,6 +185,7 @@ extension POCardsService { /// Authorize given payment request and tokenize it. @MainActor + @preconcurrency @available(*, deprecated, message: "Use the async method instead.") @discardableResult public func tokenize( diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/DefaultNativeAlternativePaymentMethodInteractor.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/DefaultNativeAlternativePaymentMethodInteractor.swift index 657d17c99..05f51c0ba 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/DefaultNativeAlternativePaymentMethodInteractor.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/DefaultNativeAlternativePaymentMethodInteractor.swift @@ -398,8 +398,10 @@ final class DefaultNativeAlternativePaymentMethodInteractor: NativeAlternativePa } private func send(event: PONativeAlternativePaymentMethodEvent) { - logger.debug("Did send event: '\(String(describing: event))'") - delegate?.nativeAlternativePaymentMethodDidEmitEvent(event) + MainActor.assumeIsolated { + logger.debug("Did send event: '\(String(describing: event))'") + delegate?.nativeAlternativePaymentMethodDidEmitEvent(event) + } } private func defaultValues( @@ -410,34 +412,26 @@ final class DefaultNativeAlternativePaymentMethodInteractor: NativeAlternativePa completion([:]) return } - if let delegate { - delegate.nativeAlternativePaymentMethodDefaultValues(for: parameters) { [self] values in - assert(Thread.isMainThread, "Completion must be called on main thread.") - var defaultValues: [String: State.ParameterValue] = [:] - parameters.forEach { parameter in - let defaultValue: String - if let value = values[parameter.key] { - switch parameter.type { - case .email, .numeric, .phone, .text: - defaultValue = self.formatted(value: value, type: parameter.type) - case .singleSelect: - precondition( - parameter.availableValues?.map(\.value).contains(value) == true, - "Unknown `singleSelect` parameter value." - ) - defaultValue = value - } - } else { - defaultValue = self.defaultValue(for: parameter) - } - defaultValues[parameter.key] = .init(value: defaultValue, recentErrorMessage: nil) - } - completion(defaultValues) - } - } else { + Task { @MainActor in + let values = await delegate?.nativeAlternativePayment(defaultValuesFor: parameters) ?? [:] var defaultValues: [String: State.ParameterValue] = [:] parameters.forEach { parameter in - defaultValues[parameter.key] = .init(value: defaultValue(for: parameter), recentErrorMessage: nil) + let defaultValue: String + if let value = values[parameter.key] { + switch parameter.type { + case .email, .numeric, .phone, .text: + defaultValue = self.formatted(value: value, type: parameter.type) + case .singleSelect: + precondition( + parameter.availableValues?.map(\.value).contains(value) == true, + "Unknown `singleSelect` parameter value." + ) + defaultValue = value + } + } else { + defaultValue = self.defaultValue(for: parameter) + } + defaultValues[parameter.key] = .init(value: defaultValue, recentErrorMessage: nil) } completion(defaultValues) } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift index 5b5fce162..dc92a0433 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/MarkdownNodeFactory.swift @@ -41,6 +41,7 @@ final class MarkdownParser { // MARK: - Bridging + // swiftlint:disable:next cyclomatic_complexity private func node(from pNode: UnsafeMutablePointer) -> MarkdownNode? { let nodeType = cmark_node_type( UInt32(pNode.pointee.type) @@ -85,11 +86,11 @@ final class MarkdownParser { // MARK: - Nodes Bridging private func documentNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { - return MarkdownDocument(children: children(of: pNode)) + MarkdownDocument(children: children(of: pNode)) } private func blockQuoteNode(from pNode: UnsafeMutablePointer) -> MarkdownNode { - return MarkdownBlockQuote(children: children(of: pNode)) + MarkdownBlockQuote(children: children(of: pNode)) } private func codeBlockNode(from pNode: UnsafeMutablePointer) -> MarkdownNode? { diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift index 5c6b965be..52f0a19fe 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift @@ -69,7 +69,7 @@ final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { } func visit(codeBlock: MarkdownCodeBlock) -> String { - return description(node: codeBlock, nodeName: "Code Block", content: codeBlock.code) + description(node: codeBlock, nodeName: "Code Block", content: codeBlock.code) } func visit(thematicBreak: MarkdownThematicBreak) -> String { diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift index 4216b3e76..8a33bda03 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift @@ -11,6 +11,7 @@ enum PresentingViewControllerProvider { /// Attempts to find view controller that can modally present other view controller. @MainActor + @preconcurrency static func find() -> UIViewController? { let rootViewController = UIApplication.shared .connectedScenes From a9db0da44dfa174c40198e0a5e3f04a6f20f966c Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 14:34:15 +0200 Subject: [PATCH 08/17] Drop AnyObject requirement from 3DS service --- .../Sources/Services/CustomerActions/PO3DS2Service.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift b/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift index 9401d1bc1..b013dcd79 100644 --- a/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift +++ b/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift @@ -8,7 +8,7 @@ // todo(andrii-vysotskyi): add Sendable conformance to PO3DS2Service when releasing 5.0.0 /// This interface provides methods to process 3-D Secure transactions. -public protocol PO3DS2Service: AnyObject { +public protocol PO3DS2Service { /// Asks implementation to create request that will be passed to 3DS Server to create the AReq. func authenticationRequestParameters( From 7e024a4434fc92459e6b0165a082d7ab2e61be31 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 14:36:00 +0200 Subject: [PATCH 09/17] Update test 3DS services --- .../Sources/Api/Utils/Test3DS/POTest3DSService.swift | 7 +++---- .../Sources/Api/Test3DS/POTest3DSService.swift | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift index 8f86b4115..01eaeb7a0 100644 --- a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift @@ -10,18 +10,16 @@ import UIKit /// Service that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access /// Control Server (ACS). Should be used only for testing purposes in sandbox environment. @available(*, deprecated, message: "Use ProcessOutUI.POTest3DSService instead.") -@MainActor -@preconcurrency public final class POTest3DSService: PO3DS2Service { /// Creates service instance. @_disfavoredOverload @available(*, deprecated, message: "Use init that doesn't require arguments.") - public nonisolated init(returnUrl: URL) { + public init(returnUrl: URL) { // Ignored } - nonisolated init() { + init() { // Ignored } @@ -42,6 +40,7 @@ public final class POTest3DSService: PO3DS2Service { ) } + @MainActor public func performChallenge(with parameters: PO3DS2ChallengeParameters) async throws -> PO3DS2ChallengeResult { await withCheckedContinuation { continuation in let alertController = UIAlertController( diff --git a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift index 1fb7b4adb..d55fc9c55 100644 --- a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift @@ -10,16 +10,15 @@ import UIKit /// Service that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access /// Control Server (ACS). Should be used only for testing purposes in sandbox environment. -@MainActor public final class POTest3DSService: PO3DS2Service { /// Creates service instance. @available(*, deprecated, message: "Use init that doesn't accept parameters.") - public nonisolated init(returnUrl: URL) { + public init(returnUrl: URL) { // Ignored } - public nonisolated init() { + public init() { // Ignored } @@ -37,6 +36,7 @@ public final class POTest3DSService: PO3DS2Service { ) } + @MainActor public func performChallenge(with parameters: PO3DS2ChallengeParameters) async throws -> PO3DS2ChallengeResult { guard let presentingViewController = PresentingViewControllerProvider.find() else { throw POFailure(message: "Unable to present 3DS challenge.", code: .generic(.mobile)) From 3372270144e2cc51984badf2568167649fb46bee Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 14:44:50 +0200 Subject: [PATCH 10/17] Use Swift 6 for example --- .../ConfigurationViewModelState.swift | 24 ++++++++----------- Example/project.yml | 1 + 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Example/Example/Sources/UI/Modules/Configuration/ViewModel/ConfigurationViewModelState.swift b/Example/Example/Sources/UI/Modules/Configuration/ViewModel/ConfigurationViewModelState.swift index 5122572e8..b6206363d 100644 --- a/Example/Example/Sources/UI/Modules/Configuration/ViewModel/ConfigurationViewModelState.swift +++ b/Example/Example/Sources/UI/Modules/Configuration/ViewModel/ConfigurationViewModelState.swift @@ -36,18 +36,14 @@ struct ConfigurationViewModelState { extension ConfigurationViewModelState { /// Idle state. - static let idle = ConfigurationViewModelState( - projectId: "", - projectKey: "", - environments: .init(sources: environmentSources, id: \.id, selection: .production), - customerId: "", - merchantId: "" - ) - - // MARK: - Private - - private static let environmentSources: [Environment] = [ - .init(id: .production, name: String(localized: .Configuration.productionEnvironment)), - .init(id: .stage, name: String(localized: .Configuration.stageEnvironment)) - ] + static var idle: ConfigurationViewModelState { + let environmentSources: [Environment] = [ + .init(id: .production, name: String(localized: .Configuration.productionEnvironment)), + .init(id: .stage, name: String(localized: .Configuration.stageEnvironment)) + ] + let environments: PickerData = .init( + sources: environmentSources, id: \.id, selection: .production + ) + return .init(projectId: "", projectKey: "", environments: environments, customerId: "", merchantId: "") + } } diff --git a/Example/project.yml b/Example/project.yml index 32f5316dd..4a9d1a050 100644 --- a/Example/project.yml +++ b/Example/project.yml @@ -12,6 +12,7 @@ settings: CURRENT_PROJECT_VERSION: 1 LOCALIZATION_PREFERS_STRING_CATALOGS: true SWIFT_EMIT_LOC_STRINGS: true + SWIFT_VERSION: 6.0 # SPM Packages packages: From c031e8c00f875ac78084768452f198b1c9604162 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 14:48:39 +0200 Subject: [PATCH 11/17] Fix concurrency warning --- .../Delegate/POCardTokenizationDelegate.swift | 2 ++ .../Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift | 2 ++ .../DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift index 6192f58dc..40057f9bc 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift @@ -5,6 +5,8 @@ // Created by Andrii Vysotskyi on 09.08.2023. // +// todo(andrii-vysotskyi): add Sendable requirement when releasing 5.0.0 + import ProcessOut /// Card tokenization module delegate definition. diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift index 806a692e1..578c2e362 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift @@ -5,6 +5,8 @@ // Created by Andrii Vysotskyi on 03.11.2023. // +// todo(andrii-vysotskyi): add Sendable requirement when releasing 5.0.0 + import ProcessOut /// Card update module delegate definition. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift index 0ff8d6152..4fbdb0155 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift @@ -10,7 +10,7 @@ import PassKit /// Dynamic checkout module delegate. @_spi(PO) -public protocol PODynamicCheckoutDelegate: AnyObject { +public protocol PODynamicCheckoutDelegate: AnyObject, Sendable { /// Invoked when module emits dynamic checkout event. /// - NOTE: default implementation does nothing. @@ -25,7 +25,7 @@ public protocol PODynamicCheckoutDelegate: AnyObject { func dynamicCheckout( willAuthorizeInvoiceWith request: inout POInvoiceAuthorizationRequest, using paymentMethod: PODynamicCheckoutPaymentMethod - ) async -> sending PO3DS2Service + ) async -> PO3DS2Service /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. From 01d510371aabd23248856cac850257a82fc1e778 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 15:23:16 +0200 Subject: [PATCH 12/17] Remove actor --- .../ProcessOut/Sources/Services/Cards/DefaultCardsService.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift index b0c3aa3a4..987d12ddc 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift @@ -38,13 +38,11 @@ final class DefaultCardsService: POCardsService { try await repository.updateCard(request: request) } - @MainActor func tokenize(request: POApplePayPaymentTokenizationRequest) async throws -> POCard { let request = try applePayCardTokenizationRequestMapper.tokenizationRequest(from: request) return try await repository.tokenize(request: request) } - @MainActor func tokenize( request: POApplePayTokenizationRequest, delegate: POApplePayTokenizationDelegate? ) async throws -> POCard { From 3346efcefb7bf02b798a29a8ae5a50274f365322 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 15:43:18 +0200 Subject: [PATCH 13/17] Update constants --- Example/Example/Sources/Application/Constants.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Example/Example/Sources/Application/Constants.swift b/Example/Example/Sources/Application/Constants.swift index 9865ded4d..c95d86c28 100644 --- a/Example/Example/Sources/Application/Constants.swift +++ b/Example/Example/Sources/Application/Constants.swift @@ -8,6 +8,7 @@ import Foundation import ProcessOut +@MainActor enum Constants { /// Project configuration. From f9c2d02c8f45dfc419cfd225ed427ecb495ba04c Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 15:45:54 +0200 Subject: [PATCH 14/17] Update readme --- Example/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Example/README.md b/Example/README.md index c594bdef4..83ff9c140 100644 --- a/Example/README.md +++ b/Example/README.md @@ -16,6 +16,7 @@ Project demonstrates multiple flows that ProcessOut framework is capable of. 3. Set constants defined in `Example/Sources/Application/Constants.swift` file to your project credentials. E.g.: ```swift +@MainActor enum Constants { /// Project configuration. From 5da60ab26aa4b24792777dcda2e802e17b2dacb6 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 15:46:28 +0200 Subject: [PATCH 15/17] Add missing preconcurrency --- .../Sources/Api/Utils/Test3DS/POTest3DSService.swift | 6 +++--- .../ApplePay/POApplePayTokenizationDelegate.swift | 5 ++--- .../Sources/Services/CustomerActions/PO3DS2Service.swift | 5 ++--- .../PONativeAlternativePaymentMethodDelegate.swift | 3 ++- .../ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift | 6 +++--- .../Delegate/POCardTokenizationDelegate.swift | 5 ++--- .../Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift | 5 ++--- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift index 01eaeb7a0..19e7989f3 100644 --- a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift @@ -10,16 +10,17 @@ import UIKit /// Service that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access /// Control Server (ACS). Should be used only for testing purposes in sandbox environment. @available(*, deprecated, message: "Use ProcessOutUI.POTest3DSService instead.") +@MainActor public final class POTest3DSService: PO3DS2Service { /// Creates service instance. @_disfavoredOverload @available(*, deprecated, message: "Use init that doesn't require arguments.") - public init(returnUrl: URL) { + public nonisolated init(returnUrl: URL) { // Ignored } - init() { + nonisolated init() { // Ignored } @@ -40,7 +41,6 @@ public final class POTest3DSService: PO3DS2Service { ) } - @MainActor public func performChallenge(with parameters: PO3DS2ChallengeParameters) async throws -> PO3DS2ChallengeResult { await withCheckedContinuation { continuation in let alertController = UIAlertController( diff --git a/Sources/ProcessOut/Sources/Services/Cards/Coordinators/ApplePay/POApplePayTokenizationDelegate.swift b/Sources/ProcessOut/Sources/Services/Cards/Coordinators/ApplePay/POApplePayTokenizationDelegate.swift index af66ef3ab..9f305831d 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/Coordinators/ApplePay/POApplePayTokenizationDelegate.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/Coordinators/ApplePay/POApplePayTokenizationDelegate.swift @@ -5,11 +5,10 @@ // Created by Andrii Vysotskyi on 05.09.2024. // -// todo(andrii-vysotskyi): add Sendable conformance to POApplePayTokenizationDelegate when releasing 5.0.0 - import PassKit -public protocol POApplePayTokenizationDelegate: AnyObject { +@preconcurrency +public protocol POApplePayTokenizationDelegate: Sendable { // swiftlint:disable:this class_delegate_protocol /// Sent to the delegate after the user has acted on the payment request and it was tokenized by ProcessOut. @MainActor diff --git a/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift b/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift index b013dcd79..1e5f81978 100644 --- a/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift +++ b/Sources/ProcessOut/Sources/Services/CustomerActions/PO3DS2Service.swift @@ -5,10 +5,9 @@ // Created by Andrii Vysotskyi on 18.09.2024. // -// todo(andrii-vysotskyi): add Sendable conformance to PO3DS2Service when releasing 5.0.0 - /// This interface provides methods to process 3-D Secure transactions. -public protocol PO3DS2Service { +@preconcurrency +public protocol PO3DS2Service: Sendable { /// Asks implementation to create request that will be passed to 3DS Server to create the AReq. func authenticationRequestParameters( diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift index dfddd8570..d66abeb44 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift @@ -6,7 +6,8 @@ // /// Native alternative payment module delegate definition. -public protocol PONativeAlternativePaymentMethodDelegate: AnyObject { +@preconcurrency +public protocol PONativeAlternativePaymentMethodDelegate: AnyObject, Sendable { /// Invoked when module emits event. @MainActor diff --git a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift index d55fc9c55..1fb7b4adb 100644 --- a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift @@ -10,15 +10,16 @@ import UIKit /// Service that emulates the normal 3DS authentication flow but does not actually make any calls to a real Access /// Control Server (ACS). Should be used only for testing purposes in sandbox environment. +@MainActor public final class POTest3DSService: PO3DS2Service { /// Creates service instance. @available(*, deprecated, message: "Use init that doesn't accept parameters.") - public init(returnUrl: URL) { + public nonisolated init(returnUrl: URL) { // Ignored } - public init() { + public nonisolated init() { // Ignored } @@ -36,7 +37,6 @@ public final class POTest3DSService: PO3DS2Service { ) } - @MainActor public func performChallenge(with parameters: PO3DS2ChallengeParameters) async throws -> PO3DS2ChallengeResult { guard let presentingViewController = PresentingViewControllerProvider.find() else { throw POFailure(message: "Unable to present 3DS challenge.", code: .generic(.mobile)) diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift index 40057f9bc..ca69341eb 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift @@ -5,12 +5,11 @@ // Created by Andrii Vysotskyi on 09.08.2023. // -// todo(andrii-vysotskyi): add Sendable requirement when releasing 5.0.0 - import ProcessOut /// Card tokenization module delegate definition. -public protocol POCardTokenizationDelegate: AnyObject { +@preconcurrency +public protocol POCardTokenizationDelegate: AnyObject, Sendable { /// Invoked when module emits event. @MainActor diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift index 578c2e362..67f83f5a6 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift @@ -5,12 +5,11 @@ // Created by Andrii Vysotskyi on 03.11.2023. // -// todo(andrii-vysotskyi): add Sendable requirement when releasing 5.0.0 - import ProcessOut /// Card update module delegate definition. -public protocol POCardUpdateDelegate: AnyObject { +@preconcurrency +public protocol POCardUpdateDelegate: AnyObject, Sendable { /// Invoked when module emits event. @MainActor From 31f9fc43a102cf9e887c2ca0cb3d0fade4eeaa87 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 15:49:54 +0200 Subject: [PATCH 16/17] Resolve warning --- .../ProcessOut/Sources/Services/Cards/DefaultCardsService.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift index 987d12ddc..b0c3aa3a4 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift @@ -38,11 +38,13 @@ final class DefaultCardsService: POCardsService { try await repository.updateCard(request: request) } + @MainActor func tokenize(request: POApplePayPaymentTokenizationRequest) async throws -> POCard { let request = try applePayCardTokenizationRequestMapper.tokenizationRequest(from: request) return try await repository.tokenize(request: request) } + @MainActor func tokenize( request: POApplePayTokenizationRequest, delegate: POApplePayTokenizationDelegate? ) async throws -> POCard { From 083ac5b6fb527364e34159f25b5f450548abfa98 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Wed, 16 Oct 2024 15:58:19 +0200 Subject: [PATCH 17/17] Fix compilation issue --- .../ApplePay/ViewModel/ApplePayTokenizationCoordinator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/Example/Sources/UI/Modules/ApplePay/ViewModel/ApplePayTokenizationCoordinator.swift b/Example/Example/Sources/UI/Modules/ApplePay/ViewModel/ApplePayTokenizationCoordinator.swift index 1211f5ef9..345b3ac5c 100644 --- a/Example/Example/Sources/UI/Modules/ApplePay/ViewModel/ApplePayTokenizationCoordinator.swift +++ b/Example/Example/Sources/UI/Modules/ApplePay/ViewModel/ApplePayTokenizationCoordinator.swift @@ -10,12 +10,12 @@ import ProcessOut final class ApplePayTokenizationCoordinator: POApplePayTokenizationDelegate { - init(didTokenizeCard: @escaping (POCard) async throws -> Void) { + init(didTokenizeCard: @escaping @Sendable (POCard) async throws -> Void) { self.didTokenizeCard = didTokenizeCard } /// Closure that is called when invoice is authorized. - let didTokenizeCard: (POCard) async throws -> Void + let didTokenizeCard: @Sendable (POCard) async throws -> Void // MARK: -