Skip to content

Commit c4810b3

Browse files
authored
[Woo POS][Modularization] Batch 1: Make Woo app dependencies reusable for PointOfSale module (#15968)
2 parents 3342f01 + 41e86f1 commit c4810b3

File tree

116 files changed

+548
-363
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+548
-363
lines changed

BuildTools/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/Sources/PointOfSale/Analytics/POSAnalyticsEvent.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,34 @@ public extension PaymentMethod {
404404
}
405405
}
406406
}
407+
408+
extension WooAnalyticsEvent {
409+
struct Orders {
410+
// MARK: - Order Creation Events
411+
412+
/// Matches errors on Android for consistency
413+
/// Only coupon tracking is relevant for now
414+
enum OrderCreationErrorType: String {
415+
case invalidCoupon = "INVALID_COUPON"
416+
}
417+
418+
static func orderCreationFailed(
419+
usesGiftCard: Bool,
420+
errorContext: String,
421+
errorDescription: String,
422+
errorType: OrderCreationErrorType? = nil
423+
) -> WooAnalyticsEvent {
424+
var properties: [String: WooAnalyticsEventPropertyType] = [
425+
"use_gift_card": usesGiftCard,
426+
"error_context": errorContext,
427+
"error_description": errorDescription
428+
]
429+
430+
if let errorType {
431+
properties["error_type"] = errorType.rawValue
432+
}
433+
434+
return WooAnalyticsEvent(statName: .orderCreationFailed, properties: properties)
435+
}
436+
}
437+
}
Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import Foundation
2+
import SwiftUI
23
import WooFoundation
34
import Experiments
45
import protocol Yosemite.Action
56
import struct Yosemite.Site
67

78
/// Main dependency provider protocol for POS module
8-
/// This abstracts away direct ServiceLocator access
9+
/// This abstracts away dependencies from the main Woo app
910
public protocol POSDependencyProviding {
1011
var analytics: POSAnalyticsProviding { get }
1112
var currency: POSCurrencySettingsProviding { get }
1213
var featureFlags: POSFeatureFlagProviding { get }
13-
var session: POSSessionManagerProviding { get }
1414
var connectivity: POSConnectivityProviding { get }
15+
var externalNavigation: POSExternalNavigationProviding { get }
16+
var externalViews: POSExternalViewProviding { get }
1517
}
1618

1719
public protocol POSAnalyticsProviding {
@@ -21,10 +23,6 @@ public protocol POSAnalyticsProviding {
2123
func track(_ stat: WooAnalyticsStat)
2224
}
2325

24-
public protocol POSSessionManagerProviding {
25-
var defaultSite: Site? { get }
26-
}
27-
2826
public protocol POSFeatureFlagProviding {
2927
func isFeatureFlagEnabled(_ flag: FeatureFlag) -> Bool
3028
}
@@ -36,3 +34,13 @@ public protocol POSCurrencySettingsProviding {
3634
public protocol POSConnectivityProviding {
3735
var connectivityObserver: ConnectivityObserver { get }
3836
}
37+
38+
/// Navigation to the Woo app service abstraction
39+
public protocol POSExternalNavigationProviding {
40+
func navigateToCreateOrder()
41+
}
42+
43+
/// External view service abstraction
44+
public protocol POSExternalViewProviding {
45+
func createSupportFormView(isPresented: Binding<Bool>) -> AnyView
46+
}

Modules/Sources/PointOfSale/Environment/POSEnvironmentKeys.swift

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@ public struct POSFeatureFlagsKey: EnvironmentKey {
2020
public static let defaultValue: POSFeatureFlagProviding = EmptyPOSFeatureFlags()
2121
}
2222

23-
/// Environment key for POS session manager
24-
public struct POSSessionManagerKey: EnvironmentKey {
25-
public static let defaultValue: POSSessionManagerProviding = EmptyPOSSessionManager()
26-
}
27-
2823
/// Environment key for POS connectivity
2924
public struct POSConnectivityKey: EnvironmentKey {
3025
public static let defaultValue: POSConnectivityProviding = EmptyPOSConnectivityProvider()
3126
}
3227

28+
/// Environment key for POS navigation service
29+
public struct POSExternalNavigationKey: EnvironmentKey {
30+
public static let defaultValue: POSExternalNavigationProviding = EmptyPOSExternalNavigation()
31+
}
32+
33+
/// Environment key for POS external view service
34+
public struct POSExternalViewKey: EnvironmentKey {
35+
public static let defaultValue: POSExternalViewProviding = EmptyPOSExternalView()
36+
}
37+
3338
public extension EnvironmentValues {
3439
var posAnalytics: POSAnalyticsProviding {
3540
get { self[POSAnalyticsKey.self] }
@@ -46,21 +51,26 @@ public extension EnvironmentValues {
4651
set { self[POSFeatureFlagsKey.self] = newValue }
4752
}
4853

49-
var posSession: POSSessionManagerProviding {
50-
get { self[POSSessionManagerKey.self] }
51-
set { self[POSSessionManagerKey.self] = newValue }
52-
}
53-
5454
var posConnectivityProvider: POSConnectivityProviding {
5555
get { self[POSConnectivityKey.self] }
5656
set { self[POSConnectivityKey.self] = newValue }
5757
}
58+
59+
var posExternalNavigation: POSExternalNavigationProviding {
60+
get { self[POSExternalNavigationKey.self] }
61+
set { self[POSExternalNavigationKey.self] = newValue }
62+
}
63+
64+
var posExternalViews: POSExternalViewProviding {
65+
get { self[POSExternalViewKey.self] }
66+
set { self[POSExternalViewKey.self] = newValue }
67+
}
5868
}
5969

6070
// MARK: - Empty Default Values
6171

62-
public struct EmptyPOSSessionManager: POSSessionManagerProviding {
63-
public var defaultSite: Site? = nil
72+
public struct EmptyPOSExternalNavigation: POSExternalNavigationProviding {
73+
public func navigateToCreateOrder() {}
6474
public init() {}
6575
}
6676

@@ -94,3 +104,8 @@ public struct EmptyPOSAnalytics: POSAnalyticsProviding {
94104
public func track(_ stat: WooFoundationCore.WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType], error: any Error) {}
95105
public init() {}
96106
}
107+
108+
public struct EmptyPOSExternalView: POSExternalViewProviding {
109+
public func createSupportFormView(isPresented: Binding<Bool>) -> AnyView { AnyView(EmptyView()) }
110+
public init() {}
111+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import UIKit
2+
3+
/// Point of Sale specific UIImage extensions
4+
/// Contains only the UIImage extensions needed by the POS module to minimize external dependencies
5+
extension UIImage {
6+
7+
/// App icon (iPhone size) - used in receipt eligibility banner
8+
///
9+
static var appIconDefault: UIImage {
10+
return UIImage(named: "AppIcon60x60", in: .main, compatibleWith: nil)!
11+
}
12+
13+
/// Card Reader Update arrow - used in reader update progress
14+
///
15+
static var cardReaderUpdateProgressArrow: UIImage {
16+
return UIImage(named: "card-reader-update-progress-arrow", in: .main, compatibleWith: nil)!
17+
}
18+
19+
/// Card Reader Update checkmark - used in reader update progress completion
20+
///
21+
static var cardReaderUpdateProgressCheckmark: UIImage {
22+
return UIImage(named: "card-reader-update-progress-checkmark", in: .main, compatibleWith: nil)!
23+
}
24+
}

WooCommerce/Classes/Extensions/Array+Helpers.swift renamed to Modules/Sources/WooFoundation/Extensions/Array+Helpers.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import Foundation
22

3-
43
// MARK: - Array Helpers
54
//
6-
extension Array {
5+
public extension Array {
76
/// Removes and returns the first element in the array. If any!
87
///
98
mutating func popFirst() -> Element? {
@@ -27,7 +26,7 @@ extension Array {
2726

2827
// MARK: - Sequence Helpers
2928
//
30-
extension Sequence {
29+
public extension Sequence {
3130
/// Get the keypaths for a elemtents in a sequence.
3231
///
3332
func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
@@ -40,12 +39,12 @@ extension Sequence {
4039
}
4140
}
4241

43-
extension Sequence where Element: Numeric {
42+
public extension Sequence where Element: Numeric {
4443
/// Returns the sum of all elements in the collection.
4544
func sum() -> Element { return reduce(0, +) }
4645
}
4746

48-
extension Sequence where Element: Equatable {
47+
public extension Sequence where Element: Equatable {
4948
/// Returns the sequence with any duplicate elements after the first one removed.
5049
func removingDuplicates() -> [Element] {
5150
var result = [Element]()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import SwiftUI
2+
import SafariServices
3+
4+
/// SwiftUI interface for UIKit SFSafariViewController
5+
/// Provides a visible interface for web browsing, and Safari features
6+
///
7+
public struct SafariView: UIViewControllerRepresentable {
8+
9+
public let url: URL
10+
11+
public init(url: URL) {
12+
self.url = url
13+
}
14+
15+
public func makeUIViewController(context: UIViewControllerRepresentableContext<SafariView>) -> SFSafariViewController {
16+
return SFSafariViewController(url: url)
17+
}
18+
19+
public func updateUIViewController(_ uiViewController: SFSafariViewController,
20+
context: UIViewControllerRepresentableContext<SafariView>) {
21+
22+
}
23+
}
Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ public struct IndefiniteCircularProgressViewStyle: ProgressViewStyle {
1515
@State private var viewRotation: Angle = .radians(0)
1616
@State private var arcTimer: Timer?
1717

18+
public init(
19+
size: CGFloat,
20+
lineWidth: CGFloat = Constants.lineWidth,
21+
lineCap: CGLineCap = .round,
22+
circleColor: Color = Color(.primaryButtonBackground).opacity(Constants.backgroundOpacity),
23+
fillColor: Color = Color(.primaryButtonBackground),
24+
arcEnd: Double = Constants.initialArcEnd,
25+
rotation: Angle = Constants.threeQuarterRotation,
26+
viewRotation: Angle = .radians(0),
27+
arcTimer: Timer? = nil
28+
) {
29+
self.size = size
30+
self.lineWidth = lineWidth
31+
self.lineCap = lineCap
32+
self.circleColor = circleColor
33+
self.fillColor = fillColor
34+
self.arcEnd = arcEnd
35+
self.rotation = rotation
36+
self.viewRotation = viewRotation
37+
self.arcTimer = arcTimer
38+
}
39+
1840
public func makeBody(configuration: ProgressViewStyleConfiguration) -> some View {
1941
VStack {
2042
ZStack {
@@ -78,17 +100,17 @@ public struct IndefiniteCircularProgressViewStyle: ProgressViewStyle {
78100
}
79101
}
80102

81-
private extension IndefiniteCircularProgressViewStyle {
103+
public extension IndefiniteCircularProgressViewStyle {
82104
enum Constants {
83-
static let lineWidth: CGFloat = 10.0
84-
static let backgroundOpacity: CGFloat = 0.2
105+
public static let lineWidth: CGFloat = 10.0
106+
public static let backgroundOpacity: CGFloat = 0.2
85107

86-
static let initialArcStart: Double = 0
87-
static let initialArcEnd: Double = 0.05
88-
static let fullCircle: Double = 1
108+
public static let initialArcStart: Double = 0
109+
public static let initialArcEnd: Double = 0.05
110+
public static let fullCircle: Double = 1
89111

90-
static let threeQuarterRotation: Angle = .radians((9 * Double.pi)/6)
91-
static let fullRotation: Angle = .radians(Double.pi * 2)
112+
public static let threeQuarterRotation: Angle = .radians((9 * Double.pi)/6)
113+
public static let fullRotation: Angle = .radians(Double.pi * 2)
92114
}
93115

94116
enum Localization {
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ import Combine
33

44
@available(iOS 17.0, *)
55
@Observable
6-
final class KeyboardObserver {
7-
private(set) var isKeyboardVisible: Bool = false
8-
private(set) var keyboardHeight: CGFloat = 0
6+
public final class KeyboardObserver {
7+
private(set) public var isKeyboardVisible: Bool = false
8+
private(set) public var keyboardHeight: CGFloat = 0
99

1010
/// When an external keyboard is in use, iPadOS shows a quicktype bar at the bottom of the screen.
1111
/// This is reported as a keyboard with height, so `isKeyboardVisible` will be true and
1212
/// keyboard height will be > 0.
1313
/// However, it's much less of an impingement on the view, so there may be no modification to the view required.
1414
/// `isFullSizeKeyboardVisible` is true when the full software keyboard is shown.
15-
var isFullSizeKeyboardVisible: Bool {
15+
public var isFullSizeKeyboardVisible: Bool {
1616
return keyboardHeight > Constants.hardwareKeyboardHelperBarHeightThreshold
1717
}
1818

1919
private var cancellables = Set<AnyCancellable>()
2020

21-
init() {
21+
public init() {
2222
NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardWillShowNotification)
2323
.merge(with: NotificationCenter.Publisher(center: .default, name: UIResponder.keyboardDidShowNotification))
2424
.receive(on: DispatchQueue.main)
@@ -64,24 +64,24 @@ private struct KeyboardObserverKey: EnvironmentKey {
6464
}
6565

6666
@available(iOS 17.0, *)
67-
extension EnvironmentValues {
67+
public extension EnvironmentValues {
6868
var keyboardObserver: KeyboardObserver {
6969
get { self[KeyboardObserverKey.self] }
7070
set { self[KeyboardObserverKey.self] = newValue }
7171
}
7272
}
7373

7474
@available(iOS 17.0, *)
75-
struct KeyboardObserverProvider: ViewModifier {
75+
public struct KeyboardObserverProvider: ViewModifier {
7676
@State private var observer = KeyboardObserver()
7777

78-
func body(content: Content) -> some View {
78+
public func body(content: Content) -> some View {
7979
content.environment(\.keyboardObserver, observer)
8080
}
8181
}
8282

8383
@available(iOS 17.0, *)
84-
extension View {
84+
public extension View {
8585
func injectKeyboardObserver() -> some View {
8686
modifier(KeyboardObserverProvider())
8787
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import SwiftUI
2+
import struct WooFoundation.ScrollableVStack
23

34
/// Wraps a VStack inside a ScrollView, ensuring the content expands to fill the available space
45
///
5-
struct ScrollableVStack<Content: View>: View {
6+
public struct ScrollableVStack<Content: View>: View {
67
let alignment: HorizontalAlignment
78
let padding: CGFloat
89
let spacing: CGFloat?
910
let content: Content
1011

11-
init(
12+
public init(
1213
alignment: HorizontalAlignment = .center,
1314
padding: CGFloat = 24,
1415
spacing: CGFloat? = nil,
@@ -20,7 +21,7 @@ struct ScrollableVStack<Content: View>: View {
2021
self.content = content()
2122
}
2223

23-
var body: some View {
24+
public var body: some View {
2425
GeometryReader { geometry in
2526
ScrollView {
2627
VStack(alignment: alignment, spacing: spacing) {

0 commit comments

Comments
 (0)