diff --git a/Modules/Package.swift b/Modules/Package.swift index 8fedc907b47..6c2a0cf9810 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -67,6 +67,10 @@ let package = Package( name: "Yosemite", targets: ["Yosemite"] ), + .library( + name: "PointOfSale", + targets: ["PointOfSale"] + ), ], dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.2.0"), @@ -224,6 +228,15 @@ let package = Package( .product(name: "WordPressEditor", package: "AztecEditor-iOS"), ] ), + .target( + name: "PointOfSale", + dependencies: [ + "Experiments", + "WooFoundation", + "Yosemite" + // Additional dependencies will be added as needed when moving files + ] + ), .testTarget( name: "ExperimentsTests", dependencies: [ @@ -289,6 +302,12 @@ let package = Package( .process("Resources"), .process("../NetworkingTests/Responses") ] + ), + .testTarget( + name: "PointOfSaleTests", + dependencies: [ + "PointOfSale" + ] ) ] ) @@ -368,6 +387,7 @@ enum XcodeSupport { "WordPressUI", "WPMediaPicker", "Yosemite", + "PointOfSale", .product(name: "Alamofire", package: "Alamofire"), .product(name: "Algorithms", package: "swift-algorithms"), .product(name: "AutomatticAbout", package: "AutomatticAbout-swift"), @@ -405,6 +425,7 @@ enum XcodeSupport { "Fakes", "TestKit", "WordPressShared", + "PointOfSale", // TODO: Remove after POS modularization .product(name: "Aztec", package: "AztecEditor-iOS"), .product(name: "BuildkiteTestCollector", package: "test-collector-swift"), .product(name: "ViewControllerPresentationSpy", package: "ViewControllerPresentationSpy"), diff --git a/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSale.swift b/Modules/Sources/PointOfSale/Analytics/POSAnalyticsEvent.swift similarity index 80% rename from WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSale.swift rename to Modules/Sources/PointOfSale/Analytics/POSAnalyticsEvent.swift index ee93978e17b..adfb5f1a384 100644 --- a/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSale.swift +++ b/Modules/Sources/PointOfSale/Analytics/POSAnalyticsEvent.swift @@ -3,10 +3,11 @@ import enum Yosemite.POSItemType import enum Yosemite.POSItem import struct Yosemite.POSSimpleProduct import struct Yosemite.POSVariation -import enum WooFoundation.CountryCode import enum Yosemite.PaymentMethod +import WooFoundation -extension WooAnalyticsEvent { +/// POS-specific analytics events +public extension WooAnalyticsEvent { enum PointOfSale { /// Event property Key. private enum Key { @@ -39,16 +40,16 @@ extension WooAnalyticsEvent { static let scanValue = "scan_value" } - static func paymentsOnboardingShown() -> WooAnalyticsEvent { + public static func paymentsOnboardingShown() -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSalePaymentsOnboardingShown, properties: [:]) } - static func paymentsOnboardingDismissed(onboardingState: CardPresentPaymentOnboardingState) -> WooAnalyticsEvent { + public static func paymentsOnboardingDismissed(onboardingState: CardPresentPaymentOnboardingState) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSalePaymentsOnboardingDismissed, properties: [Key.paymentsOnboardingState: onboardingState.reasonForAnalytics]) } - static func addItemToCart( + public static func addItemToCart( sourceView: WooAnalyticsEvent.PointOfSale.SourceView? = nil, sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType, itemType: WooAnalyticsEvent.PointOfSale.ItemType, @@ -75,7 +76,7 @@ extension WooAnalyticsEvent { ) } - static func itemRemovedFromCart( + public static func itemRemovedFromCart( sourceView: WooAnalyticsEvent.PointOfSale.SourceView, itemType: WooAnalyticsEvent.PointOfSale.ItemType, productType: WooAnalyticsEvent.PointOfSale.CartItemProductType? = nil @@ -95,7 +96,7 @@ extension WooAnalyticsEvent { ) } - static func checkoutTapped(purchasableItemsInCart: Int, couponsInCart: Int) -> WooAnalyticsEvent { + public static func checkoutTapped(purchasableItemsInCart: Int, couponsInCart: Int) -> WooAnalyticsEvent { WooAnalyticsEvent( statName: .pointOfSaleCheckoutTapped, properties: [ @@ -108,11 +109,11 @@ extension WooAnalyticsEvent { /// Tracks the time elapsed preparing reader for payment, after successful order creation /// - Parameter waitingTime: Elapsed time from Order creation to card ready for payment /// - static func cardReaderReadyForCardPayment(waitingTime: Double) -> WooAnalyticsEvent { + public static func cardReaderReadyForCardPayment(waitingTime: Double) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleReaderReadyForCardPayment, properties: [Key.waitingTime: "\(waitingTime)"]) } - static func cardPresentCollectPaymentSuccess(forGatewayID: String?, + public static func cardPresentCollectPaymentSuccess(forGatewayID: String?, countryCode: CountryCode, paymentMethod: PaymentMethod, cardReaderModel: String?, @@ -134,34 +135,34 @@ extension WooAnalyticsEvent { ]) } - static func cashCollectPaymentSuccess(millisecondsSinceCustomerIteractionStarted: Double) -> WooAnalyticsEvent { + public static func cashCollectPaymentSuccess(millisecondsSinceCustomerIteractionStarted: Double) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleCashCollectPaymentSuccess, properties: [ Key.millisecondsSinceCustomerInteractionStarted: "\(millisecondsSinceCustomerIteractionStarted)", ]) } - static func searchButtonTapped(itemListType: ItemListType) -> WooAnalyticsEvent { + public static func searchButtonTapped(itemListType: ItemListType) -> WooAnalyticsEvent { WooAnalyticsEvent( statName: .pointOfSaleSearchButtonTapped, properties: [Key.sourceView: SourceView(itemListType: itemListType).rawValue] ) } - static func itemsHeaderTapped(itemListType: ItemListType) -> WooAnalyticsEvent { + public static func itemsHeaderTapped(itemListType: ItemListType) -> WooAnalyticsEvent { WooAnalyticsEvent( statName: .pointOfSaleItemsHeaderTapped, properties: [Key.type: SourceView(itemListType: itemListType).rawValue] ) } - static func preSearchRecentTermTapped(itemListType: ItemListType) -> WooAnalyticsEvent { + public static func preSearchRecentTermTapped(itemListType: ItemListType) -> WooAnalyticsEvent { WooAnalyticsEvent( statName: .pointOfSalePreSearchRecentTermTapped, properties: [Key.sourceView: SourceView(itemListType: itemListType).rawValue] ) } - static func itemsPullToRefresh( + public static func itemsPullToRefresh( sourceView: WooAnalyticsEvent.PointOfSale.SourceView, sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType ) -> WooAnalyticsEvent { @@ -171,7 +172,7 @@ extension WooAnalyticsEvent { ) } - static func itemsNextPageLoaded( + public static func itemsNextPageLoaded( sourceView: WooAnalyticsEvent.PointOfSale.SourceView, sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType ) -> WooAnalyticsEvent { @@ -181,7 +182,7 @@ extension WooAnalyticsEvent { ) } - static func pointOfSaleSearchRemoteResultsFetched(itemType: POSItemType, + public static func pointOfSaleSearchRemoteResultsFetched(itemType: POSItemType, resultsCount: Int, millisecondsSinceRequestSent: Int) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleSearchRemoteResultsFetched, @@ -192,7 +193,7 @@ extension WooAnalyticsEvent { ]) } - static func pointOfSaleItemsFetched(itemType: POSItemType, + public static func pointOfSaleItemsFetched(itemType: POSItemType, totalItems: Int) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleItemsFetched, properties: [ @@ -201,7 +202,7 @@ extension WooAnalyticsEvent { ]) } - static func barcodeScanningSuccess(scanDurationMs: Int, barcodeLength: Int) -> WooAnalyticsEvent { + public static func barcodeScanningSuccess(scanDurationMs: Int, barcodeLength: Int) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScanningSuccess, properties: [ Key.scanDurationMs: "\(scanDurationMs)", @@ -209,7 +210,7 @@ extension WooAnalyticsEvent { ]) } - static func barcodeScanningFailed(scanDurationMs: Int, barcodeLength: Int, failReason: String) -> WooAnalyticsEvent { + public static func barcodeScanningFailed(scanDurationMs: Int, barcodeLength: Int, failReason: String) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScanningFailed, properties: [ Key.scanDurationMs: "\(scanDurationMs)", @@ -218,12 +219,12 @@ extension WooAnalyticsEvent { ]) } - static func barcodeScannerSetupScannerSelected(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { + public static func barcodeScannerSetupScannerSelected(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupScannerSelected, properties: [Key.scanner: scanner.analyticsName]) } - static func barcodeScannerSetupNextTapped(scanner: PointOfSaleBarcodeScannerType, step: String) -> WooAnalyticsEvent { + public static func barcodeScannerSetupNextTapped(scanner: PointOfSaleBarcodeScannerType, step: String) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupNextTapped, properties: [ Key.scanner: scanner.analyticsName, @@ -231,7 +232,7 @@ extension WooAnalyticsEvent { ]) } - static func barcodeScannerSetupBackTapped(scanner: PointOfSaleBarcodeScannerType, step: String) -> WooAnalyticsEvent { + public static func barcodeScannerSetupBackTapped(scanner: PointOfSaleBarcodeScannerType, step: String) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupBackTapped, properties: [ Key.scanner: scanner.analyticsName, @@ -239,17 +240,17 @@ extension WooAnalyticsEvent { ]) } - static func barcodeScannerSetupOpenSystemSettingsTapped(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { + public static func barcodeScannerSetupOpenSystemSettingsTapped(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupOpenSystemSettingsTapped, properties: [Key.scanner: scanner.analyticsName]) } - static func barcodeScannerSetupTestScanSuccess(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { + public static func barcodeScannerSetupTestScanSuccess(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupTestScanSuccess, properties: [Key.scanner: scanner.analyticsName]) } - static func barcodeScannerSetupTestScanFailed(scanner: PointOfSaleBarcodeScannerType, scanValue: String) -> WooAnalyticsEvent { + public static func barcodeScannerSetupTestScanFailed(scanner: PointOfSaleBarcodeScannerType, scanValue: String) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupTestScanFailed, properties: [ Key.scanner: scanner.analyticsName, @@ -257,12 +258,12 @@ extension WooAnalyticsEvent { ]) } - static func barcodeScannerSetupTestScanTimedOut(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { + public static func barcodeScannerSetupTestScanTimedOut(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupTestScanTimedOut, properties: [Key.scanner: scanner.analyticsName]) } - static func barcodeScannerSetupDismissed(scanner: PointOfSaleBarcodeScannerType? = nil, step: String? = nil) -> WooAnalyticsEvent { + public static func barcodeScannerSetupDismissed(scanner: PointOfSaleBarcodeScannerType? = nil, step: String? = nil) -> WooAnalyticsEvent { var properties: [String: String] = [:] if let scanner { properties[Key.scanner] = scanner.analyticsName @@ -274,12 +275,12 @@ extension WooAnalyticsEvent { properties: properties) } - static func barcodeScannerSetupRetryTapped(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { + public static func barcodeScannerSetupRetryTapped(scanner: PointOfSaleBarcodeScannerType) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupRetryTapped, properties: [Key.scanner: scanner.analyticsName]) } - static func barcodeScannerSetupScannerConnected(scanner: PointOfSaleBarcodeScannerType, step: String) -> WooAnalyticsEvent { + public static func barcodeScannerSetupScannerConnected(scanner: PointOfSaleBarcodeScannerType, step: String) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleBarcodeScannerSetupScannerConnected, properties: [Key.scanner: scanner.analyticsName]) } @@ -296,7 +297,7 @@ private extension WooAnalyticsEvent.PointOfSale { } } -extension WooAnalyticsEvent.PointOfSale { +public extension WooAnalyticsEvent.PointOfSale { /// Source of the event where the event is triggered /// Views: Product, Variation, and Coupon Lists. Cart view and Checkout error. /// @@ -307,7 +308,7 @@ extension WooAnalyticsEvent.PointOfSale { case cart case error - init(itemType: POSItemType) { + public init(itemType: POSItemType) { switch itemType { case .product: self = .product @@ -318,7 +319,7 @@ extension WooAnalyticsEvent.PointOfSale { } } - init(itemListType: ItemListType) { + public init(itemListType: ItemListType) { switch itemListType { case .products: self = .product @@ -337,7 +338,7 @@ extension WooAnalyticsEvent.PointOfSale { case preSearch = "pre_search" case scanner - init(isSearching: Bool, searchTerm: String = "") { + public init(isSearching: Bool, searchTerm: String = "") { switch (isSearching, searchTerm.isEmpty) { case (false, _): self = .list @@ -357,7 +358,7 @@ extension WooAnalyticsEvent.PointOfSale { case loading case error - init(cartItem: Cart.PurchasableItem) { + public init(cartItem: Cart.PurchasableItem) { switch cartItem.state { case .loaded: self = .product @@ -375,7 +376,7 @@ extension WooAnalyticsEvent.PointOfSale { case simple case variation - init?(cartItem: Cart.PurchasableItem) { + public init?(cartItem: Cart.PurchasableItem) { guard case let .loaded(item) = cartItem.state else { return nil } @@ -390,3 +391,16 @@ extension WooAnalyticsEvent.PointOfSale { } } } + +public extension PaymentMethod { + var analyticsValue: String { + switch self { + case .card, .cardPresent: + return "card" + case .interacPresent: + return "card_interac" + case .unknown: + return "unknown" + } + } +} diff --git a/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift b/Modules/Sources/PointOfSale/Analytics/POSIneligibleUIAnalyticsEvent.swift similarity index 76% rename from WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift rename to Modules/Sources/PointOfSale/Analytics/POSIneligibleUIAnalyticsEvent.swift index 94eb13a2200..9e9a7e335a1 100644 --- a/WooCommerce/Classes/POS/Analytics/WooAnalyticsEvent+PointOfSaleIneligibleUI.swift +++ b/Modules/Sources/PointOfSale/Analytics/POSIneligibleUIAnalyticsEvent.swift @@ -1,15 +1,18 @@ -extension WooAnalyticsEvent { +import WooFoundation + +/// POS Ineligible UI specific analytics events +public extension WooAnalyticsEvent { enum PointOfSaleIneligibleUI { /// Event property key. private enum Key { static let reason = "reason" } - static func ineligibleUIShown(reason: POSIneligibleReason) -> WooAnalyticsEvent { + public static func ineligibleUIShown(reason: POSIneligibleReason) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleIneligibleUIShown, properties: [Key.reason: reason.analyticsValue]) } - static func ineligibleUIRetryTapped(reason: POSIneligibleReason) -> WooAnalyticsEvent { + public static func ineligibleUIRetryTapped(reason: POSIneligibleReason) -> WooAnalyticsEvent { WooAnalyticsEvent(statName: .pointOfSaleIneligibleUIRetryTapped, properties: [Key.reason: reason.analyticsValue]) } } diff --git a/Modules/Sources/PointOfSale/DependencyProviders/POSDependencyProviding.swift b/Modules/Sources/PointOfSale/DependencyProviders/POSDependencyProviding.swift new file mode 100644 index 00000000000..ca1757a006c --- /dev/null +++ b/Modules/Sources/PointOfSale/DependencyProviders/POSDependencyProviding.swift @@ -0,0 +1,58 @@ +import Foundation +import WooFoundation +import Experiments + +/// Main dependency provider protocol for POS module +/// This abstracts away direct ServiceLocator access +public protocol POSDependencyProviding { + var analytics: POSAnalyticsProviding { get } + var stores: POSStoresProviding { get } + var currency: POSCurrencyProviding { get } + var storage: POSStorageProviding { get } + var featureFlags: POSFeatureFlagProviding { get } + var pushNotifications: POSPushNotificationProviding { get } +} + +/// Analytics service abstraction +public protocol POSAnalyticsProviding { + func track(event: WooAnalyticsEvent) + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType], error: Error) + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType]) + func track(_ stat: WooAnalyticsStat) +} + +/// Stores manager abstraction +public protocol POSStoresProviding { + var sessionManager: POSSessionManagerProviding { get } + // Add other stores manager methods as needed +} + +/// Session manager abstraction +public protocol POSSessionManagerProviding { + var defaultSite: POSSiteProviding? { get } +} + +/// Site abstraction +public protocol POSSiteProviding { + // Add site properties as needed during migration +} + +/// Currency settings and formatting abstraction +public protocol POSCurrencyProviding { + // Currency-related methods will be added as we migrate files +} + +/// Storage manager abstraction +public protocol POSStorageProviding { + // Storage methods will be added as we migrate files +} + +/// Feature flag service abstraction +public protocol POSFeatureFlagProviding { + func isFeatureFlagEnabled(_ flag: FeatureFlag) -> Bool +} + +/// Push notifications abstraction +public protocol POSPushNotificationProviding { + // Push notification methods will be added as we migrate files +} diff --git a/Modules/Sources/PointOfSale/Environment/POSEnvironmentKeys.swift b/Modules/Sources/PointOfSale/Environment/POSEnvironmentKeys.swift new file mode 100644 index 00000000000..1915b67c20c --- /dev/null +++ b/Modules/Sources/PointOfSale/Environment/POSEnvironmentKeys.swift @@ -0,0 +1,33 @@ +import SwiftUI +import WooFoundation + +/// Environment key for POS analytics service +public struct POSAnalyticsKey: EnvironmentKey { + public static let defaultValue: POSAnalyticsProviding = DefaultPOSAnalytics() +} + +/// Default implementation that does nothing (for previews/testing) +private struct DefaultPOSAnalytics: POSAnalyticsProviding { + func track(event: WooAnalyticsEvent) { + // No-op implementation for previews/testing + } + + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType] = [:]) { + // No-op implementation for previews/testing + } + + func track(_ stat: WooAnalyticsStat) { + // No-op implementation for previews/testing + } + + func track(_ stat: WooFoundationCore.WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType], error: any Error) { + // No-op implementation for previews/testing + } +} + +public extension EnvironmentValues { + var posAnalytics: POSAnalyticsProviding { + get { self[POSAnalyticsKey.self] } + set { self[POSAnalyticsKey.self] = newValue } + } +} diff --git a/WooCommerce/Classes/Extensions/Error+Connectivity.swift b/Modules/Sources/PointOfSale/Extensions/Error+Connectivity.swift similarity index 96% rename from WooCommerce/Classes/Extensions/Error+Connectivity.swift rename to Modules/Sources/PointOfSale/Extensions/Error+Connectivity.swift index 17409577ecf..5d421506cae 100644 --- a/WooCommerce/Classes/Extensions/Error+Connectivity.swift +++ b/Modules/Sources/PointOfSale/Extensions/Error+Connectivity.swift @@ -1,7 +1,7 @@ import Foundation import enum Alamofire.AFError -extension Error { +public extension Error { var isConnectivityError: Bool { if let afError = self as? AFError { if let underlyingError = afError.underlyingError { diff --git a/WooCommerce/Classes/POS/Models/Cart+BarcodeScanError.swift b/Modules/Sources/PointOfSale/Models/Cart+BarcodeScanError.swift similarity index 97% rename from WooCommerce/Classes/POS/Models/Cart+BarcodeScanError.swift rename to Modules/Sources/PointOfSale/Models/Cart+BarcodeScanError.swift index f4577a72d58..47a80b79d2f 100644 --- a/WooCommerce/Classes/POS/Models/Cart+BarcodeScanError.swift +++ b/Modules/Sources/PointOfSale/Models/Cart+BarcodeScanError.swift @@ -1,7 +1,7 @@ import Foundation import enum Yosemite.PointOfSaleBarcodeScanError -extension Cart { +public extension Cart { @discardableResult mutating func updateLoadingItem(id: UUID, with error: PointOfSaleBarcodeScanError) -> Cart.PurchasableItem? { guard let index = purchasableItems.firstIndex(where: { $0.id == id }) else { return nil } @@ -59,8 +59,8 @@ extension Cart { } } -extension PointOfSaleBarcodeScanError { - var localizedDescription: String { +public extension PointOfSaleBarcodeScanError { + public var localizedDescription: String { switch self { case .notFound, .unknown: return Localization.notFound diff --git a/WooCommerce/Classes/POS/Models/Cart.swift b/Modules/Sources/PointOfSale/Models/Cart.swift similarity index 68% rename from WooCommerce/Classes/POS/Models/Cart.swift rename to Modules/Sources/PointOfSale/Models/Cart.swift index 6172fc4ce3f..f1f5105e311 100644 --- a/WooCommerce/Classes/POS/Models/Cart.swift +++ b/Modules/Sources/PointOfSale/Models/Cart.swift @@ -3,39 +3,47 @@ import protocol Yosemite.POSOrderableItem import enum Yosemite.POSItem import enum Yosemite.PointOfSaleBarcodeScanError -struct Cart { - var purchasableItems: [Cart.PurchasableItem] = [] - var coupons: [Cart.CouponItem] = [] - - var accessibilityFocusedItemID: UUID? = nil +public struct Cart { + public var purchasableItems: [Cart.PurchasableItem] = [] + public var coupons: [Cart.CouponItem] = [] + + public var accessibilityFocusedItemID: UUID? = nil + + public init(purchasableItems: [Cart.PurchasableItem] = [], + coupons: [Cart.CouponItem] = [], + accessibilityFocusedItemID: UUID? = nil) { + self.purchasableItems = purchasableItems + self.coupons = coupons + self.accessibilityFocusedItemID = accessibilityFocusedItemID + } } -protocol CartItem { +public protocol CartItem { var id: UUID { get } var type: CartItemType { get } } -enum CartItemType: CaseIterable { +public enum CartItemType: CaseIterable { case purchasableItem case coupon } -extension Cart { +public extension Cart { struct PurchasableItem: CartItem { - let id: UUID - let title: String - let subtitle: String? - let quantity: Int - let type: CartItemType = .purchasableItem - let state: ItemState - let accessibilityLabel: String? - - enum ItemState { + public let id: UUID + public let title: String + public let subtitle: String? + public let quantity: Int + public let type: CartItemType = .purchasableItem + public let state: ItemState + public let accessibilityLabel: String? + + public enum ItemState { case loaded(POSOrderableItem) case loading case error - var isLoading: Bool { + public var isLoading: Bool { switch self { case .loading: return true @@ -45,7 +53,7 @@ extension Cart { } } - var formattedPrice: String? { + public var formattedPrice: String? { switch state { case .loaded(let item): return item.formattedPrice @@ -54,7 +62,7 @@ extension Cart { } } - init(id: UUID, title: String, subtitle: String?, quantity: Int, state: ItemState, accessibilityLabel: String? = nil) { + public init(id: UUID, title: String, subtitle: String?, quantity: Int, state: ItemState, accessibilityLabel: String? = nil) { self.id = id self.title = title self.subtitle = subtitle @@ -63,7 +71,7 @@ extension Cart { self.accessibilityLabel = accessibilityLabel } - init(id: UUID, item: POSOrderableItem, title: String, subtitle: String?, quantity: Int) { + public init(id: UUID, item: POSOrderableItem, title: String, subtitle: String?, quantity: Int) { self.id = id self.title = title self.subtitle = subtitle @@ -72,7 +80,7 @@ extension Cart { self.accessibilityLabel = nil } - static func loading(id: UUID) -> PurchasableItem { + public static func loading(id: UUID) -> PurchasableItem { PurchasableItem( id: id, title: "Loading...", @@ -84,16 +92,22 @@ extension Cart { } struct CouponItem: CartItem { - let id: UUID - let code: String - let summary: String - let type: CartItemType = .coupon + public let id: UUID + public let code: String + public let summary: String + public let type: CartItemType = .coupon + + public init(id: UUID, code: String, summary: String) { + self.id = id + self.code = code + self.summary = summary + } } } // MARK: - Helper Methods -extension Cart { +public extension Cart { mutating func add(_ posItem: POSItem) { if let purchasableItem = createPurchasableItem(id: UUID(), from: posItem) { purchasableItems.insert(purchasableItem, at: purchasableItems.startIndex) diff --git a/WooCommerce/Classes/POS/Models/ItemListType.swift b/Modules/Sources/PointOfSale/Models/ItemListType.swift similarity index 77% rename from WooCommerce/Classes/POS/Models/ItemListType.swift rename to Modules/Sources/PointOfSale/Models/ItemListType.swift index d6d9a92226e..cb579a42021 100644 --- a/WooCommerce/Classes/POS/Models/ItemListType.swift +++ b/Modules/Sources/PointOfSale/Models/ItemListType.swift @@ -1,10 +1,10 @@ import enum Yosemite.POSItemType -enum ItemListType: Equatable, Hashable { +public enum ItemListType: Equatable, Hashable { case products(search: Bool = false) case coupons(search: Bool = false) - var itemType: POSItemType { + public var itemType: POSItemType { switch self { case .coupons: return .coupon @@ -13,7 +13,7 @@ enum ItemListType: Equatable, Hashable { } } - var isProducts: Bool { + public var isProducts: Bool { switch self { case .products: return true @@ -22,7 +22,7 @@ enum ItemListType: Equatable, Hashable { } } - var isCoupons: Bool { + public var isCoupons: Bool { switch self { case .products: return false @@ -31,7 +31,7 @@ enum ItemListType: Equatable, Hashable { } } - var isSearching: Bool { + public var isSearching: Bool { switch self { case let .products(search), let .coupons(search): return search diff --git a/Modules/Sources/PointOfSale/Models/POSIneligibleReason.swift b/Modules/Sources/PointOfSale/Models/POSIneligibleReason.swift new file mode 100644 index 00000000000..3564de9f832 --- /dev/null +++ b/Modules/Sources/PointOfSale/Models/POSIneligibleReason.swift @@ -0,0 +1,19 @@ +import enum WooFoundation.CountryCode +import enum WooFoundation.CurrencyCode + +/// Represents the reasons why a site may be ineligible for POS. +public enum POSIneligibleReason: Equatable { + case unsupportedIOSVersion + case unsupportedWooCommerceVersion(minimumVersion: String) + case siteSettingsNotAvailable + case wooCommercePluginNotFound + case featureSwitchDisabled + case unsupportedCurrency(countryCode: CountryCode, supportedCurrencies: [CurrencyCode]) + case selfDeallocated +} + +/// Represents the eligibility state for POS. +public enum POSEligibilityState: Equatable { + case eligible + case ineligible(reason: POSIneligibleReason) +} diff --git a/Modules/Sources/PointOfSale/PointOfSale.swift b/Modules/Sources/PointOfSale/PointOfSale.swift new file mode 100644 index 00000000000..b970ac436b8 --- /dev/null +++ b/Modules/Sources/PointOfSale/PointOfSale.swift @@ -0,0 +1,12 @@ +import Foundation + +/// Main module interface for Point of Sale functionality +public struct PointOfSale { + /// Module version + public static let version = "1.0.0" + + /// Initialize the Point of Sale module + public static func initialize() { + // Placeholder for module initialization + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift b/Modules/Sources/PointOfSale/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift similarity index 65% rename from WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift rename to Modules/Sources/PointOfSale/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift index 5e1c83bf8bb..144589e387c 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift +++ b/Modules/Sources/PointOfSale/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupModels.swift @@ -1,19 +1,24 @@ import SwiftUI // MARK: - Data Models -struct PointOfSaleBarcodeScannerSetupFlowOption: Identifiable { - let id = UUID() - let title: String - let scannerType: PointOfSaleBarcodeScannerType +public struct PointOfSaleBarcodeScannerSetupFlowOption: Identifiable { + public let id = UUID() + public let title: String + public let scannerType: PointOfSaleBarcodeScannerType + + public init(title: String, scannerType: PointOfSaleBarcodeScannerType) { + self.title = title + self.scannerType = scannerType + } } -enum PointOfSaleBarcodeScannerType { +public enum PointOfSaleBarcodeScannerType { case starBSH20B case tera12002D case netum1228BC case other - var name: String { + public var name: String { switch self { case .starBSH20B: return Localization.starBsh20BName @@ -26,7 +31,7 @@ enum PointOfSaleBarcodeScannerType { } } - var analyticsName: String { + public var analyticsName: String { switch self { case .starBSH20B: return "Star_BSH_20B" @@ -66,13 +71,13 @@ private extension PointOfSaleBarcodeScannerType { } // MARK: - Flow State -enum PointOfSaleBarcodeScannerSetupFlowState { +public enum PointOfSaleBarcodeScannerSetupFlowState { case scannerSelection case setupFlow(PointOfSaleBarcodeScannerType) } // MARK: - Step Identifiers -enum PointOfSaleBarcodeScannerStepID: String, CaseIterable { +public enum PointOfSaleBarcodeScannerStepID: String, CaseIterable { case setupBarcodeHID case setupBarcodePair case setupInformation @@ -83,7 +88,7 @@ enum PointOfSaleBarcodeScannerStepID: String, CaseIterable { case testScanTimedOut case complete - var analyticsValue: String? { + public var analyticsValue: String? { switch self { case .setupBarcodeHID: return "setup_barcode_hid" @@ -107,50 +112,18 @@ enum PointOfSaleBarcodeScannerStepID: String, CaseIterable { } } -// MARK: - Button Customization Protocol -@available(iOS 17.0, *) -protocol PointOfSaleBarcodeScannerButtonCustomization { - func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration -} - -// MARK: - Transition Types -enum PointOfSaleBarcodeScannerTransitionType: Hashable { - case next - case retry - case back -} - -// MARK: - Setup Step -@available(iOS 17.0, *) -struct PointOfSaleBarcodeScannerSetupStep { - let title: String - let content: any View - let buttonCustomization: PointOfSaleBarcodeScannerButtonCustomization? - let transitions: [PointOfSaleBarcodeScannerTransitionType: PointOfSaleBarcodeScannerStepID] - - init(title: String = "", - @ViewBuilder content: () -> any View, - buttonCustomization: PointOfSaleBarcodeScannerButtonCustomization? = nil, - transitions: [PointOfSaleBarcodeScannerTransitionType: PointOfSaleBarcodeScannerStepID] = [:]) { - self.title = title - self.content = content() - self.buttonCustomization = buttonCustomization - self.transitions = transitions - } -} - // MARK: - Test Barcodes -enum PointOfSaleBarcodeScannerTestBarcode { +public enum PointOfSaleBarcodeScannerTestBarcode { case ean13 - var barcodeAsset: PointOfSaleAssets { + public var barcodeAsset: PointOfSaleAssets { switch self { case .ean13: return .testEan13Barcode } } - var expectedValue: String { + public var expectedValue: String { switch self { case .ean13: return "1234567890128" diff --git a/Modules/Sources/PointOfSale/Presentation/Barcode Scanning/HIDBarcodeParserTypes.swift b/Modules/Sources/PointOfSale/Presentation/Barcode Scanning/HIDBarcodeParserTypes.swift new file mode 100644 index 00000000000..d9a84d5486f --- /dev/null +++ b/Modules/Sources/PointOfSale/Presentation/Barcode Scanning/HIDBarcodeParserTypes.swift @@ -0,0 +1,36 @@ +import Foundation + +public enum HIDBarcodeParserError: Error { + case scanTooShort(barcode: String) + case timedOut(barcode: String) + + public var analyticsReason: String { + switch self { + case .scanTooShort: + return "too_short" + case .timedOut: + return "no_terminator" + } + } + + public var barcode: String { + switch self { + case .scanTooShort(let barcode), .timedOut(let barcode): + return barcode + } + } +} + +public enum HIDBarcodeParserResult { + case success(barcode: String, scanDurationMs: Int) + case failure(error: HIDBarcodeParserError, scanDurationMs: Int) + + public var asResult: Result { + switch self { + case .success(let barcode, _): + return .success(barcode) + case .failure(let error, _): + return .failure(error) + } + } +} diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleAssets.swift b/Modules/Sources/PointOfSale/Presentation/PointOfSaleAssets.swift similarity index 96% rename from WooCommerce/Classes/POS/Presentation/PointOfSaleAssets.swift rename to Modules/Sources/PointOfSale/Presentation/PointOfSaleAssets.swift index 4c8070ad7f0..2b22f21d1da 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleAssets.swift +++ b/Modules/Sources/PointOfSale/Presentation/PointOfSaleAssets.swift @@ -1,6 +1,6 @@ import Foundation -enum PointOfSaleAssets: CaseIterable { +public enum PointOfSaleAssets: CaseIterable { case error case exclamationMark case magnifierNotFound @@ -23,7 +23,7 @@ enum PointOfSaleAssets: CaseIterable { case netum1228BCPairBarcode case testEan13Barcode - var imageName: String { + public var imageName: String { switch self { case .error: "pos-error" diff --git a/Modules/Sources/WooFoundation/Protocols/Analytics.swift b/Modules/Sources/WooFoundationCore/Analytics/Analytics.swift similarity index 100% rename from Modules/Sources/WooFoundation/Protocols/Analytics.swift rename to Modules/Sources/WooFoundationCore/Analytics/Analytics.swift diff --git a/Modules/Sources/WooFoundation/Protocols/Analytics/AnalyticsProvider.swift b/Modules/Sources/WooFoundationCore/Analytics/AnalyticsProvider.swift similarity index 100% rename from Modules/Sources/WooFoundation/Protocols/Analytics/AnalyticsProvider.swift rename to Modules/Sources/WooFoundationCore/Analytics/AnalyticsProvider.swift diff --git a/Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsEvent.swift b/Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsEvent.swift new file mode 100644 index 00000000000..d9cd0141e8a --- /dev/null +++ b/Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsEvent.swift @@ -0,0 +1,50 @@ +import Foundation + +/// This struct represents an analytics event. It is a combination of `WooAnalyticsStat` and +/// its properties. +/// +/// This was mostly created to promote static-typing via constructors. +/// +/// ## Adding New Events +/// +/// 1. Add the event name (`String`) to `WooAnalyticsStat`. +/// 2. Create an `extension` of `WooAnalyticsStat` if necessary for grouping. +/// 3. Add a `static func` constructor. +/// +/// Here is an example: +/// +/// ~~~ +/// extension WooAnalyticsEvent { +/// enum LoginStep: String { +/// case start +/// case success +/// } +/// +/// static func login(step: LoginStep) -> WooAnalyticsEvent { +/// let properties = [ +/// "step": step.rawValue +/// ] +/// +/// return WooAnalyticsEvent(name: "login", properties: properties) +/// } +/// } +/// ~~~ +/// +/// Examples of tracking calls (in the client App or Pod): +/// +/// ~~~ +/// Analytics.track(event: .login(step: .start)) +/// Analytics.track(event: .loginStart) +/// ~~~ +/// +public struct WooAnalyticsEvent { + public let statName: WooAnalyticsStat + public let properties: [String: WooAnalyticsEventPropertyType] + public let error: Error? + + public init(statName: WooAnalyticsStat, properties: [String: WooAnalyticsEventPropertyType] = [:], error: Error? = nil) { + self.statName = statName + self.properties = properties + self.error = error + } +} diff --git a/Modules/Sources/WooFoundation/Protocols/Analytics/WooAnalyticsEventPropertyType.swift b/Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsEventPropertyType.swift similarity index 100% rename from Modules/Sources/WooFoundation/Protocols/Analytics/WooAnalyticsEventPropertyType.swift rename to Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsEventPropertyType.swift diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsStat.swift similarity index 94% rename from WooCommerce/Classes/Analytics/WooAnalyticsStat.swift rename to Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsStat.swift index 46e2892c54f..7bd56040fff 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/Modules/Sources/WooFoundationCore/Analytics/WooAnalyticsStat.swift @@ -1,6 +1,4 @@ -#if canImport(WordPressShared) -import WordPressShared -#endif +import Foundation /// This enum contains all of the events we track in the WooCommerce app. /// @@ -18,7 +16,7 @@ import WordPressShared /// Note: If you would like to exclude site properties (e.g. `blog_id`) for a given event, please /// add the event to the `WooAnalyticsStat.shouldSendSiteProperties` var. /// -enum WooAnalyticsStat: String { +public enum WooAnalyticsStat: String { // MARK: Application Events // @@ -1357,7 +1355,7 @@ extension WooAnalyticsStat { /// Note: Currently all authentication events will return false. If you wish /// to include additional no-site-info events, please add them here. /// - var shouldSendSiteProperties: Bool { + public var shouldSendSiteProperties: Bool { switch self { // Authentication Events case .signedIn, .logout, .openedLogin, .loginFailed, @@ -1379,102 +1377,3 @@ extension WooAnalyticsStat { } } } - -#if canImport(WordPressShared) -extension WooAnalyticsStat { - - /// Converts the provided WPAnalyticsStat into a WooAnalyticsStat. - /// This whole process kinda stinks, but we need this for the `WordPressAuthenticatorDelegate` - /// implementation. ☹️ Feel free to refactor later on! - /// - /// - Parameter stat: The WPAnalyticsStat to convert - /// - Returns: The corresponding WooAnalyticsStat or nil if it cannot be converted - /// - static func valueOf(stat: WPAnalyticsStat) -> WooAnalyticsStat? { - var wooEvent: WooAnalyticsStat? = nil - - switch stat { - case .signedIn: - wooEvent = WooAnalyticsStat.signedIn - case .signedInToJetpack: - wooEvent = WooAnalyticsStat.signedIn - case .logout: - wooEvent = WooAnalyticsStat.logout - case .openedLogin: - wooEvent = WooAnalyticsStat.openedLogin - case .loginFailed: - wooEvent = WooAnalyticsStat.loginFailed - case .loginAutoFillCredentialsFilled: - wooEvent = WooAnalyticsStat.loginAutoFillCredentialsFilled - case .loginAutoFillCredentialsUpdated: - wooEvent = WooAnalyticsStat.loginAutoFillCredentialsUpdated - case .loginProloguePaged: - wooEvent = WooAnalyticsStat.loginProloguePaged - case .loginPrologueViewed: - wooEvent = WooAnalyticsStat.loginPrologueViewed - case .loginEmailFormViewed: - wooEvent = WooAnalyticsStat.loginEmailFormViewed - case .loginMagicLinkOpenEmailClientViewed: - wooEvent = WooAnalyticsStat.loginMagicLinkOpenEmailClientViewed - case .loginMagicLinkRequestFormViewed: - wooEvent = WooAnalyticsStat.loginMagicLinkRequestFormViewed - case .loginMagicLinkExited: - wooEvent = WooAnalyticsStat.loginMagicLinkExited - case .loginMagicLinkFailed: - wooEvent = WooAnalyticsStat.loginMagicLinkFailed - case .loginMagicLinkOpened: - wooEvent = WooAnalyticsStat.loginMagicLinkOpened - case .loginMagicLinkRequested: - wooEvent = WooAnalyticsStat.loginMagicLinkRequested - case .loginMagicLinkSucceeded: - wooEvent = WooAnalyticsStat.loginMagicLinkSucceeded - case .loginPasswordFormViewed: - wooEvent = WooAnalyticsStat.loginPasswordFormViewed - case .loginURLFormViewed: - wooEvent = WooAnalyticsStat.loginURLFormViewed - case .loginURLHelpScreenViewed: - wooEvent = WooAnalyticsStat.loginURLHelpScreenViewed - case .loginUsernamePasswordFormViewed: - wooEvent = WooAnalyticsStat.loginUsernamePasswordFormViewed - case .loginTwoFactorFormViewed: - wooEvent = WooAnalyticsStat.loginTwoFactorFormViewed - case .loginEpilogueViewed: - wooEvent = WooAnalyticsStat.loginEpilogueViewed - case .loginForgotPasswordClicked: - wooEvent = WooAnalyticsStat.loginForgotPasswordClicked - case .loginSocialButtonClick: - wooEvent = WooAnalyticsStat.loginSocialButtonClick - case .loginSocialButtonFailure: - wooEvent = WooAnalyticsStat.loginSocialButtonFailure - case .loginSocialConnectSuccess: - wooEvent = WooAnalyticsStat.loginSocialConnectSuccess - case .loginSocialConnectFailure: - wooEvent = WooAnalyticsStat.loginSocialConnectFailure - case .loginSocialSuccess: - wooEvent = WooAnalyticsStat.loginSocialSuccess - case .loginSocialFailure: - wooEvent = WooAnalyticsStat.loginSocialFailure - case .loginSocial2faNeeded: - wooEvent = WooAnalyticsStat.loginSocial2faNeeded - case .loginSocialAccountsNeedConnecting: - wooEvent = WooAnalyticsStat.loginSocialAccountsNeedConnecting - case .loginSocialErrorUnknownUser: - wooEvent = WooAnalyticsStat.loginSocialErrorUnknownUser - case .onePasswordFailed: - wooEvent = WooAnalyticsStat.onePasswordFailed - case .onePasswordLogin: - wooEvent = WooAnalyticsStat.onePasswordLogin - case .onePasswordSignup: - wooEvent = WooAnalyticsStat.onePasswordSignup - case .twoFactorCodeRequested: - wooEvent = WooAnalyticsStat.twoFactorCodeRequested - case .twoFactorSentSMS: - wooEvent = WooAnalyticsStat.twoFactorSentSMS - default: - wooEvent = nil - } - - return wooEvent - } -} -#endif diff --git a/Modules/Tests/PointOfSaleTests/PointOfSaleTests.swift b/Modules/Tests/PointOfSaleTests/PointOfSaleTests.swift new file mode 100644 index 00000000000..e0a1533ced4 --- /dev/null +++ b/Modules/Tests/PointOfSaleTests/PointOfSaleTests.swift @@ -0,0 +1,23 @@ +import Foundation +import Testing +@testable import PointOfSale + +struct PointOfSaleTests { + + @Test func module_version_is_correct() async throws { + // Given + let expectedVersion = "1.0.0" + + // When + let actualVersion = PointOfSale.version + + // Then + #expect(actualVersion == expectedVersion) + } + + @Test func initialize_does_not_throw() async throws { + // When, Then + // Should not throw any errors + PointOfSale.initialize() + } +} diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift b/WooCommerce/Classes/Analytics/WooAnalyticsEvent+WooApp.swift similarity index 99% rename from WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift rename to WooCommerce/Classes/Analytics/WooAnalyticsEvent+WooApp.swift index 75ec2ebd734..4068343b7cb 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsEvent+WooApp.swift @@ -3,54 +3,8 @@ import UIKit import Yosemite import WooFoundation -/// This struct represents an analytics event. It is a combination of `WooAnalyticsStat` and -/// its properties. -/// -/// This was mostly created to promote static-typing via constructors. -/// -/// ## Adding New Events -/// -/// 1. Add the event name (`String`) to `WooAnalyticsStat`. -/// 2. Create an `extension` of `WooAnalyticsStat` if necessary for grouping. -/// 3. Add a `static func` constructor. -/// -/// Here is an example: -/// -/// ~~~ -/// extension WooAnalyticsEvent { -/// enum LoginStep: String { -/// case start -/// case success -/// } -/// -/// static func login(step: LoginStep) -> WooAnalyticsEvent { -/// let properties = [ -/// "step": step.rawValue -/// ] -/// -/// return WooAnalyticsEvent(name: "login", properties: properties) -/// } -/// } -/// ~~~ -/// -/// Examples of tracking calls (in the client App or Pod): -/// -/// ~~~ -/// Analytics.track(event: .login(step: .start)) -/// Analytics.track(event: .loginStart) -/// ~~~ -/// -struct WooAnalyticsEvent { - init(statName: WooAnalyticsStat, properties: [String: WooAnalyticsEventPropertyType], error: Error? = nil) { - self.statName = statName - self.properties = properties - self.error = error - } - - let statName: WooAnalyticsStat - let properties: [String: WooAnalyticsEventPropertyType] - let error: Error? -} +typealias WooAnalyticsEvent = WooFoundation.WooAnalyticsEvent +typealias WooAnalyticsStat = WooFoundation.WooAnalyticsStat // MARK: - In-app Feedback and Survey diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat+WPAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat+WPAnalyticsStat.swift new file mode 100644 index 00000000000..f66f380936d --- /dev/null +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat+WPAnalyticsStat.swift @@ -0,0 +1,101 @@ +#if canImport(WordPressShared) + +import WordPressShared + +extension WooAnalyticsStat { + + /// Converts the provided WPAnalyticsStat into a WooAnalyticsStat. + /// This whole process kinda stinks, but we need this for the `WordPressAuthenticatorDelegate` + /// implementation. ☹️ Feel free to refactor later on! + /// + /// - Parameter stat: The WPAnalyticsStat to convert + /// - Returns: The corresponding WooAnalyticsStat or nil if it cannot be converted + /// + static func valueOf(stat: WPAnalyticsStat) -> WooAnalyticsStat? { + var wooEvent: WooAnalyticsStat? = nil + + switch stat { + case .signedIn: + wooEvent = WooAnalyticsStat.signedIn + case .signedInToJetpack: + wooEvent = WooAnalyticsStat.signedIn + case .logout: + wooEvent = WooAnalyticsStat.logout + case .openedLogin: + wooEvent = WooAnalyticsStat.openedLogin + case .loginFailed: + wooEvent = WooAnalyticsStat.loginFailed + case .loginAutoFillCredentialsFilled: + wooEvent = WooAnalyticsStat.loginAutoFillCredentialsFilled + case .loginAutoFillCredentialsUpdated: + wooEvent = WooAnalyticsStat.loginAutoFillCredentialsUpdated + case .loginProloguePaged: + wooEvent = WooAnalyticsStat.loginProloguePaged + case .loginPrologueViewed: + wooEvent = WooAnalyticsStat.loginPrologueViewed + case .loginEmailFormViewed: + wooEvent = WooAnalyticsStat.loginEmailFormViewed + case .loginMagicLinkOpenEmailClientViewed: + wooEvent = WooAnalyticsStat.loginMagicLinkOpenEmailClientViewed + case .loginMagicLinkRequestFormViewed: + wooEvent = WooAnalyticsStat.loginMagicLinkRequestFormViewed + case .loginMagicLinkExited: + wooEvent = WooAnalyticsStat.loginMagicLinkExited + case .loginMagicLinkFailed: + wooEvent = WooAnalyticsStat.loginMagicLinkFailed + case .loginMagicLinkOpened: + wooEvent = WooAnalyticsStat.loginMagicLinkOpened + case .loginMagicLinkRequested: + wooEvent = WooAnalyticsStat.loginMagicLinkRequested + case .loginMagicLinkSucceeded: + wooEvent = WooAnalyticsStat.loginMagicLinkSucceeded + case .loginPasswordFormViewed: + wooEvent = WooAnalyticsStat.loginPasswordFormViewed + case .loginURLFormViewed: + wooEvent = WooAnalyticsStat.loginURLFormViewed + case .loginURLHelpScreenViewed: + wooEvent = WooAnalyticsStat.loginURLHelpScreenViewed + case .loginUsernamePasswordFormViewed: + wooEvent = WooAnalyticsStat.loginUsernamePasswordFormViewed + case .loginTwoFactorFormViewed: + wooEvent = WooAnalyticsStat.loginTwoFactorFormViewed + case .loginEpilogueViewed: + wooEvent = WooAnalyticsStat.loginEpilogueViewed + case .loginForgotPasswordClicked: + wooEvent = WooAnalyticsStat.loginForgotPasswordClicked + case .loginSocialButtonClick: + wooEvent = WooAnalyticsStat.loginSocialButtonClick + case .loginSocialButtonFailure: + wooEvent = WooAnalyticsStat.loginSocialButtonFailure + case .loginSocialConnectSuccess: + wooEvent = WooAnalyticsStat.loginSocialConnectSuccess + case .loginSocialConnectFailure: + wooEvent = WooAnalyticsStat.loginSocialConnectFailure + case .loginSocialSuccess: + wooEvent = WooAnalyticsStat.loginSocialSuccess + case .loginSocialFailure: + wooEvent = WooAnalyticsStat.loginSocialFailure + case .loginSocial2faNeeded: + wooEvent = WooAnalyticsStat.loginSocial2faNeeded + case .loginSocialAccountsNeedConnecting: + wooEvent = WooAnalyticsStat.loginSocialAccountsNeedConnecting + case .loginSocialErrorUnknownUser: + wooEvent = WooAnalyticsStat.loginSocialErrorUnknownUser + case .onePasswordFailed: + wooEvent = WooAnalyticsStat.onePasswordFailed + case .onePasswordLogin: + wooEvent = WooAnalyticsStat.onePasswordLogin + case .onePasswordSignup: + wooEvent = WooAnalyticsStat.onePasswordSignup + case .twoFactorCodeRequested: + wooEvent = WooAnalyticsStat.twoFactorCodeRequested + case .twoFactorSentSMS: + wooEvent = WooAnalyticsStat.twoFactorSentSMS + default: + wooEvent = nil + } + + return wooEvent + } +} +#endif diff --git a/WooCommerce/Classes/POS/Analytics/POSCollectOrderPaymentAnalytics.swift b/WooCommerce/Classes/POS/Analytics/POSCollectOrderPaymentAnalytics.swift index b8fb64b85bc..510253f308b 100644 --- a/WooCommerce/Classes/POS/Analytics/POSCollectOrderPaymentAnalytics.swift +++ b/WooCommerce/Classes/POS/Analytics/POSCollectOrderPaymentAnalytics.swift @@ -10,7 +10,7 @@ final class POSCollectOrderPaymentAnalytics: POSCollectOrderPaymentAnalyticsTrac private var checkoutTapCount: Int = 0 private var hasTrackedProcessingPayment = false - private let analytics: Analytics + private let analytics: POSAnalyticsProviding private var paymentGatewayAccount: PaymentGatewayAccount? private let configuration: CardPresentPaymentsConfiguration @@ -19,7 +19,7 @@ final class POSCollectOrderPaymentAnalytics: POSCollectOrderPaymentAnalyticsTrac connectedReader?.readerType.model } - init(analytics: Analytics = ServiceLocator.analytics, + init(analytics: POSAnalyticsProviding, configuration: CardPresentPaymentsConfiguration = CardPresentConfigurationLoader().configuration) { self.analytics = analytics self.configuration = configuration diff --git a/WooCommerce/Classes/POS/Analytics/POSItemFetchAnalytics.swift b/WooCommerce/Classes/POS/Analytics/POSItemFetchAnalytics.swift index f845bcc673b..50c8ef394b2 100644 --- a/WooCommerce/Classes/POS/Analytics/POSItemFetchAnalytics.swift +++ b/WooCommerce/Classes/POS/Analytics/POSItemFetchAnalytics.swift @@ -8,13 +8,13 @@ struct POSItemFetchAnalytics: POSItemFetchAnalyticsTracking { /// The type of item being fetched private let itemType: POSItemType /// The analytics service to use for tracking - private let analytics: Analytics + private let analytics: POSAnalyticsProviding /// Creates a new analytics tracker for the given item type /// - Parameters: /// - itemType: The type of item being fetched (e.g. "product", "variation") - /// - analytics: The analytics service to use for tracking. Defaults to ServiceLocator.analytics - init(itemType: POSItemType, analytics: Analytics = ServiceLocator.analytics) { + /// - analytics: The analytics service to use for tracking. + init(itemType: POSItemType, analytics: POSAnalyticsProviding) { self.itemType = itemType self.analytics = analytics } diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift index 3d91e333473..d0bced17b2d 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift @@ -23,13 +23,16 @@ protocol PointOfSaleCouponsControllerProtocol: PointOfSaleSearchingItemsControll private let couponProvider: PointOfSaleCouponServiceProtocol private let fetchStrategyFactory: PointOfSaleCouponFetchStrategyFactory private var fetchStrategy: PointOfSaleCouponFetchStrategy + private let analyticsProvider: POSAnalyticsProviding init(itemProvider: PointOfSaleCouponServiceProtocol, - fetchStrategyFactory: PointOfSaleCouponFetchStrategyFactory) { + fetchStrategyFactory: PointOfSaleCouponFetchStrategyFactory, + analyticsProvider: POSAnalyticsProviding) { self.couponProvider = itemProvider self.fetchStrategyFactory = fetchStrategyFactory self.fetchStrategy = fetchStrategyFactory.defaultStrategy self.paginationTracker = .init() + self.analyticsProvider = analyticsProvider } @MainActor @@ -44,7 +47,8 @@ protocol PointOfSaleCouponsControllerProtocol: PointOfSaleSearchingItemsControll @MainActor func searchItems(searchTerm: String, baseItem: ItemListBaseItem) async { - fetchStrategy = fetchStrategyFactory.searchStrategy(searchTerm: searchTerm, analytics: POSItemFetchAnalytics(itemType: .coupon)) + fetchStrategy = fetchStrategyFactory.searchStrategy(searchTerm: searchTerm, + analytics: POSItemFetchAnalytics(itemType: .coupon, analytics: analyticsProvider)) setSearchingState() await loadFirstPage() } diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift index a6636e64c5f..78ab7559afb 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift @@ -10,7 +10,6 @@ import struct Yosemite.POSVariableParentProduct import class Yosemite.Store import enum Yosemite.POSItemType - @available(iOS 17.0, *) protocol PointOfSaleItemsControllerProtocol { /// @@ -39,17 +38,21 @@ protocol PointOfSaleSearchingItemsControllerProtocol: PointOfSaleItemsController private var itemProvider: PointOfSaleItemServiceProtocol private let itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactoryProtocol private var fetchStrategy: PointOfSalePurchasableItemFetchStrategy + private let analyticsProvider: POSAnalyticsProviding init(itemProvider: PointOfSaleItemServiceProtocol, itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactoryProtocol, initialState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), - itemStates: [:]))) { + itemStates: [:])), + analyticsProvider: POSAnalyticsProviding) { self.itemProvider = itemProvider self.itemFetchStrategyFactory = itemFetchStrategyFactory self.itemsViewState = initialState self.paginationTracker = .init() - self.fetchStrategy = itemFetchStrategyFactory.defaultStrategy(analytics: POSItemFetchAnalytics(itemType: .product)) + self.analyticsProvider = analyticsProvider + self.fetchStrategy = itemFetchStrategyFactory.defaultStrategy(analytics: POSItemFetchAnalytics(itemType: .product, + analytics: analyticsProvider)) } @MainActor @@ -66,7 +69,8 @@ protocol PointOfSaleSearchingItemsControllerProtocol: PointOfSaleItemsController @MainActor func searchItems(searchTerm: String, baseItem: ItemListBaseItem) async { fetchStrategy = itemFetchStrategyFactory.searchStrategy(searchTerm: searchTerm, - analytics: POSItemFetchAnalytics(itemType: .product)) + analytics: POSItemFetchAnalytics(itemType: .product, + analytics: analyticsProvider)) setSearchingState(base: baseItem) await loadFirstPage(base: baseItem) } diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleOrderController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleOrderController.swift index 98031bd8b6c..44a32ab9ada 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleOrderController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleOrderController.swift @@ -45,7 +45,7 @@ protocol PointOfSaleOrderControllerProtocol { receiptService: POSReceiptServiceProtocol, stores: StoresManager = ServiceLocator.stores, currencySettings: CurrencySettings = ServiceLocator.currencySettings, - analytics: Analytics = ServiceLocator.analytics, + analytics: POSAnalyticsProviding, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService, pluginsService: PluginsServiceProtocol = PluginsService(storageManager: ServiceLocator.storageManager), celebration: PaymentCaptureCelebrationProtocol = PaymentCaptureCelebration()) { @@ -66,7 +66,7 @@ protocol PointOfSaleOrderControllerProtocol { private let currencyFormatter: CurrencyFormatter private let celebration: PaymentCaptureCelebrationProtocol private let storeCurrency: CurrencyCode - private let analytics: Analytics + private let analytics: POSAnalyticsProviding private let stores: StoresManager private let featureFlagService: FeatureFlagService private let pluginsService: PluginsServiceProtocol @@ -133,12 +133,12 @@ protocol PointOfSaleOrderControllerProtocol { try await receiptService.sendReceipt(order: order, recipientEmail: recipientEmail, isEligibleForPOSReceipt: posReceiptEligibility) - analytics.track(.receiptEmailSuccess, withProperties: ["eligible_for_pos_receipt": posReceiptEligibility]) + analytics.track(.receiptEmailSuccess, parameters: ["eligible_for_pos_receipt": posReceiptEligibility]) } catch { let properties = [ "eligible_for_pos_receipt": isEligibleForPOSReceipt ].compactMapValues( { $0 }) - analytics.track(.receiptEmailFailed, properties: properties, error: error) + analytics.track(.receiptEmailFailed, parameters: properties, error: error) throw error } } diff --git a/WooCommerce/Classes/POS/Dependencies/POSAnalyticsAdapter.swift b/WooCommerce/Classes/POS/Dependencies/POSAnalyticsAdapter.swift new file mode 100644 index 00000000000..e7bbe3c77b3 --- /dev/null +++ b/WooCommerce/Classes/POS/Dependencies/POSAnalyticsAdapter.swift @@ -0,0 +1,22 @@ +import Foundation +import WooFoundation + +/// Adapter that implements POSAnalyticsProviding using ServiceLocator +final class POSAnalyticsAdapter: POSAnalyticsProviding { + func track(event: WooAnalyticsEvent) { + let mainAppEvent = WooAnalyticsEvent(statName: event.statName, properties: event.properties, error: event.error) + ServiceLocator.analytics.track(event: mainAppEvent) + } + + func track(_ stat: WooAnalyticsStat) { + track(stat, parameters: [:]) + } + + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType] = [:]) { + ServiceLocator.analytics.track(stat, withProperties: parameters) + } + + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType] = [:], error: Error) { + ServiceLocator.analytics.track(stat, properties: parameters, error: error) + } +} diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index e40ffa2a39f..0539790bede 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -75,7 +75,7 @@ protocol PointOfSaleAggregateModelProtocol { private let cardPresentPaymentService: CardPresentPaymentFacade private let orderController: PointOfSaleOrderControllerProtocol - private let analytics: Analytics + private let analytics: POSAnalyticsProviding private let collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking let searchHistoryService: POSSearchHistoryProviding private let barcodeScanService: PointOfSaleBarcodeScanServiceProtocol @@ -107,7 +107,7 @@ protocol PointOfSaleAggregateModelProtocol { couponsSearchController: PointOfSaleSearchingItemsControllerProtocol, cardPresentPaymentService: CardPresentPaymentFacade, orderController: PointOfSaleOrderControllerProtocol, - analytics: Analytics = ServiceLocator.analytics, + analytics: POSAnalyticsProviding, collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking, searchHistoryService: POSSearchHistoryProviding, popularPurchasableItemsController: PointOfSaleItemsControllerProtocol, diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift index 21d1b600b14..4c183891400 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetup.swift @@ -5,10 +5,11 @@ struct PointOfSaleBarcodeScannerSetup: View { @Binding var isPresented: Bool @State private var flowManager: PointOfSaleBarcodeScannerSetupFlowManager @Environment(\.posModalParentSize) var parentSize + @Environment(\.posAnalytics) private var analytics - init(isPresented: Binding) { + init(isPresented: Binding, analytics: POSAnalyticsProviding) { self._isPresented = isPresented - self.flowManager = PointOfSaleBarcodeScannerSetupFlowManager(isPresented: isPresented) + self.flowManager = PointOfSaleBarcodeScannerSetupFlowManager(isPresented: isPresented, analytics: analytics) } var body: some View { @@ -39,7 +40,7 @@ struct PointOfSaleBarcodeScannerSetup: View { .background(Color.posSurfaceBright) } .onAppear { - ServiceLocator.analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown) + analytics.track(.pointOfSaleBarcodeScannerSetupFlowShown) } .onDisappear { flowManager.onDisappear() @@ -119,10 +120,12 @@ private extension PointOfSaleBarcodeScannerSetup { // MARK: - Previews +#if DEBUG @available(iOS 17.0, *) #Preview { - PointOfSaleBarcodeScannerSetup(isPresented: .constant(true)) + PointOfSaleBarcodeScannerSetup(isPresented: .constant(true), analytics: POSPreviewAnalytics()) } +#endif /// A container view that animates changes in its child content with a fade-out and fade-in transition, /// while also smoothly animating changes in height. diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift index 515a0d059ff..4efd0bc9154 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlow.swift @@ -1,5 +1,4 @@ import SwiftUI -import WooFoundation // MARK: - Point of Sale Barcode Scanner Setup Flow @available(iOS 17.0, *) @@ -10,10 +9,10 @@ class PointOfSaleBarcodeScannerSetupFlow { fileprivate let onDismiss: () -> Void private var flowSteps: [PointOfSaleBarcodeScannerStepID: PointOfSaleBarcodeScannerSetupStep] = [:] private(set) var currentStepKey: PointOfSaleBarcodeScannerStepID = .setupBarcodeHID - private let analytics: Analytics + private let analytics: POSAnalyticsProviding init(scannerType: PointOfSaleBarcodeScannerType, - analytics: Analytics = ServiceLocator.analytics, + analytics: POSAnalyticsProviding, onBackToSelection: @escaping () -> Void, onDismiss: @escaping () -> Void) { self.scannerType = scannerType @@ -406,31 +405,31 @@ struct PointOfSaleBarcodeScannerProductBarcodeSetupInformationButtonCustomizatio @available(iOS 17.0, *) private extension PointOfSaleBarcodeScannerSetupFlow { private func trackTestScanSuccess() { - analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupTestScanSuccess(scanner: scannerType)) + analytics.track(event: .PointOfSale.barcodeScannerSetupTestScanSuccess(scanner: scannerType)) } private func trackTestScanFailed(scanValue: String) { - analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupTestScanFailed(scanner: scannerType, scanValue: scanValue)) + analytics.track(event: .PointOfSale.barcodeScannerSetupTestScanFailed(scanner: scannerType, scanValue: scanValue)) } private func trackTestScanTimedOut() { - analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupTestScanTimedOut(scanner: scannerType)) + analytics.track(event: .PointOfSale.barcodeScannerSetupTestScanTimedOut(scanner: scannerType)) } private func trackSetupNext() { if let step = getCurrentAnalyticsStepValue() { - analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupNextTapped(scanner: scannerType, step: step)) + analytics.track(event: .PointOfSale.barcodeScannerSetupNextTapped(scanner: scannerType, step: step)) } } private func trackSetupBack() { if let step = getCurrentAnalyticsStepValue() { - analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupBackTapped(scanner: scannerType, step: step)) + analytics.track(event: .PointOfSale.barcodeScannerSetupBackTapped(scanner: scannerType, step: step)) } } private func trackRetry() { - analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupRetryTapped(scanner: scannerType)) + analytics.track(event: .PointOfSale.barcodeScannerSetupRetryTapped(scanner: scannerType)) } } diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift index 363d8f8a4fd..020e531c117 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManager.swift @@ -9,14 +9,14 @@ class PointOfSaleBarcodeScannerSetupFlowManager { var currentState: PointOfSaleBarcodeScannerSetupFlowState = .scannerSelection @ObservationIgnored @Binding var isPresented: Bool private var currentFlow: PointOfSaleBarcodeScannerSetupFlow? - private let analytics: Analytics + private let analytics: POSAnalyticsProviding private var keyboardObserver: NSObjectProtocol? var currentStepKey: String? { currentFlow?.currentStepKey.rawValue } - init(isPresented: Binding, analytics: Analytics = ServiceLocator.analytics) { + init(isPresented: Binding, analytics: POSAnalyticsProviding) { self._isPresented = isPresented self.analytics = analytics setupKeyboardObserver() diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupStepViews.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupStepViews.swift index ba8d425ee79..038a3e2fe62 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupStepViews.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupStepViews.swift @@ -37,6 +37,7 @@ extension PointOfSaleBarcodeScannerBarcodeView { } struct PointOfSaleBarcodeScannerPairingView: View { + @Environment(\.posAnalytics) private var analytics let scanner: PointOfSaleBarcodeScannerType var body: some View { @@ -58,7 +59,7 @@ struct PointOfSaleBarcodeScannerPairingView: View { } Button { - ServiceLocator.analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupOpenSystemSettingsTapped(scanner: scanner)) + analytics.track(event: WooAnalyticsEvent.PointOfSale.barcodeScannerSetupOpenSystemSettingsTapped(scanner: scanner)) guard let targetURL = URL(string: UIApplication.openSettingsURLString) else { return diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupTypes.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupTypes.swift new file mode 100644 index 00000000000..54cc92cecb9 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupTypes.swift @@ -0,0 +1,33 @@ +import SwiftUI + +// MARK: - Button Customization Protocol +@available(iOS 17.0, *) +protocol PointOfSaleBarcodeScannerButtonCustomization { + func customizeButtons(for flow: PointOfSaleBarcodeScannerSetupFlow) -> PointOfSaleFlowButtonConfiguration +} + +// MARK: - Transition Types +public enum PointOfSaleBarcodeScannerTransitionType: Hashable { + case next + case retry + case back +} + +// MARK: - Setup Step +@available(iOS 17.0, *) +struct PointOfSaleBarcodeScannerSetupStep { + let title: String + let content: any View + let buttonCustomization: PointOfSaleBarcodeScannerButtonCustomization? + let transitions: [PointOfSaleBarcodeScannerTransitionType: PointOfSaleBarcodeScannerStepID] + + init(title: String = "", + @ViewBuilder content: () -> any View, + buttonCustomization: PointOfSaleBarcodeScannerButtonCustomization? = nil, + transitions: [PointOfSaleBarcodeScannerTransitionType: PointOfSaleBarcodeScannerStepID] = [:]) { + self.title = title + self.content = content() + self.buttonCustomization = buttonCustomization + self.transitions = transitions + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanning/BarcodeScannerContainer.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanning/BarcodeScannerContainer.swift index 3836a34664c..8fd2874a670 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanning/BarcodeScannerContainer.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanning/BarcodeScannerContainer.swift @@ -13,6 +13,8 @@ struct BarcodeScannerContainer: View { /// Callback that is triggered when a barcode scan completes (success or failure) let onScan: (Result) -> Void + @Environment(\.posAnalytics) private var analytics + init( configuration: HIDBarcodeParserConfiguration = .default, onScan: @escaping (Result) -> Void @@ -24,6 +26,7 @@ struct BarcodeScannerContainer: View { var body: some View { BarcodeScannerContainerRepresentable( configuration: configuration, + analytics: analytics, onScan: onScan ) .frame(width: 0, height: 0) @@ -37,11 +40,13 @@ struct BarcodeScannerContainer: View { /// keyboard input for barcode scanning. struct BarcodeScannerContainerRepresentable: UIViewControllerRepresentable { let configuration: HIDBarcodeParserConfiguration + let analytics: POSAnalyticsProviding let onScan: (Result) -> Void func makeUIViewController(context: Context) -> UIViewController { return GameControllerBarcodeScannerHostingController( configuration: configuration, + analytics: analytics, onScan: onScan ) } @@ -56,11 +61,16 @@ final class GameControllerBarcodeScannerHostingController: UIHostingController) -> Void ) { super.init(rootView: EmptyView()) - gameControllerBarcodeObserver = GameControllerBarcodeObserver(configuration: configuration, onScan: onScan) + gameControllerBarcodeObserver = GameControllerBarcodeObserver( + configuration: configuration, + analytics: analytics, + onScan: onScan + ) } @MainActor required dynamic init?(coder aDecoder: NSCoder) { diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeObserver.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeObserver.swift index 6a902238d64..a381faf7c79 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeObserver.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeObserver.swift @@ -19,6 +19,7 @@ final class GameControllerBarcodeObserver { private var coalescedKeyboard: GCKeyboard? private var barcodeParser: GameControllerBarcodeParser? private let configuration: HIDBarcodeParserConfiguration + private let analytics: POSAnalyticsProviding /// Tracks current shift state to be applied to the next character key private var isShiftPressed: Bool = false @@ -27,8 +28,11 @@ final class GameControllerBarcodeObserver { /// - Parameters: /// - configuration: The configuration to use for the barcode parser. Defaults to the standard configuration. /// - onScan: The closure to be called when a scan is completed. - init(configuration: HIDBarcodeParserConfiguration = .default, onScan: @escaping (Result) -> Void) { + init(configuration: HIDBarcodeParserConfiguration = .default, + analytics: POSAnalyticsProviding, + onScan: @escaping (Result) -> Void) { self.onScan = onScan + self.analytics = analytics self.configuration = configuration addObservers() setupCoalescedKeyboard() @@ -126,14 +130,14 @@ final class GameControllerBarcodeObserver { private func trackAnalyticsEvent(for result: HIDBarcodeParserResult) { switch result { case .success(let barcode, let scanDurationMs): - ServiceLocator.analytics.track( + analytics.track( event: WooAnalyticsEvent.PointOfSale.barcodeScanningSuccess( scanDurationMs: scanDurationMs, barcodeLength: barcode.count ) ) case .failure(let error, let scanDurationMs): - ServiceLocator.analytics.track( + analytics.track( event: WooAnalyticsEvent.PointOfSale.barcodeScanningFailed( scanDurationMs: scanDurationMs, barcodeLength: error.barcode.count, diff --git a/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeParser.swift b/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeParser.swift index e2f4c25409b..5a7e3bced82 100644 --- a/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeParser.swift +++ b/WooCommerce/Classes/POS/Presentation/Barcode Scanning/GameControllerBarcodeParser.swift @@ -227,38 +227,3 @@ struct HIDBarcodeParserConfiguration { maximumInterCharacterTime: 0.2 ) } - -enum HIDBarcodeParserError: Error { - case scanTooShort(barcode: String) - case timedOut(barcode: String) - - var analyticsReason: String { - switch self { - case .scanTooShort: - return "too_short" - case .timedOut: - return "no_terminator" - } - } - - var barcode: String { - switch self { - case .scanTooShort(let barcode), .timedOut(let barcode): - return barcode - } - } -} - -enum HIDBarcodeParserResult { - case success(barcode: String, scanDurationMs: Int) - case failure(error: HIDBarcodeParserError, scanDurationMs: Int) - - var asResult: Result { - switch self { - case .success(let barcode, _): - return .success(barcode) - case .failure(let error, _): - return .failure(error) - } - } -} diff --git a/WooCommerce/Classes/POS/Presentation/CartView.swift b/WooCommerce/Classes/POS/Presentation/CartView.swift index a9ecb0380e5..dc30d29cee9 100644 --- a/WooCommerce/Classes/POS/Presentation/CartView.swift +++ b/WooCommerce/Classes/POS/Presentation/CartView.swift @@ -3,6 +3,7 @@ import SwiftUI @available(iOS 17.0, *) struct CartView: View { @Environment(PointOfSaleAggregateModel.self) private var posModel + @Environment(\.posAnalytics) private var analytics private let viewHelper = CartViewHelper() @Environment(\.floatingControlAreaSize) var floatingControlAreaSize: CGSize @@ -182,7 +183,7 @@ private extension CartView { case .finalizing: let state: POSPageHeaderBackButtonConfiguration.State = shouldPreventCartEditing ? .shimmering : .enabled return .init(state: state, action: { - ServiceLocator.analytics.track(.pointOfSaleBackToCartTapped) + analytics.track(.pointOfSaleBackToCartTapped) posModel.addMoreToCart() }) } @@ -215,6 +216,8 @@ private extension CartView { @available(iOS 17.0, *) private struct CartClearMenuButton: View { + @Environment(\.posAnalytics) private var analytics + let removeAllItemsFromCart: () -> Void var body: some View { @@ -222,7 +225,7 @@ private struct CartClearMenuButton: View { Button(role: .destructive, action: { removeAllItemsFromCart() - ServiceLocator.analytics.track(.pointOfSaleClearCartTapped) + analytics.track(.pointOfSaleClearCartTapped) }) { Text(Localization.clearButtonTitle) } @@ -354,6 +357,7 @@ private struct CartScrollViewContent: View { @available(iOS 17.0, *) private struct CouponsCartSection: View { @Environment(PointOfSaleAggregateModel.self) private var posModel + @Environment(\.posAnalytics) private var analytics @Binding var shouldShowItemImages: Bool private let viewHelper = CartViewHelper() @@ -370,12 +374,10 @@ private struct CouponsCartSection: View { ), showImage: $shouldShowItemImages, onItemRemoveTapped: posModel.orderStage == .building ? { - ServiceLocator.analytics.track( - event: .PointOfSale.itemRemovedFromCart( + analytics.track(event: .PointOfSale.itemRemovedFromCart( sourceView: .cart, itemType: .coupon - ) - ) + )) posModel.remove(cartItem: couponItem) } : nil ) @@ -389,6 +391,7 @@ private struct CouponsCartSection: View { @available(iOS 17.0, *) private struct PurchasableItemsCartSection: View { @Environment(PointOfSaleAggregateModel.self) private var posModel + @Environment(\.posAnalytics) private var analytics @Binding var shouldShowItemImages: Bool @AccessibilityFocusState private var accessibilityFocusedItem: UUID? @@ -419,7 +422,7 @@ private struct PurchasableItemsCartSection: View { guard posModel.orderStage == .building else { return nil } return { - ServiceLocator.analytics.track( + analytics.track( event: .PointOfSale.itemRemovedFromCart( sourceView: .cart, itemType: .init(cartItem: cartItem), @@ -448,7 +451,7 @@ private extension CartView { func trackCheckoutTapped() { let purchasableItems = posModel.cart.purchasableItems.count let coupons = posModel.cart.coupons.count - ServiceLocator.analytics.track( + analytics.track( event: .PointOfSale.checkoutTapped( purchasableItemsInCart: purchasableItems, couponsInCart: coupons diff --git a/WooCommerce/Classes/POS/Presentation/Item Search/POSPreSearchView.swift b/WooCommerce/Classes/POS/Presentation/Item Search/POSPreSearchView.swift index 8e151f5902d..7d7f2999411 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Search/POSPreSearchView.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Search/POSPreSearchView.swift @@ -4,6 +4,7 @@ import enum Yosemite.POSItem @available(iOS 17.0, *) struct POSPreSearchView: View { @Environment(PointOfSaleAggregateModel.self) private var posModel + @Environment(\.posAnalytics) private var analytics @ScaledMetric private var chipHeight: CGFloat = 56.0 let savedSearches: [String] @@ -27,7 +28,8 @@ struct POSPreSearchView: View { itemActionHandler: StandardPOSItemActionHandler( posModel: posModel, sourceView: .init(itemListType: itemListType), - sourceViewType: .preSearch + sourceViewType: .preSearch, + analytics: analytics ), headerView: { VStack(alignment: .leading, spacing: POSSpacing.none) { diff --git a/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchView.swift b/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchView.swift index ff5c125e0ec..22696767b6c 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchView.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchView.swift @@ -22,6 +22,7 @@ protocol POSSearchable { @available(iOS 17.0, *) struct POSSearchField: View { @Environment(\.keyboardObserver) private var keyboardObserver + @Environment(\.posAnalytics) private var analytics @Binding private var searchTerm: String @FocusState private var isSearchFieldFocused: Bool @@ -96,7 +97,7 @@ struct POSSearchField: View { } .onChange(of: keyboardObserver.isKeyboardVisible) { _, isVisible in guard isVisible == false else { return } - ServiceLocator.analytics.track(.pointOfSaleKeyboardDismissedInSearch) + analytics.track(.pointOfSaleKeyboardDismissedInSearch) } .onAppear { isSearchFieldFocused = true @@ -108,6 +109,7 @@ struct POSSearchField: View { @available(iOS 17.0, *) struct POSSearchContentView: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize + @Environment(\.posAnalytics) private var analytics private let searchable: any POSSearchable @Binding private var searchTerm: String @@ -136,8 +138,7 @@ struct POSSearchContentView: View { POSPreSearchView(savedSearches: searchable.searchHistory, onSearchSelected: { selectedSearchTerm in searchTerm = selectedSearchTerm - ServiceLocator.analytics.track( - event: .PointOfSale.preSearchRecentTermTapped(itemListType: searchable.itemListType)) + analytics.track(event: .PointOfSale.preSearchRecentTermTapped(itemListType: searchable.itemListType)) }, itemListType: searchable.itemListType ) diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift index 8c781b07e15..60ef336943d 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift @@ -177,7 +177,8 @@ private extension ChildItemList { itemActionHandler: PointOfSalePreviewItemActionHandler(), analyticsTracker: PointOfSaleItemListAnalyticsTracker( sourceView: .variation, - sourceViewType: .list + sourceViewType: .list, + analytics: POSPreviewAnalytics() )) } @@ -203,7 +204,8 @@ private extension ChildItemList { itemActionHandler: PointOfSalePreviewItemActionHandler(), analyticsTracker: PointOfSaleItemListAnalyticsTracker( sourceView: .variation, - sourceViewType: .list + sourceViewType: .list, + analytics: POSPreviewAnalytics() )) } diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift index 1039f5aa94f..dd1ddef5edf 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift @@ -8,6 +8,7 @@ struct ItemList: View { @Environment(\.floatingControlAreaSize) private var floatingControlAreaSize: CGSize @Environment(PointOfSaleAggregateModel.self) private var posModel @Environment(\.keyboardObserver) private var keyboardObserver + @Environment(\.posAnalytics) private var analytics @StateObject private var infiniteScrollTriggerDeterminer = ThresholdInfiniteScrollTriggerDeterminer() // Navigation only uses this on iOS 17 @@ -88,7 +89,9 @@ struct ItemList: View { sourceViewType: .init( isSearching: posModel.viewStateCoordinatorForView.selectedItemListType.isSearching, searchTerm: posModel.viewStateCoordinatorForView.searchTerm - ))) + ), + analytics: analytics + )) .barcodeScanning { scannedCode in posModel.barcodeScanned(scannedCode) }, diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/POSItemActionHandler.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/POSItemActionHandler.swift index 8e2205c65b2..bae6a14edde 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/POSItemActionHandler.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/POSItemActionHandler.swift @@ -22,7 +22,7 @@ extension POSItemActionHandler { for item: POSItem, sourceView: WooAnalyticsEvent.PointOfSale.SourceView, sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType, - using analytics: Analytics + using analytics: POSAnalyticsProviding ) { switch item { case .simpleProduct: @@ -72,12 +72,12 @@ final class StandardPOSItemActionHandler: POSItemActionHandler { private let posModel: PointOfSaleAggregateModelProtocol private let sourceView: WooAnalyticsEvent.PointOfSale.SourceView private let sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType - private let analytics: Analytics + private let analytics: POSAnalyticsProviding init(posModel: PointOfSaleAggregateModelProtocol, sourceView: WooAnalyticsEvent.PointOfSale.SourceView, sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType, - analytics: Analytics = ServiceLocator.analytics + analytics: POSAnalyticsProviding ) { self.posModel = posModel self.sourceView = sourceView @@ -107,13 +107,13 @@ final class SearchResultItemActionHandler: POSItemActionHandler { private let searchTerm: String private let itemType: POSItemType private let sourceView: WooAnalyticsEvent.PointOfSale.SourceView - private let analytics: Analytics + private let analytics: POSAnalyticsProviding init(posModel: PointOfSaleAggregateModelProtocol, searchTerm: String, itemType: POSItemType, sourceView: WooAnalyticsEvent.PointOfSale.SourceView, - analytics: Analytics = ServiceLocator.analytics) { + analytics: POSAnalyticsProviding) { self.posModel = posModel self.searchTerm = searchTerm self.itemType = itemType @@ -147,7 +147,7 @@ struct POSItemActionHandlerFactory { itemListType: ItemListType, searchTerm: String, posModel: PointOfSaleAggregateModelProtocol, - analytics: Analytics = ServiceLocator.analytics + analytics: POSAnalyticsProviding ) -> POSItemActionHandler { switch itemListType { case .products(search: false): @@ -165,7 +165,7 @@ struct POSItemActionHandlerFactory { itemListType: ItemListType, searchTerm: String, posModel: PointOfSaleAggregateModelProtocol, - analytics: Analytics = ServiceLocator.analytics + analytics: POSAnalyticsProviding ) -> POSItemActionHandler { if itemListType.isSearching { SearchResultItemActionHandler( diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/PointOfSaleItemListAnalyticsTracker.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/PointOfSaleItemListAnalyticsTracker.swift index 26340160af2..8525062ed5a 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/PointOfSaleItemListAnalyticsTracker.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/PointOfSaleItemListAnalyticsTracker.swift @@ -5,12 +5,12 @@ import enum Yosemite.POSItemType struct PointOfSaleItemListAnalyticsTracker { private let sourceView: WooAnalyticsEvent.PointOfSale.SourceView private let sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType - private let analytics: Analytics + private let analytics: POSAnalyticsProviding init( sourceView: WooAnalyticsEvent.PointOfSale.SourceView, sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType, - analytics: Analytics = ServiceLocator.analytics + analytics: POSAnalyticsProviding ) { self.sourceView = sourceView self.sourceViewType = sourceViewType @@ -20,7 +20,7 @@ struct PointOfSaleItemListAnalyticsTracker { init( selectedItemListType: ItemListType, searchTerm: String, - analytics: Analytics = ServiceLocator.analytics + analytics: POSAnalyticsProviding ) { switch selectedItemListType { case .products(search: false): diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 096dcea8fe9..f08ad1a5cf3 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -5,6 +5,7 @@ import protocol Yosemite.POSOrderableItem @available(iOS 17.0, *) struct ItemListView: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize + @Environment(\.posAnalytics) private var analytics @Environment(PointOfSaleAggregateModel.self) private var posModel @Environment(\.keyboardObserver) private var keyboardObserver @@ -14,7 +15,11 @@ struct ItemListView: View { @Binding var searchTerm: String private var analyticsTracker: PointOfSaleItemListAnalyticsTracker { - PointOfSaleItemListAnalyticsTracker(selectedItemListType: selectedItemListType, searchTerm: searchTerm) + PointOfSaleItemListAnalyticsTracker( + selectedItemListType: selectedItemListType, + searchTerm: searchTerm, + analytics: analytics + ) } private var _isSearching: Binding { @@ -184,7 +189,8 @@ struct ItemListView: View { POSItemActionHandlerFactory.itemActionHandler( itemListType: itemListType, searchTerm: searchTerm, - posModel: posModel + posModel: posModel, + analytics: analytics ) } @@ -192,7 +198,8 @@ struct ItemListView: View { POSItemActionHandlerFactory.variationActionHandler( itemListType: itemListType, searchTerm: searchTerm, - posModel: posModel + posModel: posModel, + analytics: analytics ) } @@ -208,7 +215,8 @@ struct ItemListView: View { itemActionHandler: variationActionHandler(selectedItemListType), analyticsTracker: PointOfSaleItemListAnalyticsTracker( sourceView: .variation, - sourceViewType: .init(isSearching: selectedItemListType.isSearching, searchTerm: searchTerm) + sourceViewType: .init(isSearching: selectedItemListType.isSearching, searchTerm: searchTerm), + analytics: analytics ) ) .barcodeScanning(enabled: isBarcodeScanningEnabled) { scannedCode in @@ -299,7 +307,7 @@ private extension ItemListView { @ViewBuilder private var createCouponButton: some View { POSPageHeaderActionButton(systemName: "plus") { - ServiceLocator.analytics.track(.pointOfSaleCouponsCreateTapped) + analytics.track(.pointOfSaleCouponsCreateTapped) showCouponCreationModal = true } .renderedIf(isAddingCouponAllowed) @@ -360,7 +368,7 @@ private extension ItemListView { PointOfSaleItemListErrorView(error: errorState, onAction: { Task { await posModel.couponsController.enableCoupons() - ServiceLocator.analytics.track(.couponSettingEnabled) + analytics.track(.couponSettingEnabled) } }) default: diff --git a/WooCommerce/Classes/POS/Presentation/Order Messages/PointOfSaleOrderSyncCouponsErrorMessageView.swift b/WooCommerce/Classes/POS/Presentation/Order Messages/PointOfSaleOrderSyncCouponsErrorMessageView.swift index f0ab9987c87..6980f0897c0 100644 --- a/WooCommerce/Classes/POS/Presentation/Order Messages/PointOfSaleOrderSyncCouponsErrorMessageView.swift +++ b/WooCommerce/Classes/POS/Presentation/Order Messages/PointOfSaleOrderSyncCouponsErrorMessageView.swift @@ -8,6 +8,7 @@ struct PointOfSaleOrderSyncCouponsErrorMessageView: View { @Environment(PointOfSaleAggregateModel.self) private var posModel @Environment(\.dynamicTypeSize) var dynamicTypeSize + @Environment(\.posAnalytics) private var analytics var body: some View { GeometryReader { geometry in @@ -34,12 +35,10 @@ struct PointOfSaleOrderSyncCouponsErrorMessageView: View { .buttonStyle(POSFilledButtonStyle(size: .normal)) Button(retryActionTitle, action: { - ServiceLocator.analytics.track( - event: .PointOfSale.itemRemovedFromCart( + analytics.track(event: .PointOfSale.itemRemovedFromCart( sourceView: .error, itemType: .coupon - ) - ) + )) posModel.removeAllItemsFromCart(types: [.coupon]) retryHandler() }) diff --git a/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift b/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift index 79fa802323a..8c8c302d6cf 100644 --- a/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift +++ b/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift @@ -5,6 +5,7 @@ struct POSFloatingControlView: View { @Environment(\.posBackgroundAppearance) var backgroundAppearance @Environment(PointOfSaleAggregateModel.self) private var posModel @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Environment(\.posAnalytics) private var analytics @Binding private var showExitPOSModal: Bool @Binding private var showSupport: Bool @Binding private var showDocumentation: Bool @@ -23,7 +24,7 @@ struct POSFloatingControlView: View { HStack { Menu { Button { - ServiceLocator.analytics.track(.pointOfSaleExitMenuItemTapped) + analytics.track(.pointOfSaleExitMenuItemTapped) showExitPOSModal = true } label: { Label( @@ -32,7 +33,7 @@ struct POSFloatingControlView: View { ) } Button { - ServiceLocator.analytics.track(.pointOfSaleGetSupportTapped) + analytics.track(.pointOfSaleGetSupportTapped) showSupport = true } label: { Label( @@ -42,7 +43,7 @@ struct POSFloatingControlView: View { } Button { showDocumentation = true - ServiceLocator.analytics.track(.pointOfSaleViewDocsTapped) + analytics.track(.pointOfSaleViewDocsTapped) } label: { Label( title: { Text(Localization.viewDocumentation) }, @@ -51,7 +52,7 @@ struct POSFloatingControlView: View { } Button { showProductRestrictionsModal = true - ServiceLocator.analytics.track(.pointOfSaleSimpleProductsExplanationDialogShown) + analytics.track(.pointOfSaleSimpleProductsExplanationDialogShown) } label: { Label( title: { Text(Localization.productRestrictionsInfo) }, @@ -60,7 +61,7 @@ struct POSFloatingControlView: View { if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleBarcodeScanningi1) { Button { showBarcodeScanningModal = true - ServiceLocator.analytics.track(.pointOfSaleBarcodeScanningMenuItemTapped) + analytics.track(.pointOfSaleBarcodeScanningMenuItemTapped) } label: { Label( title: { Text(Localization.barcodeScanning) }, @@ -94,7 +95,7 @@ struct POSFloatingControlView: View { } .posModal(isPresented: $showBarcodeScanningModal) { if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pointOfSaleBarcodeScanningi2) { - PointOfSaleBarcodeScannerSetup(isPresented: $showBarcodeScanningModal) + PointOfSaleBarcodeScannerSetup(isPresented: $showBarcodeScanningModal, analytics: analytics) } else { PointOfSaleBarcodeScannerInformationModal(isPresented: $showBarcodeScanningModal) } diff --git a/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift b/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift index 1c5c15634b7..a83f9acd3b1 100644 --- a/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift +++ b/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift @@ -3,6 +3,7 @@ import SwiftUI @available(iOS 17.0, *) struct PaymentsActionButtons: View { @Environment(PointOfSaleAggregateModel.self) private var posModel + @Environment(\.posAnalytics) private var analytics @Binding var isShowingSendReceiptView: Bool @Binding private(set) var isShowingReceiptNotEligibleBanner: Bool @@ -23,7 +24,7 @@ private extension PaymentsActionButtons { var sendReceiptButton: some View { Button(action: { Task { @MainActor in - ServiceLocator.analytics.track(.receiptEmailTapped) + analytics.track(.receiptEmailTapped) await handleSendReceiptAction() } }, label: { @@ -36,7 +37,7 @@ private extension PaymentsActionButtons { var newOrderButton: some View { Button(action: { - ServiceLocator.analytics.track(.pointOfSaleCreateNewOrderTapped) + analytics.track(.pointOfSaleCreateNewOrderTapped) posModel.startNewCart() }, label: { HStack(spacing: Constants.buttonSpacing) { diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerInformationModal.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerInformationModal.swift index aa076e210ec..5e38891e265 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerInformationModal.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleBarcodeScannerInformationModal.swift @@ -16,6 +16,8 @@ struct PointOfSaleBarcodeScannerInformationModal: View { } struct LegacyBarcodeScannerInformationContent: View { + @Environment(\.posAnalytics) private var analytics + var body: some View { VStack(spacing: POSSpacing.medium) { PointOfSaleInformationModalParagraphView { @@ -39,7 +41,7 @@ struct LegacyBarcodeScannerInformationContent: View { } } .onAppear(perform: { - ServiceLocator.analytics.track(.pointOfSaleBarcodeScanningExplanationDialogShown) + analytics.track(.pointOfSaleBarcodeScanningExplanationDialogShown) }) } @@ -59,6 +61,8 @@ struct LegacyBarcodeScannerInformationContent: View { } struct BarcodeScannerInformation: View { + @Environment(\.posAnalytics) private var analytics + var body: some View { VStack(spacing: POSSpacing.xLarge) { Text(Localization.scannerInfoHeading) @@ -86,13 +90,15 @@ struct BarcodeScannerInformation: View { } } .onAppear(perform: { - ServiceLocator.analytics.track(.pointOfSaleBarcodeScanningExplanationDialogShown) + analytics.track(.pointOfSaleBarcodeScanningExplanationDialogShown) }) } } @available(iOS 17.0, *) struct ProductBarcodeSetupInformation: View { + @Environment(\.posAnalytics) private var analytics + var body: some View { VStack(spacing: POSSpacing.xLarge) { Text(Localization.productBarcodeInfoHeading) @@ -112,7 +118,7 @@ struct ProductBarcodeSetupInformation: View { .aspectRatio(contentMode: .fit) } .onAppear(perform: { - ServiceLocator.analytics.track(.pointOfSaleBarcodeScanningExplanationDialogShown) + analytics.track(.pointOfSaleBarcodeScanningExplanationDialogShown) }) } diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift index e4534694753..fb1f733f9f2 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift @@ -20,6 +20,7 @@ struct PointOfSaleEntryPointView: View { private let searchHistoryService: POSSearchHistoryProviding private let popularPurchasableItemsController: PointOfSaleItemsControllerProtocol private let barcodeScanService: PointOfSaleBarcodeScanServiceProtocol + private let analytics: POSAnalyticsProviding init(itemsController: PointOfSaleItemsControllerProtocol, purchasableItemsSearchController: PointOfSaleSearchingItemsControllerProtocol, @@ -32,7 +33,8 @@ struct PointOfSaleEntryPointView: View { searchHistoryService: POSSearchHistoryProviding, popularPurchasableItemsController: PointOfSaleItemsControllerProtocol, barcodeScanService: PointOfSaleBarcodeScanServiceProtocol, - posEligibilityChecker: POSEntryPointEligibilityCheckerProtocol) { + posEligibilityChecker: POSEntryPointEligibilityCheckerProtocol, + analytics: POSAnalyticsProviding) { self.onPointOfSaleModeActiveStateChange = onPointOfSaleModeActiveStateChange self.itemsController = itemsController @@ -46,6 +48,7 @@ struct PointOfSaleEntryPointView: View { self.popularPurchasableItemsController = popularPurchasableItemsController self.barcodeScanService = barcodeScanService self.posEntryPointController = POSEntryPointController(eligibilityChecker: posEligibilityChecker) + self.analytics = analytics } var body: some View { @@ -69,12 +72,14 @@ struct PointOfSaleEntryPointView: View { couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + analytics: analytics, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, popularPurchasableItemsController: popularPurchasableItemsController, barcodeScanService: barcodeScanService) } .environmentObject(posModalManager) + .environment(\.posAnalytics, analytics) .injectKeyboardObserver() .onAppear { onPointOfSaleModeActiveStateChange(true) @@ -101,7 +106,8 @@ struct PointOfSaleEntryPointView: View { searchHistoryService: PointOfSalePreviewHistoryService(), popularPurchasableItemsController: PointOfSalePreviewItemsController(), barcodeScanService: PointOfSalePreviewBarcodeScanService(), - posEligibilityChecker: POSTabEligibilityChecker(siteID: 0)) + posEligibilityChecker: POSTabEligibilityChecker(siteID: 0), + analytics: POSPreviewAnalytics()) } #endif diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleExitPosAlertView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleExitPosAlertView.swift index cb51b22d577..e009ffa2a3d 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleExitPosAlertView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleExitPosAlertView.swift @@ -3,6 +3,7 @@ import SwiftUI @available(iOS 17.0, *) struct PointOfSaleExitPosAlertView: View { @Environment(\.dismiss) private var dismiss + @Environment(\.posAnalytics) private var analytics @Binding private var isPresented: Bool init(isPresented: Binding) { @@ -28,7 +29,7 @@ struct PointOfSaleExitPosAlertView: View { .font(.posBodyLargeRegular()) .foregroundColor(Color.posOnSurface) Button { - ServiceLocator.analytics.track(.pointOfSaleExitConfirmed) + analytics.track(.pointOfSaleExitConfirmed) dismiss() } label: { Text(Localization.exitButton) diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift index e75f7e906f1..c4e74e9c9f7 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift @@ -7,6 +7,7 @@ import class WordPressShared.EmailFormatValidator struct POSSendReceiptView: View { @Environment(PointOfSaleAggregateModel.self) private var posModel @Environment(\.dynamicTypeSize) var dynamicTypeSize + @Environment(\.posAnalytics) private var analytics @State private var textFieldInput: String = "" @State private var isLoading: Bool = false @State private var errorMessage: String? @@ -93,7 +94,7 @@ struct POSSendReceiptView: View { } private func sendReceipt() { - ServiceLocator.analytics.track(.pointOfSaleReceiptEmailSendTapped) + analytics.track(.pointOfSaleReceiptEmailSendTapped) Task { @MainActor in guard isEmailValid else { errorMessage = Localization.emailValidationErrorText diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index b490cb7786e..84fbf0d797e 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -8,6 +8,7 @@ struct POSIneligibleView: View { let onRefresh: () async throws -> Void @Environment(\.dismiss) private var dismiss @Environment(\.sizeCategory) private var sizeCategory + @Environment(\.posAnalytics) private var analytics @State private var isLoading: Bool = false @State private var scrollViewHeight: CGFloat = 0 @State private var contentHeight: CGFloat = 0 @@ -60,9 +61,7 @@ struct POSIneligibleView: View { Task { @MainActor in do { isLoading = true - ServiceLocator.analytics.track( - event: .PointOfSaleIneligibleUI.ineligibleUIRetryTapped(reason: reason) - ) + analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIRetryTapped(reason: reason)) try await onRefresh() isLoading = false } catch { @@ -96,10 +95,10 @@ struct POSIneligibleView: View { contentHeight = height } .onAppear { - ServiceLocator.analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIShown(reason: reason)) + analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIShown(reason: reason)) } .onChange(of: reason) { newReason in - ServiceLocator.analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIShown(reason: newReason)) + analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIShown(reason: newReason)) } } .scrollDisabled(shouldDisableScrolling) diff --git a/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift b/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift index 23492bdb61b..e15aa1cb6db 100644 --- a/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift +++ b/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift @@ -5,6 +5,11 @@ import Yosemite import class WooFoundation.CurrencySettings import protocol Storage.StorageManagerType +/// TODO: Remove @_exported after the refactoring +/// Global import of PointOfSale +/// Avoid importing PointOfSale into many different files while in the middle of refactoring +@_exported import PointOfSale + /// View controller that provides the tab bar item for the Point of Sale tab. /// It is never visible on the screen, only used to provide the tab bar item as all POS UI is full-screen. final class POSTabViewController: UIViewController { @@ -88,7 +93,8 @@ private extension POSTabCoordinator { func presentPOSView() { Task { @MainActor [weak self] in guard let self else { return } - let collectOrderPaymentAnalyticsTracker = POSCollectOrderPaymentAnalytics() + let analyticsAdapter = POSAnalyticsAdapter() + let collectOrderPaymentAnalyticsTracker = POSCollectOrderPaymentAnalytics(analytics: analyticsAdapter) let cardPresentPaymentService = await CardPresentPaymentService(siteID: siteID, stores: storesManager, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker) @@ -101,31 +107,38 @@ private extension POSTabCoordinator { itemsController: PointOfSaleItemsController( itemProvider: PointOfSaleItemService( currencySettings: currencySettings), - itemFetchStrategyFactory: posItemFetchStrategyFactory), + itemFetchStrategyFactory: posItemFetchStrategyFactory, + analyticsProvider: analyticsAdapter), purchasableItemsSearchController: PointOfSaleItemsController( itemProvider: PointOfSaleItemService( currencySettings: currencySettings), itemFetchStrategyFactory: posItemFetchStrategyFactory, initialState: .init(containerState: .content, - itemsStack: .init(root: .loaded([], hasMoreItems: true), itemStates: [:]))), + itemsStack: .init(root: .loaded([], hasMoreItems: true), itemStates: [:])), + analyticsProvider: analyticsAdapter), couponsController: PointOfSaleCouponsController(itemProvider: posCouponProvider, - fetchStrategyFactory: posCouponFetchStrategyFactory), + fetchStrategyFactory: posCouponFetchStrategyFactory, + analyticsProvider: analyticsAdapter), couponsSearchController: PointOfSaleCouponsController(itemProvider: posCouponProvider, - fetchStrategyFactory: posCouponFetchStrategyFactory), + fetchStrategyFactory: posCouponFetchStrategyFactory, + analyticsProvider: analyticsAdapter), onPointOfSaleModeActiveStateChange: { [weak self] isEnabled in self?.updateDefaultConfigurationForPointOfSale(isEnabled) }, cardPresentPaymentService: cardPresentPaymentService, orderController: PointOfSaleOrderController(orderService: orderService, - receiptService: receiptService), + receiptService: receiptService, + analytics: analyticsAdapter), collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: POSSearchHistoryService(siteID: siteID), popularPurchasableItemsController: PointOfSaleItemsController( itemProvider: PointOfSaleItemService(currencySettings: currencySettings), - itemFetchStrategyFactory: posPopularItemFetchStrategyFactory + itemFetchStrategyFactory: posPopularItemFetchStrategyFactory, + analyticsProvider: analyticsAdapter ), barcodeScanService: barcodeScanService, - posEligibilityChecker: eligibilityChecker + posEligibilityChecker: eligibilityChecker, + analytics: analyticsAdapter ) let hostingController = UIHostingController(rootView: posView) hostingController.modalPresentationStyle = .fullScreen diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index 89706617730..f108605c40b 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -226,6 +226,7 @@ struct POSPreviewHelpers { couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + analytics: POSPreviewAnalytics(), collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, popularPurchasableItemsController: popularItemsController, @@ -279,4 +280,11 @@ final class POSCollectOrderPaymentPreviewAnalytics: POSCollectOrderPaymentAnalyt func trackReceiptPrintFailed(error: any Error) {} } +final class POSPreviewAnalytics: POSAnalyticsProviding { + func track(event: WooFoundationCore.WooAnalyticsEvent) {} + func track(_ stat: WooFoundationCore.WooAnalyticsStat, parameters: [String: any WooFoundationCore.WooAnalyticsEventPropertyType]) {} + func track(_ stat: WooFoundationCore.WooAnalyticsStat) {} + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType] = [:], error: Error) {} +} + #endif diff --git a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift index d54d67a3104..bad81aea6d8 100644 --- a/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift +++ b/WooCommerce/Classes/System/WatchDependenciesSynchronizer.swift @@ -3,6 +3,7 @@ import Combine import Networking import protocol WooFoundation.Analytics import class WooFoundation.CurrencySettings +import enum WooFoundationCore.WooAnalyticsStat /// Type that syncs the necessary dependencies to the watch session. /// diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift index 04ba3334b3f..fe272c12908 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift @@ -17,23 +17,6 @@ import protocol Yosemite.POSSiteSettingServiceProtocol import class Yosemite.POSSiteSettingService import enum Networking.SiteSettingsFeature -/// Represents the reasons why a site may be ineligible for POS. -enum POSIneligibleReason: Equatable { - case unsupportedIOSVersion - case unsupportedWooCommerceVersion(minimumVersion: String) - case siteSettingsNotAvailable - case wooCommercePluginNotFound - case featureSwitchDisabled - case unsupportedCurrency(countryCode: CountryCode, supportedCurrencies: [CurrencyCode]) - case selfDeallocated -} - -/// Represents the eligibility state for POS. -enum POSEligibilityState: Equatable { - case eligible - case ineligible(reason: POSIneligibleReason) -} - protocol POSEntryPointEligibilityCheckerProtocol { /// Checks the initial visibility of the POS tab. func checkInitialVisibility() -> Bool diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift index 4bcc2dbb226..d7307a9bc62 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift @@ -149,8 +149,6 @@ final class HubMenuViewModel: ObservableObject { credentials: credentials))) }() - private(set) var cardPresentPaymentService: CardPresentPaymentFacade? - private(set) var collectOrderPaymentAnalyticsTracker = POSCollectOrderPaymentAnalytics() private let analytics: Analytics init(siteID: Int64, @@ -178,7 +176,6 @@ final class HubMenuViewModel: ObservableObject { observePlanName() observeGoogleAdsEntryPointAvailability() tapToPayBadgePromotionChecker.$shouldShowTapToPayBadges.share().assign(to: &$shouldShowNewFeatureBadgeOnPayments) - createCardPresentPaymentService() } func viewDidAppear() async { @@ -290,14 +287,6 @@ private extension HubMenuViewModel { // MARK: - Helper methods // private extension HubMenuViewModel { - func createCardPresentPaymentService() { - Task { - self.cardPresentPaymentService = await CardPresentPaymentService(siteID: siteID, - stores: stores, - collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker) - } - } - func setupSettingsElements() { settingsElements = [Settings()] diff --git a/WooCommerce/Woo Watch App/App/WatchTracksProvider.swift b/WooCommerce/Woo Watch App/App/WatchTracksProvider.swift index 617e80249e5..8b39409bd55 100644 --- a/WooCommerce/Woo Watch App/App/WatchTracksProvider.swift +++ b/WooCommerce/Woo Watch App/App/WatchTracksProvider.swift @@ -1,5 +1,6 @@ import Foundation import WatchConnectivity +import enum WooFoundationCore.WooAnalyticsStat /// Delegate track events to the paired counterpart using the `WCSession` /// diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 9d17a69156f..809ea0be378 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -50,6 +50,16 @@ 0174DDBF2CE600C5005D20CA /* ReceiptEmailViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0174DDBE2CE600C0005D20CA /* ReceiptEmailViewModelTests.swift */; }; 0177250C2E1CFF7F00016148 /* GameControllerBarcodeParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177250B2E1CFF7F00016148 /* GameControllerBarcodeParser.swift */; }; 0177250E2E1CFF9B00016148 /* GameControllerBarcodeParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0177250D2E1CFF9B00016148 /* GameControllerBarcodeParserTests.swift */; }; + 017DC5C62E378E4300EC1605 /* WooAnalyticsStat+WPAnalyticsStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5C52E378E3B00EC1605 /* WooAnalyticsStat+WPAnalyticsStat.swift */; }; + 017DC5C92E37917100EC1605 /* POSAnalyticsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5C72E37917100EC1605 /* POSAnalyticsAdapter.swift */; }; + 017DC5CE2E379DF700EC1605 /* PointOfSaleFlowButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5CD2E379DF700EC1605 /* PointOfSaleFlowButtonsView.swift */; }; + 017DC5CF2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupStepViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5CB2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupStepViews.swift */; }; + 017DC5D02E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5CC2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupViews.swift */; }; + 017DC5D22E379E1200EC1605 /* PointOfSaleBarcodeScannerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5D12E379E1200EC1605 /* PointOfSaleBarcodeScannerSetup.swift */; }; + 017DC5D42E379EAA00EC1605 /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5D32E379EAA00EC1605 /* PointOfSaleBarcodeScannerSetupFlow.swift */; }; + 017DC5D62E379FA700EC1605 /* PointOfSaleBarcodeScannerSetupTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5D52E379FA000EC1605 /* PointOfSaleBarcodeScannerSetupTypes.swift */; }; + 017DC5D82E37A07100EC1605 /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5D72E37A07100EC1605 /* PointOfSaleBarcodeScannerSetupFlowManager.swift */; }; + 017DC5DA2E37A21400EC1605 /* PointOfSaleBarcodeScannerSetupScanTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DC5D92E37A21400EC1605 /* PointOfSaleBarcodeScannerSetupScanTester.swift */; }; 01806E132E2F7F400033363C /* POSBrightnessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01806E122E2F7F400033363C /* POSBrightnessControl.swift */; }; 0182C8BE2CE3B11300474355 /* MockReceiptEligibilityUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0182C8BD2CE3B10E00474355 /* MockReceiptEligibilityUseCase.swift */; }; 0182C8C02CE4DDC700474355 /* CardReaderTransactionAlertReceiptState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0182C8BF2CE4DDC100474355 /* CardReaderTransactionAlertReceiptState.swift */; }; @@ -77,7 +87,6 @@ 01AB2D162DDC8CDA00AA67FD /* MockAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AB2D152DDC8CD600AA67FD /* MockAnalytics.swift */; }; 01ADC1362C9AB4810036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ADC1352C9AB4810036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift */; }; 01ADC1382C9AB6050036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ADC1372C9AB6050036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift */; }; - 01B3A1F22DB6D48800286B7F /* ItemListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3A1F12DB6D48800286B7F /* ItemListType.swift */; }; 01B744E22D2FCA1400AEB3F4 /* PushNotificationBackgroundSynchronizerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B744E12D2FCA1300AEB3F4 /* PushNotificationBackgroundSynchronizerFactory.swift */; }; 01BB6C072D09DC560094D55B /* CardPresentModalLocationPreAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BB6C062D09DC470094D55B /* CardPresentModalLocationPreAlert.swift */; }; 01BB6C0A2D09E9630094D55B /* LocationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BB6C092D09E9630094D55B /* LocationService.swift */; }; @@ -86,11 +95,9 @@ 01BD77482C58D19C00147191 /* PointOfSaleCardPresentPaymentCancelledOnReaderMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BD77472C58D19C00147191 /* PointOfSaleCardPresentPaymentCancelledOnReaderMessageView.swift */; }; 01BD774A2C58D29700147191 /* PointOfSaleCardPresentPaymentDisconnectedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BD77492C58D29700147191 /* PointOfSaleCardPresentPaymentDisconnectedMessageView.swift */; }; 01BD774C2C58D2BE00147191 /* PointOfSaleCardPresentPaymentDisconnectedMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BD774B2C58D2BE00147191 /* PointOfSaleCardPresentPaymentDisconnectedMessageViewModel.swift */; }; - 01BE94002DDCB1110063541C /* Error+Connectivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BE93FF2DDCB1110063541C /* Error+Connectivity.swift */; }; 01BE94042DDCC7670063541C /* PointOfSaleEmptyErrorStateViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BE94032DDCC7650063541C /* PointOfSaleEmptyErrorStateViewLayout.swift */; }; 01C9C59F2DA3D98400CD81D8 /* CartRowRemoveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C9C59E2DA3D97E00CD81D8 /* CartRowRemoveButton.swift */; }; 01D082402C5B9EAB007FE81F /* POSBackgroundAppearanceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0823F2C5B9EAB007FE81F /* POSBackgroundAppearanceKey.swift */; }; - 01E62EC82DFADF56003A6D9E /* Cart+BarcodeScanError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E62EC72DFADF4B003A6D9E /* Cart+BarcodeScanError.swift */; }; 01F067ED2D0C5D59001C5805 /* MockLocationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F067EC2D0C5D56001C5805 /* MockLocationService.swift */; }; 01F42C162CE34AB8003D0A5A /* CardPresentModalTapToPaySuccessEmailSent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F42C152CE34AB3003D0A5A /* CardPresentModalTapToPaySuccessEmailSent.swift */; }; 01F42C182CE34AD2003D0A5A /* CardPresentModalSuccessEmailSent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F42C172CE34AD1003D0A5A /* CardPresentModalSuccessEmailSent.swift */; }; @@ -169,7 +176,6 @@ 02162727237963AF000208D2 /* ProductFormViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 02162725237963AF000208D2 /* ProductFormViewController.xib */; }; 02162729237965E8000208D2 /* ProductFormTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02162728237965E8000208D2 /* ProductFormTableViewModel.swift */; }; 0216272B2379662C000208D2 /* DefaultProductFormTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0216272A2379662C000208D2 /* DefaultProductFormTableViewModel.swift */; }; - 0216DA702E2576CB00016600 /* WooAnalyticsEvent+PointOfSaleIneligibleUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0216DA6F2E2576C300016600 /* WooAnalyticsEvent+PointOfSaleIneligibleUI.swift */; }; 0218B4EC242E06F00083A847 /* MediaType+WPMediaType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0218B4EB242E06F00083A847 /* MediaType+WPMediaType.swift */; }; 0219B03723964527007DCD5E /* PaginatedProductShippingClassListSelectorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0219B03623964527007DCD5E /* PaginatedProductShippingClassListSelectorDataSource.swift */; }; 021A17212D7036AF006DF7C0 /* DynamicFrameScaler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021A17202D7036AF006DF7C0 /* DynamicFrameScaler.swift */; }; @@ -582,7 +588,6 @@ 02CEBB8024C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CEBB7F24C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift */; }; 02CEBB8224C98861002EDF35 /* ProductFormDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CEBB8124C98861002EDF35 /* ProductFormDataModel.swift */; }; 02CEBB8424C99A10002EDF35 /* Product+ShippingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CEBB8324C99A10002EDF35 /* Product+ShippingTests.swift */; }; - 02D1D2DA2CD3CDA40069A93F /* WooAnalyticsEvent+PointOfSale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D1D2D92CD3CD8D0069A93F /* WooAnalyticsEvent+PointOfSale.swift */; }; 02D29A8E29F7C26000473D6D /* InputAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D29A8D29F7C26000473D6D /* InputAccessoryView.swift */; }; 02D29A9029F7C2DA00473D6D /* AztecAIViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D29A8F29F7C2DA00473D6D /* AztecAIViewFactory.swift */; }; 02D29A9229F7C39200473D6D /* UIImage+Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D29A9129F7C39200473D6D /* UIImage+Text.swift */; }; @@ -876,7 +881,6 @@ 207823E32C5D18CE00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207823E22C5D18CE00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertViewModel.swift */; }; 207823E52C5D1B2F00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207823E42C5D1B2F00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertView.swift */; }; 207823E92C5D3A1700025A59 /* POSErrorExclamationMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207823E82C5D3A1700025A59 /* POSErrorExclamationMark.swift */; }; - 207CEA852E1FD59B0023EC35 /* PointOfSaleBarcodeScannerSetupScanTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CEA842E1FD59B0023EC35 /* PointOfSaleBarcodeScannerSetupScanTester.swift */; }; 207CEA882E1FD6F80023EC35 /* PointOfSaleBarcodeScannerSetupScanTesterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CEA872E1FD6F80023EC35 /* PointOfSaleBarcodeScannerSetupScanTesterTests.swift */; }; 207D2D232CFDCCBF00F79204 /* MockPOSOrderableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207D2D222CFDCCBF00F79204 /* MockPOSOrderableItem.swift */; }; 207E71CB2C60F765008540FC /* MockPOSOrderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207E71CA2C60F765008540FC /* MockPOSOrderService.swift */; }; @@ -893,7 +897,6 @@ 2088784B2D96E98000F7AE03 /* PointOfSaleCardPresentPaymentConnectingLocationPreAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2088784A2D96E98000F7AE03 /* PointOfSaleCardPresentPaymentConnectingLocationPreAlertView.swift */; }; 2088784D2D96EA3900F7AE03 /* PointOfSaleCardPresentPaymentConnectingLocationPreAlertViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2088784C2D96EA3900F7AE03 /* PointOfSaleCardPresentPaymentConnectingLocationPreAlertViewModel.swift */; }; 20897C9E2D4A68C5008AD16C /* PointOfSaleUnsupportedWidthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20897C9D2D4A68C5008AD16C /* PointOfSaleUnsupportedWidthView.swift */; }; - 208C0F0A2E1FAC1900FE619E /* PointOfSaleBarcodeScannerSetupStepViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 208C0F092E1FAC1900FE619E /* PointOfSaleBarcodeScannerSetupStepViews.swift */; }; 209566252D4CF00100977124 /* PointOfSalePaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209566242D4CF00100977124 /* PointOfSalePaymentMethod.swift */; }; 209AD3D02AC1EDDA00825D76 /* WooPaymentsPayoutsCurrencyOverviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209AD3CF2AC1EDDA00825D76 /* WooPaymentsPayoutsCurrencyOverviewViewModel.swift */; }; 209AD3D22AC1EDF600825D76 /* WooPaymentsPayoutsCurrencyOverviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 209AD3D12AC1EDF600825D76 /* WooPaymentsPayoutsCurrencyOverviewView.swift */; }; @@ -921,12 +924,6 @@ 20BCF6F02B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6EF2B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift */; }; 20BCF6F72B0E5AF000954840 /* MockSystemStatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20BCF6F62B0E5AEF00954840 /* MockSystemStatusService.swift */; }; 20C3CC3C2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */; }; - 20C3DB232E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */; }; - 20C3DB242E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */; }; - 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */; }; - 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */; }; - 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */; }; - 20C3DB292E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */; }; 20C6E7512CDE4AEA00CD124C /* ItemListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */; }; 20C909962D3151FA0013BCCF /* ItemListBaseItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */; }; 20CC1EDB2AFA8381006BD429 /* InPersonPaymentsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */; }; @@ -1082,7 +1079,6 @@ 26838356296F702B00CCF60A /* GenerateAllVariationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26838355296F702B00CCF60A /* GenerateAllVariationsPresenter.swift */; }; 26838358296F9A1E00CCF60A /* GenerateAllVariationsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26838357296F9A1E00CCF60A /* GenerateAllVariationsUseCase.swift */; }; 2683835A296F9C1A00CCF60A /* GenerateAllVariationsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26838359296F9C1A00CCF60A /* GenerateAllVariationsUseCaseTests.swift */; }; - 268631CF2C07D38C00521364 /* WooAnalyticsStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74213449210A323C00C13890 /* WooAnalyticsStat.swift */; }; 2687165524D21BC80042F6AE /* SurveySubmittedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2687165324D21BC80042F6AE /* SurveySubmittedViewController.swift */; }; 2687165624D21BC80042F6AE /* SurveySubmittedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2687165424D21BC80042F6AE /* SurveySubmittedViewController.xib */; }; 2687165A24D350C20042F6AE /* SurveyCoordinatingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2687165924D350C20042F6AE /* SurveyCoordinatingController.swift */; }; @@ -1549,7 +1545,7 @@ 57CDABB9252E9BEB00BED88C /* ButtonTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CDABB8252E9BEB00BED88C /* ButtonTableFooterView.swift */; }; 57CFCD28248845B4003F51EC /* PrimarySectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57CFCD27248845B4003F51EC /* PrimarySectionHeaderView.swift */; }; 57CFCD2A2488496F003F51EC /* PrimarySectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 57CFCD292488496F003F51EC /* PrimarySectionHeaderView.xib */; }; - 57EBC92024EEE61800C1D45B /* WooAnalyticsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EBC91F24EEE61800C1D45B /* WooAnalyticsEvent.swift */; }; + 57EBC92024EEE61800C1D45B /* WooAnalyticsEvent+WooApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57EBC91F24EEE61800C1D45B /* WooAnalyticsEvent+WooApp.swift */; }; 57F2C6CD246DECC10074063B /* SummaryTableViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F2C6CC246DECC10074063B /* SummaryTableViewCellViewModelTests.swift */; }; 57F42E40253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F42E3F253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift */; }; 581D5052274AA2480089B6AD /* View+AutofocusTextModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 581D5051274AA2480089B6AD /* View+AutofocusTextModifier.swift */; }; @@ -1639,7 +1635,6 @@ 68E952D0287587BF0095A23D /* CardReaderManualRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */; }; 68E952D22875A44B0095A23D /* CardReaderType+Manual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */; }; 68ED2BD62ADD2C8C00ECA88D /* LineDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68ED2BD52ADD2C8C00ECA88D /* LineDetailView.swift */; }; - 68F151E12C0DA7910082AEC8 /* Cart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F151E02C0DA7910082AEC8 /* Cart.swift */; }; 68F68A502D6730E200BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */; }; 68F68A522D67365900BB9568 /* MockPOSCollectOrderPaymentAnalyticsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F68A512D67365900BB9568 /* MockPOSCollectOrderPaymentAnalyticsTracker.swift */; }; 68F896422D5E4323000B308B /* POSCollectOrderPaymentAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F896412D5E4321000B308B /* POSCollectOrderPaymentAnalytics.swift */; }; @@ -1647,7 +1642,6 @@ 740382DC2267D94100A627F4 /* LargeImageTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 740382DA2267D94100A627F4 /* LargeImageTableViewCell.xib */; }; 740987B321B87760000E4C80 /* FancyAnimatedButton+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740987B221B87760000E4C80 /* FancyAnimatedButton+Woo.swift */; }; 740ADFE521C33688009EE5A9 /* licenses.html in Resources */ = {isa = PBXBuildFile; fileRef = 740ADFE421C33688009EE5A9 /* licenses.html */; }; - 7421344A210A323C00C13890 /* WooAnalyticsStat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74213449210A323C00C13890 /* WooAnalyticsStat.swift */; }; 7435E58E21C0151B00216F0F /* OrderNote+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7435E58D21C0151B00216F0F /* OrderNote+Woo.swift */; }; 7435E59021C0162C00216F0F /* OrderNoteWooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7435E58F21C0162C00216F0F /* OrderNoteWooTests.swift */; }; 743E272021AEF20100D6DC82 /* FancyAlertViewController+Upgrade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 743E271F21AEF20100D6DC82 /* FancyAlertViewController+Upgrade.swift */; }; @@ -2536,7 +2530,6 @@ D8F82AC522AF903700B67E4B /* IconsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F82AC422AF903700B67E4B /* IconsTests.swift */; }; DA013F512C65125100D9A391 /* PointOfSaleExitPosAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA013F502C65125100D9A391 /* PointOfSaleExitPosAlertView.swift */; }; DA0DBE2F2C4FC61D00DF14C0 /* POSFloatingControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0DBE2E2C4FC61D00DF14C0 /* POSFloatingControlView.swift */; }; - DA1D68C22C36F0980097859A /* PointOfSaleAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1D68C12C36F0980097859A /* PointOfSaleAssets.swift */; }; DA24152B2D116EAE0008F69A /* WooShippingAddPackageViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA24152A2D116EA90008F69A /* WooShippingAddPackageViewModelTests.swift */; }; DA25ADDD2C86145E00AE81FE /* MarkOrderAsReadUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA25ADDC2C86145E00AE81FE /* MarkOrderAsReadUseCase.swift */; }; DA25ADDF2C87403900AE81FE /* PushNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA25ADDE2C87403900AE81FE /* PushNotificationTests.swift */; }; @@ -3228,6 +3221,16 @@ 0174DDBE2CE600C0005D20CA /* ReceiptEmailViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptEmailViewModelTests.swift; sourceTree = ""; }; 0177250B2E1CFF7F00016148 /* GameControllerBarcodeParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerBarcodeParser.swift; sourceTree = ""; }; 0177250D2E1CFF9B00016148 /* GameControllerBarcodeParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameControllerBarcodeParserTests.swift; sourceTree = ""; }; + 017DC5C52E378E3B00EC1605 /* WooAnalyticsStat+WPAnalyticsStat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsStat+WPAnalyticsStat.swift"; sourceTree = ""; }; + 017DC5C72E37917100EC1605 /* POSAnalyticsAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSAnalyticsAdapter.swift; sourceTree = ""; }; + 017DC5CB2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupStepViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupStepViews.swift; sourceTree = ""; }; + 017DC5CC2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupViews.swift; sourceTree = ""; }; + 017DC5CD2E379DF700EC1605 /* PointOfSaleFlowButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleFlowButtonsView.swift; sourceTree = ""; }; + 017DC5D12E379E1200EC1605 /* PointOfSaleBarcodeScannerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetup.swift; sourceTree = ""; }; + 017DC5D32E379EAA00EC1605 /* PointOfSaleBarcodeScannerSetupFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlow.swift; sourceTree = ""; }; + 017DC5D52E379FA000EC1605 /* PointOfSaleBarcodeScannerSetupTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupTypes.swift; sourceTree = ""; }; + 017DC5D72E37A07100EC1605 /* PointOfSaleBarcodeScannerSetupFlowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlowManager.swift; sourceTree = ""; }; + 017DC5D92E37A21400EC1605 /* PointOfSaleBarcodeScannerSetupScanTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupScanTester.swift; sourceTree = ""; }; 01806E122E2F7F400033363C /* POSBrightnessControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSBrightnessControl.swift; sourceTree = ""; }; 0182C8BD2CE3B10E00474355 /* MockReceiptEligibilityUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockReceiptEligibilityUseCase.swift; sourceTree = ""; }; 0182C8BF2CE4DDC100474355 /* CardReaderTransactionAlertReceiptState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderTransactionAlertReceiptState.swift; sourceTree = ""; }; @@ -3255,7 +3258,6 @@ 01AB2D152DDC8CD600AA67FD /* MockAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalytics.swift; sourceTree = ""; }; 01ADC1352C9AB4810036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentIntentCreationErrorMessageViewModel.swift; sourceTree = ""; }; 01ADC1372C9AB6050036F7D2 /* PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentIntentCreationErrorMessageView.swift; sourceTree = ""; }; - 01B3A1F12DB6D48800286B7F /* ItemListType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListType.swift; sourceTree = ""; }; 01B744E12D2FCA1300AEB3F4 /* PushNotificationBackgroundSynchronizerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationBackgroundSynchronizerFactory.swift; sourceTree = ""; }; 01BB6C062D09DC470094D55B /* CardPresentModalLocationPreAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalLocationPreAlert.swift; sourceTree = ""; }; 01BB6C092D09E9630094D55B /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = ""; }; @@ -3264,11 +3266,9 @@ 01BD77472C58D19C00147191 /* PointOfSaleCardPresentPaymentCancelledOnReaderMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentCancelledOnReaderMessageView.swift; sourceTree = ""; }; 01BD77492C58D29700147191 /* PointOfSaleCardPresentPaymentDisconnectedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentDisconnectedMessageView.swift; sourceTree = ""; }; 01BD774B2C58D2BE00147191 /* PointOfSaleCardPresentPaymentDisconnectedMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentDisconnectedMessageViewModel.swift; sourceTree = ""; }; - 01BE93FF2DDCB1110063541C /* Error+Connectivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Connectivity.swift"; sourceTree = ""; }; 01BE94032DDCC7650063541C /* PointOfSaleEmptyErrorStateViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleEmptyErrorStateViewLayout.swift; sourceTree = ""; }; 01C9C59E2DA3D97E00CD81D8 /* CartRowRemoveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartRowRemoveButton.swift; sourceTree = ""; }; 01D0823F2C5B9EAB007FE81F /* POSBackgroundAppearanceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSBackgroundAppearanceKey.swift; sourceTree = ""; }; - 01E62EC72DFADF4B003A6D9E /* Cart+BarcodeScanError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cart+BarcodeScanError.swift"; sourceTree = ""; }; 01F067EC2D0C5D56001C5805 /* MockLocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationService.swift; sourceTree = ""; }; 01F42C152CE34AB3003D0A5A /* CardPresentModalTapToPaySuccessEmailSent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalTapToPaySuccessEmailSent.swift; sourceTree = ""; }; 01F42C172CE34AD1003D0A5A /* CardPresentModalSuccessEmailSent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalSuccessEmailSent.swift; sourceTree = ""; }; @@ -3347,7 +3347,6 @@ 02162725237963AF000208D2 /* ProductFormViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProductFormViewController.xib; sourceTree = ""; }; 02162728237965E8000208D2 /* ProductFormTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormTableViewModel.swift; sourceTree = ""; }; 0216272A2379662C000208D2 /* DefaultProductFormTableViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultProductFormTableViewModel.swift; sourceTree = ""; }; - 0216DA6F2E2576C300016600 /* WooAnalyticsEvent+PointOfSaleIneligibleUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+PointOfSaleIneligibleUI.swift"; sourceTree = ""; }; 0218B4EB242E06F00083A847 /* MediaType+WPMediaType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MediaType+WPMediaType.swift"; sourceTree = ""; }; 0219B03623964527007DCD5E /* PaginatedProductShippingClassListSelectorDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginatedProductShippingClassListSelectorDataSource.swift; sourceTree = ""; }; 021A17202D7036AF006DF7C0 /* DynamicFrameScaler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicFrameScaler.swift; sourceTree = ""; }; @@ -3762,7 +3761,6 @@ 02CEBB7F24C9869E002EDF35 /* ProductFormActionsFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormActionsFactoryProtocol.swift; sourceTree = ""; }; 02CEBB8124C98861002EDF35 /* ProductFormDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormDataModel.swift; sourceTree = ""; }; 02CEBB8324C99A10002EDF35 /* Product+ShippingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Product+ShippingTests.swift"; sourceTree = ""; }; - 02D1D2D92CD3CD8D0069A93F /* WooAnalyticsEvent+PointOfSale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+PointOfSale.swift"; sourceTree = ""; }; 02D29A8D29F7C26000473D6D /* InputAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputAccessoryView.swift; sourceTree = ""; }; 02D29A8F29F7C2DA00473D6D /* AztecAIViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AztecAIViewFactory.swift; sourceTree = ""; }; 02D29A9129F7C39200473D6D /* UIImage+Text.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Text.swift"; sourceTree = ""; }; @@ -4062,7 +4060,6 @@ 207823E22C5D18CE00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentConnectionSuccessAlertViewModel.swift; sourceTree = ""; }; 207823E42C5D1B2F00025A59 /* PointOfSaleCardPresentPaymentConnectionSuccessAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentConnectionSuccessAlertView.swift; sourceTree = ""; }; 207823E82C5D3A1700025A59 /* POSErrorExclamationMark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSErrorExclamationMark.swift; sourceTree = ""; }; - 207CEA842E1FD59B0023EC35 /* PointOfSaleBarcodeScannerSetupScanTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupScanTester.swift; sourceTree = ""; }; 207CEA872E1FD6F80023EC35 /* PointOfSaleBarcodeScannerSetupScanTesterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupScanTesterTests.swift; sourceTree = ""; }; 207D2D222CFDCCBF00F79204 /* MockPOSOrderableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MockPOSOrderableItem.swift; path = ../Modules/Tests/YosemiteTests/Mocks/MockPOSOrderableItem.swift; sourceTree = SOURCE_ROOT; }; 207E71CA2C60F765008540FC /* MockPOSOrderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPOSOrderService.swift; sourceTree = ""; }; @@ -4079,7 +4076,6 @@ 2088784A2D96E98000F7AE03 /* PointOfSaleCardPresentPaymentConnectingLocationPreAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentConnectingLocationPreAlertView.swift; sourceTree = ""; }; 2088784C2D96EA3900F7AE03 /* PointOfSaleCardPresentPaymentConnectingLocationPreAlertViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentConnectingLocationPreAlertViewModel.swift; sourceTree = ""; }; 20897C9D2D4A68C5008AD16C /* PointOfSaleUnsupportedWidthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleUnsupportedWidthView.swift; sourceTree = ""; }; - 208C0F092E1FAC1900FE619E /* PointOfSaleBarcodeScannerSetupStepViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupStepViews.swift; sourceTree = ""; }; 209566242D4CF00100977124 /* PointOfSalePaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSalePaymentMethod.swift; sourceTree = ""; }; 209AD3CF2AC1EDDA00825D76 /* WooPaymentsPayoutsCurrencyOverviewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsPayoutsCurrencyOverviewViewModel.swift; sourceTree = ""; }; 209AD3D12AC1EDF600825D76 /* WooPaymentsPayoutsCurrencyOverviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsPayoutsCurrencyOverviewView.swift; sourceTree = ""; }; @@ -4106,12 +4102,6 @@ 20BCF6EF2B0E48CC00954840 /* WooPaymentsPayoutsOverviewViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooPaymentsPayoutsOverviewViewModelTests.swift; sourceTree = ""; }; 20BCF6F62B0E5AEF00954840 /* MockSystemStatusService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSystemStatusService.swift; sourceTree = ""; }; 20C3CC3B2E1D31B100CF7D3B /* PointOfSaleModalHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleModalHeader.swift; sourceTree = ""; }; - 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlow.swift; sourceTree = ""; }; - 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetup.swift; sourceTree = ""; }; - 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupFlowManager.swift; sourceTree = ""; }; - 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupModels.swift; sourceTree = ""; }; - 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleBarcodeScannerSetupViews.swift; sourceTree = ""; }; - 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleFlowButtonsView.swift; sourceTree = ""; }; 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListState.swift; sourceTree = ""; }; 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListBaseItem.swift; sourceTree = ""; }; 20CC1EDA2AFA8381006BD429 /* InPersonPaymentsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsMenu.swift; sourceTree = ""; }; @@ -4709,7 +4699,7 @@ 57CDABB8252E9BEB00BED88C /* ButtonTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTableFooterView.swift; sourceTree = ""; }; 57CFCD27248845B4003F51EC /* PrimarySectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimarySectionHeaderView.swift; sourceTree = ""; }; 57CFCD292488496F003F51EC /* PrimarySectionHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PrimarySectionHeaderView.xib; sourceTree = ""; }; - 57EBC91F24EEE61800C1D45B /* WooAnalyticsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooAnalyticsEvent.swift; sourceTree = ""; }; + 57EBC91F24EEE61800C1D45B /* WooAnalyticsEvent+WooApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+WooApp.swift"; sourceTree = ""; }; 57F2C6CC246DECC10074063B /* SummaryTableViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryTableViewCellViewModelTests.swift; sourceTree = ""; }; 57F42E3F253768D600EA87F7 /* TitleAndEditableValueTableViewCellViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleAndEditableValueTableViewCellViewModelTests.swift; sourceTree = ""; }; 581D5051274AA2480089B6AD /* View+AutofocusTextModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AutofocusTextModifier.swift"; sourceTree = ""; }; @@ -4799,7 +4789,6 @@ 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualRowView.swift; sourceTree = ""; }; 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardReaderType+Manual.swift"; sourceTree = ""; }; 68ED2BD52ADD2C8C00ECA88D /* LineDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineDetailView.swift; sourceTree = ""; }; - 68F151E02C0DA7910082AEC8 /* Cart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cart.swift; sourceTree = ""; }; 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCollectOrderPaymentAnalyticsTracking.swift; sourceTree = ""; }; 68F68A512D67365900BB9568 /* MockPOSCollectOrderPaymentAnalyticsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPOSCollectOrderPaymentAnalyticsTracker.swift; sourceTree = ""; }; 68F896412D5E4321000B308B /* POSCollectOrderPaymentAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCollectOrderPaymentAnalytics.swift; sourceTree = ""; }; @@ -4807,7 +4796,6 @@ 740382DA2267D94100A627F4 /* LargeImageTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageTableViewCell.xib; sourceTree = ""; }; 740987B221B87760000E4C80 /* FancyAnimatedButton+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FancyAnimatedButton+Woo.swift"; sourceTree = ""; }; 740ADFE421C33688009EE5A9 /* licenses.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = licenses.html; sourceTree = ""; }; - 74213449210A323C00C13890 /* WooAnalyticsStat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooAnalyticsStat.swift; sourceTree = ""; }; 7435E58D21C0151B00216F0F /* OrderNote+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrderNote+Woo.swift"; sourceTree = ""; }; 7435E58F21C0162C00216F0F /* OrderNoteWooTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrderNoteWooTests.swift; sourceTree = ""; }; 743E271F21AEF20100D6DC82 /* FancyAlertViewController+Upgrade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FancyAlertViewController+Upgrade.swift"; sourceTree = ""; }; @@ -5731,7 +5719,6 @@ D8FBFF1622D4CC2F006E3336 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = docs; path = ../docs; sourceTree = ""; }; DA013F502C65125100D9A391 /* PointOfSaleExitPosAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleExitPosAlertView.swift; sourceTree = ""; }; DA0DBE2E2C4FC61D00DF14C0 /* POSFloatingControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSFloatingControlView.swift; sourceTree = ""; }; - DA1D68C12C36F0980097859A /* PointOfSaleAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointOfSaleAssets.swift; sourceTree = ""; }; DA24152A2D116EA90008F69A /* WooShippingAddPackageViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooShippingAddPackageViewModelTests.swift; sourceTree = ""; }; DA25ADDC2C86145E00AE81FE /* MarkOrderAsReadUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkOrderAsReadUseCase.swift; sourceTree = ""; }; DA25ADDE2C87403900AE81FE /* PushNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationTests.swift; sourceTree = ""; }; @@ -6465,6 +6452,29 @@ path = ReceiptEmail; sourceTree = ""; }; + 017DC5C82E37917100EC1605 /* Dependencies */ = { + isa = PBXGroup; + children = ( + 017DC5C72E37917100EC1605 /* POSAnalyticsAdapter.swift */, + ); + path = Dependencies; + sourceTree = ""; + }; + 017DC5CA2E379D9300EC1605 /* Barcode Scanner Setup */ = { + isa = PBXGroup; + children = ( + 017DC5D92E37A21400EC1605 /* PointOfSaleBarcodeScannerSetupScanTester.swift */, + 017DC5D72E37A07100EC1605 /* PointOfSaleBarcodeScannerSetupFlowManager.swift */, + 017DC5D52E379FA000EC1605 /* PointOfSaleBarcodeScannerSetupTypes.swift */, + 017DC5D32E379EAA00EC1605 /* PointOfSaleBarcodeScannerSetupFlow.swift */, + 017DC5D12E379E1200EC1605 /* PointOfSaleBarcodeScannerSetup.swift */, + 017DC5CB2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupStepViews.swift */, + 017DC5CC2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupViews.swift */, + 017DC5CD2E379DF700EC1605 /* PointOfSaleFlowButtonsView.swift */, + ); + path = "Barcode Scanner Setup"; + sourceTree = ""; + }; 019130152CF49A52008C0C88 /* Tap to Pay Education */ = { isa = PBXGroup; children = ( @@ -7132,7 +7142,7 @@ 026826A12BF59DED0036F959 /* Presentation */ = { isa = PBXGroup; children = ( - 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */, + 017DC5CA2E379D9300EC1605 /* Barcode Scanner Setup */, 20D557572DF9D57800D9EC8B /* Barcode Scanning */, 016A77672D9D24A30004FCD6 /* Coupons */, 026A50262D2F6BBF002C42C2 /* Infinite Scroll */, @@ -7160,7 +7170,6 @@ 01C9C59E2DA3D97E00CD81D8 /* CartRowRemoveButton.swift */, 68D8FBD02BFEF9C700477C42 /* TotalsView.swift */, 68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */, - DA1D68C12C36F0980097859A /* PointOfSaleAssets.swift */, ); path = Presentation; sourceTree = ""; @@ -7482,6 +7491,7 @@ 029327662BF59D2D00D703E7 /* POS */ = { isa = PBXGroup; children = ( + 017DC5C82E37917100EC1605 /* Dependencies */, 20E014E12CF63671008C823B /* README.md */, 021080FD2D544B490054C78D /* Colors */, 68F151DF2C0DA7800082AEC8 /* Models */, @@ -7737,10 +7747,8 @@ 02D1D2D82CD3CD710069A93F /* Analytics */ = { isa = PBXGroup; children = ( - 0216DA6F2E2576C300016600 /* WooAnalyticsEvent+PointOfSaleIneligibleUI.swift */, 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */, 68F896412D5E4321000B308B /* POSCollectOrderPaymentAnalytics.swift */, - 02D1D2D92CD3CD8D0069A93F /* WooAnalyticsEvent+PointOfSale.swift */, 20F6A46B2DE5FCEF0066D8CB /* POSItemFetchAnalytics.swift */, ); path = Analytics; @@ -8342,21 +8350,6 @@ path = AddOrderComponentsSection; sourceTree = ""; }; - 20C3DB222E1E69CF00CF7D3B /* Barcode Scanner Setup */ = { - isa = PBXGroup; - children = ( - 20C3DB1D2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift */, - 20C3DB1E2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift */, - 20C3DB1F2E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift */, - 20C3DB202E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift */, - 20C3DB212E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift */, - 208C0F092E1FAC1900FE619E /* PointOfSaleBarcodeScannerSetupStepViews.swift */, - 207CEA842E1FD59B0023EC35 /* PointOfSaleBarcodeScannerSetupScanTester.swift */, - 20C3DB282E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift */, - ); - path = "Barcode Scanner Setup"; - sourceTree = ""; - }; 20CCBF1F2B0E159D003102E6 /* Deposits Overview */ = { isa = PBXGroup; children = ( @@ -9921,7 +9914,6 @@ 68F151DF2C0DA7800082AEC8 /* Models */ = { isa = PBXGroup; children = ( - 01B3A1F12DB6D48800286B7F /* ItemListType.swift */, 20FCBCDC2CE223340082DCA3 /* PointOfSaleAggregateModel.swift */, 209ECA802DB8FC280089F3D2 /* PointOfSaleViewStateCoordinator.swift */, 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */, @@ -9929,8 +9921,6 @@ 20F7B12E2D12CBE700C08193 /* ItemsViewState.swift */, 20C909952D3151FA0013BCCF /* ItemListBaseItem.swift */, 20D4AE002D133B43004555B2 /* ItemsStackState.swift */, - 68F151E02C0DA7910082AEC8 /* Cart.swift */, - 01E62EC72DFADF4B003A6D9E /* Cart+BarcodeScanError.swift */, 20D920E92CEF86520023B089 /* PointOfSaleErrorState.swift */, 2044158C2CE4DB480070BF54 /* PointOfSaleOrderStage.swift */, 2044158E2CE6181E0070BF54 /* PointOfSaleOrderState.swift */, @@ -10069,8 +10059,8 @@ children = ( EEC0120E2CE34B69003B865B /* WooAnalyticsEvent+WPCOMSuspendedSite.swift */, CEA16F3920FD0C8C0061B4E1 /* WooAnalytics.swift */, - 57EBC91F24EEE61800C1D45B /* WooAnalyticsEvent.swift */, - 74213449210A323C00C13890 /* WooAnalyticsStat.swift */, + 57EBC91F24EEE61800C1D45B /* WooAnalyticsEvent+WooApp.swift */, + 017DC5C52E378E3B00EC1605 /* WooAnalyticsStat+WPAnalyticsStat.swift */, 747AA08A2107CF8D0047A89B /* TracksProvider.swift */, EE57C11C297AC27300BC31E7 /* TrackEventRequestNotificationHandler.swift */, 02C3FACD282A93020095440A /* WooAnalyticsEvent+Dashboard.swift */, @@ -12075,7 +12065,6 @@ 204C9C732B6BDFFB007A94E0 /* UIUserInterfaceSizeClass+Helpers.swift */, EE8A30442B74948C001D7C66 /* OrderAttributionInfo+Origin.swift */, 26CE6F332B7D4C27008DB858 /* Error+Timeout.swift */, - 01BE93FF2DDCB1110063541C /* Error+Connectivity.swift */, B9ACF6872BEE4FC60076E6BC /* String+ProductQuantityRules.swift */, ); path = Extensions; @@ -14954,7 +14943,6 @@ 26B984D42BEECC610052658C /* Environment+Dependencies.swift in Sources */, 26F81B1A2BE433A2009EC58E /* MyStoreView.swift in Sources */, 26A52EF02C69B8F5000B1CFB /* WatchCrashLoggingStack.swift in Sources */, - 268631CF2C07D38C00521364 /* WooAnalyticsStat.swift in Sources */, 26CFDEDA2C029322005ABC31 /* AppDelegate.swift in Sources */, 264E9E942BF1D1DF009C48FD /* AppLocalizedString.swift in Sources */, 26A52EEE2C69B51D000B1CFB /* PerformanceTracking.swift in Sources */, @@ -15045,6 +15033,7 @@ 0205021E27C8B6C600FB1C6B /* InboxEligibilityUseCase.swift in Sources */, 26E7EE6E29300E8100793045 /* AnalyticsTopPerformersCard.swift in Sources */, 03E471DA29424E82001A58AD /* CardPresentModalTapToPaySuccess.swift in Sources */, + 017DC5D42E379EAA00EC1605 /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */, 26E1BECE251CD9F80096D0A1 /* RefundItemViewModel.swift in Sources */, DE7B479027A153C20018742E /* CouponSearchUICommand.swift in Sources */, 026826B52BF59E330036F959 /* CardReaderConnectionStatusView.swift in Sources */, @@ -15255,7 +15244,6 @@ DE8AA0B52BBEBE590084D2CC /* ViewControllerContainer.swift in Sources */, DE7E5E8C2B4E9353002E28D2 /* ErrorStateView.swift in Sources */, 20ADE9412C6A02B700C91265 /* POSErrorXMark.swift in Sources */, - 01B3A1F22DB6D48800286B7F /* ItemListType.swift in Sources */, DE63115B2AF1E13200587641 /* WPComLoginCoordinator.swift in Sources */, EE3B17B62AA03837004D3E0C /* CelebrationView.swift in Sources */, 0291497D2D26CB2500F7B3B3 /* ChildItemList.swift in Sources */, @@ -15294,7 +15282,6 @@ 03E471CA293E0A30001A58AD /* CardPresentModalTapToPayConfigurationProgress.swift in Sources */, 31AD0B1126E9575F000B6391 /* CardPresentModalConnectingFailed.swift in Sources */, 576EA39425264C9B00AFC0B3 /* RefundConfirmationViewModel.swift in Sources */, - 208C0F0A2E1FAC1900FE619E /* PointOfSaleBarcodeScannerSetupStepViews.swift in Sources */, 02ED3D272C23315400ED6F3E /* PointOfSaleCardPresentPaymentReaderUpdateFailedView.swift in Sources */, 01BD77482C58D19C00147191 /* PointOfSaleCardPresentPaymentCancelledOnReaderMessageView.swift in Sources */, DEC51B00276AEE91009F3DF4 /* SystemStatusReportViewModel.swift in Sources */, @@ -15469,6 +15456,7 @@ 6891C3642D364AFE00B5B48C /* CollectCashViewHelper.swift in Sources */, DE3877E2283CCBC20075D87E /* BottomSheetListSelector.swift in Sources */, DED9740B2AD7D09E00122EB4 /* BlazeCampaignListView.swift in Sources */, + 017DC5DA2E37A21400EC1605 /* PointOfSaleBarcodeScannerSetupScanTester.swift in Sources */, E1ED16E4266E10A10037B8DB /* ApplicationLogViewModel.swift in Sources */, 02F6800925807CD300C3BAD2 /* GridStackView.swift in Sources */, DE279BB126EA184A002BA963 /* ShippingLabelPackageListViewModel.swift in Sources */, @@ -15510,12 +15498,12 @@ CE22E3F72170E23C005A6BEF /* PrivacySettingsViewController.swift in Sources */, 02222BD02D5AFE4F00FB97D2 /* POSButtonProgressViewStyle.swift in Sources */, 021125482577CC650075AD2A /* ShippingLabelDetailsViewModel.swift in Sources */, - 207CEA852E1FD59B0023EC35 /* PointOfSaleBarcodeScannerSetupScanTester.swift in Sources */, 2004E2D82C08E56300D62521 /* CardPresentPaymentOnboardingPresentationEvent.swift in Sources */, 68D1BEDD2900E4180074A29E /* CustomerSearchUICommand.swift in Sources */, 316837DA25CCA90C00E36B2F /* OrderStatusListDataSource.swift in Sources */, FE28F6F4268477C1004465C7 /* RoleEligibilityUseCase.swift in Sources */, 57C5FF7A25091A350074EC26 /* OrderListSyncActionUseCase.swift in Sources */, + 017DC5D62E379FA700EC1605 /* PointOfSaleBarcodeScannerSetupTypes.swift in Sources */, 02645D7E27BA027B0065DC68 /* InboxViewModel.swift in Sources */, B58B4AB32108F01700076FDD /* DefaultNoticePresenter.swift in Sources */, 4512055224655FB6005D68DE /* TitleAndTextFieldWithImageTableViewCell.swift in Sources */, @@ -15656,7 +15644,6 @@ 028AFFB32484ED2800693C09 /* Dictionary+Logging.swift in Sources */, 026CAF802AC2B7FF002D23BB /* ConfigurableBundleProductView.swift in Sources */, 45BBFBC1274FD94300213001 /* HubMenuCoordinator.swift in Sources */, - 01E62EC82DFADF56003A6D9E /* Cart+BarcodeScanError.swift in Sources */, D8652E582630BFF500350F37 /* OrderDetailsPaymentAlerts.swift in Sources */, B5A56BF0219F2CE90065A902 /* VerticalButton.swift in Sources */, D831E2DC230E0558000037D0 /* Authentication.swift in Sources */, @@ -15733,7 +15720,6 @@ 2664210326F40FB1001FC5B4 /* View+ScrollModifiers.swift in Sources */, 02695770237281A9001BA0BF /* AztecTextViewAttachmentHandler.swift in Sources */, CCCFFC5A2934EF5E006130AF /* StatsIntervalDataParser.swift in Sources */, - 01BE94002DDCB1110063541C /* Error+Connectivity.swift in Sources */, DE5746342B4512900034B10D /* BlazeBudgetSettingView.swift in Sources */, 26771A14256FFA8700EE030E /* IssueRefundCoordinatingController.swift in Sources */, 027A2E162513356100DA6ACB /* AppleIDCredentialChecker.swift in Sources */, @@ -15848,6 +15834,9 @@ 864213022AE77C730036E5A6 /* UIImage+Resizing.swift in Sources */, 2004E2E12C08ED3200D62521 /* ViewControllerPresenting.swift in Sources */, DE61978B28991F0E005E4362 /* WKWebView+Authenticated.swift in Sources */, + 017DC5CE2E379DF700EC1605 /* PointOfSaleFlowButtonsView.swift in Sources */, + 017DC5CF2E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupStepViews.swift in Sources */, + 017DC5D02E379DF700EC1605 /* PointOfSaleBarcodeScannerSetupViews.swift in Sources */, 311237EE2714DA240033C44E /* CardPresentModalDisplayMessage.swift in Sources */, B9001CE12B1DF33300EC87B2 /* CashPaymentTenderViewModel.swift in Sources */, 0313651128AB81B100EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpView.swift in Sources */, @@ -15856,7 +15845,6 @@ CE6302432BAB431D00E3325C /* CustomerDetailView.swift in Sources */, EE45E29F2A381A2E0085F227 /* ProductDescriptionGenerationCelebrationViewModel.swift in Sources */, 02667A1A2ABDD44200C77B56 /* GiftCardCodeScannerViewController.swift in Sources */, - 20C3DB292E1E6FBA00CF7D3B /* PointOfSaleFlowButtonsView.swift in Sources */, 57A25C7C25ACFAEC00A54A62 /* OrderFulfillmentNoticePresenter.swift in Sources */, B9E4364C287587D300883CFA /* FeatureAnnouncementCardView.swift in Sources */, 205E794B2C2051B5001BA266 /* PointOfSaleCardPresentPaymentTapSwipeInsertCardMessageView.swift in Sources */, @@ -15890,7 +15878,6 @@ 45DB70662614CE3F0064A6CF /* Decimal+Helpers.swift in Sources */, 209EEF902C762ED5007969A4 /* POSModalManager.swift in Sources */, 0379C51727BFCE9800A7E284 /* WCPayCardBrand+icons.swift in Sources */, - DA1D68C22C36F0980097859A /* PointOfSaleAssets.swift in Sources */, 0210D8692A7BEEF700846F8C /* WooAnalyticsEvent+ProductListFilter.swift in Sources */, B958A7C728B3D44A00823EEF /* UniversalLinkRouter.swift in Sources */, 02E8B17C23E2C78A00A43403 /* ProductImageStatus+Extension.swift in Sources */, @@ -15929,6 +15916,7 @@ DEB387A72C3682C40025256E /* GoogleAdsCampaign+UI.swift in Sources */, B958A7CD28B3DD9100823EEF /* OrderDetailsRoute.swift in Sources */, 45DB7040261209B10064A6CF /* ItemToFulfillRow.swift in Sources */, + 017DC5C62E378E4300EC1605 /* WooAnalyticsStat+WPAnalyticsStat.swift in Sources */, 02564A8C246CE38E00D6DB2A /* SwappableSubviewContainerView.swift in Sources */, EE0EE7A628B7415200F6061E /* CustomHelpCenterContent.swift in Sources */, 023453F22579DA1A00A6BB20 /* ShippingLabelPrintingInstructionsViewController.swift in Sources */, @@ -15980,7 +15968,6 @@ CE1F512B206985DF00C6C810 /* PaddedLabel.swift in Sources */, 26E0ADF12631D94D00A5EB3B /* TopBannerWrapperView.swift in Sources */, 26C6E8EA26E8FD3900C7BB0F /* LazyView.swift in Sources */, - 7421344A210A323C00C13890 /* WooAnalyticsStat.swift in Sources */, 4567389D2745497C00743054 /* OrderStatusFilterViewController.swift in Sources */, 268D7C9C2984752A00D38709 /* SupportForm.swift in Sources */, 02A275BA23FE50AA005C560F /* ProductUIImageLoader.swift in Sources */, @@ -15989,6 +15976,7 @@ B5D6DC54214802740003E48A /* SyncCoordinator.swift in Sources */, 205B7EBD2C19FB6600D14A36 /* PointOfSaleCardPresentPaymentFoundReaderAlertViewModel.swift in Sources */, 018D5C7E2CA6B4A60085EBEE /* CurrencySettings+Sanitized.swift in Sources */, + 017DC5D82E37A07100EC1605 /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */, B57C5C9421B80E4700FF82B2 /* Data+Woo.swift in Sources */, 453A907925EFB6D6006EE892 /* ButtonActivityIndicator.swift in Sources */, 4546E09A271F0942003836F3 /* FilteredOrdersHeaderBar.swift in Sources */, @@ -16001,11 +15989,6 @@ EECB6D1E2AFBFE0000040BC9 /* WooSubscriptionProductsEligibilityChecker.swift in Sources */, CEA455C72BB5CA5E00D932CF /* AnalyticsSessionsUnavailableCard.swift in Sources */, EE45E29D2A381A250085F227 /* ProductDescriptionGenerationCelebrationView.swift in Sources */, - 20C3DB232E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupViews.swift in Sources */, - 20C3DB242E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlowManager.swift in Sources */, - 20C3DB252E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetup.swift in Sources */, - 20C3DB262E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupModels.swift in Sources */, - 20C3DB272E1E69CF00CF7D3B /* PointOfSaleBarcodeScannerSetupFlow.swift in Sources */, 0260B1B12805321B00FCFE8C /* OrderDetailsPaymentAlertsProtocol.swift in Sources */, 4556ED38270645A6005CBC0D /* ShippingLabelCarrierSectionHeader.swift in Sources */, 267C01CF29E89E1700FCC97B /* StorePlanSynchronizer.swift in Sources */, @@ -16025,7 +16008,6 @@ EEC259422B43EF33004D703C /* BlazeEditAdView.swift in Sources */, 03E471C0293A158D001A58AD /* CardReaderConnectionAlertsProviding.swift in Sources */, 0182C8C22CE4F0DB00474355 /* ReceiptEmailView.swift in Sources */, - 02D1D2DA2CD3CDA40069A93F /* WooAnalyticsEvent+PointOfSale.swift in Sources */, D8C2A291231BD0FD00F503E9 /* ReviewsDataSource.swift in Sources */, CE855366209BA6A700938BDC /* CustomerInfoTableViewCell.swift in Sources */, 205E79442C204368001BA266 /* PointOfSaleCardPresentPaymentNonRetryableErrorMessageViewModel.swift in Sources */, @@ -16148,7 +16130,6 @@ 57C5FF7F250925C90074EC26 /* OrderListViewModel.swift in Sources */, 029D02602C231F5F00CB1E75 /* PointOfSaleCardPresentPaymentReaderUpdateCompletionView.swift in Sources */, 26E0AE13263359F900A5EB3B /* View+Conditionals.swift in Sources */, - 68F151E12C0DA7910082AEC8 /* Cart.swift in Sources */, CE583A072107849F00D73C1C /* SwitchTableViewCell.swift in Sources */, EE7E75A82D83EB1F00E6FF5B /* WooShippingSplitShipmentsRow.swift in Sources */, D8149F562251EE300006A245 /* UITextField+Helpers.swift in Sources */, @@ -16433,7 +16414,7 @@ 022266BA2AE76E0E00614F34 /* ProductBundleItem+SwiftUIPreviewHelpers.swift in Sources */, 262A09A5262F65690033AD20 /* OrderAddOnTopBanner.swift in Sources */, 4515262E2577D56C0076B03C /* AddAttributeViewController.swift in Sources */, - 57EBC92024EEE61800C1D45B /* WooAnalyticsEvent.swift in Sources */, + 57EBC92024EEE61800C1D45B /* WooAnalyticsEvent+WooApp.swift in Sources */, 57CDABB9252E9BEB00BED88C /* ButtonTableFooterView.swift in Sources */, B946881829B8DDC2000646B0 /* ProductsViewController+Activity.swift in Sources */, B63D9009293E56E300BB5C9D /* AnalyticsHubQuarterToDateRangeData.swift in Sources */, @@ -16667,7 +16648,6 @@ B59D1EE5219080B4009D1978 /* Note+Woo.swift in Sources */, 02913E9523A774C500707A0C /* UnitInputFormatter.swift in Sources */, 0204E3622B8CD40B00F1B5FD /* WooAnalyticsEvent+Products.swift in Sources */, - 0216DA702E2576CB00016600 /* WooAnalyticsEvent+PointOfSaleIneligibleUI.swift in Sources */, 4508BF622660E34A00707869 /* ShippingLabelCarrierAndRatesTopBanner.swift in Sources */, DE7E5E7F2B4BC52C002E28D2 /* MultiSelectionList.swift in Sources */, DA0DBE2F2C4FC61D00DF14C0 /* POSFloatingControlView.swift in Sources */, @@ -16695,6 +16675,7 @@ 0313651728ACE9F400EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpViewModel.swift in Sources */, 02E4E7442E0EEF80003A31E7 /* POSIneligibleView.swift in Sources */, 571CDD5A250ACC470076B8CC /* UITableViewDiffableDataSource+Helpers.swift in Sources */, + 017DC5C92E37917100EC1605 /* POSAnalyticsAdapter.swift in Sources */, DE7E5E7D2B4BB617002E28D2 /* BlazeTargetLanguagePickerView.swift in Sources */, 029149782D26658A00F7B3B3 /* VariationCardView.swift in Sources */, EE09DE082C2C0CA100A32680 /* ProductCreationAIStartingInfoViewModel.swift in Sources */, @@ -16768,6 +16749,7 @@ CE1CCB4B20570B1F000EE3AC /* OrderTableViewCell.swift in Sources */, 269098B827D68CCD001FEB07 /* FeesInputTransformer.swift in Sources */, B6C78B8C293BADAA008934A1 /* AnalyticsHubLastWeekRangeData.swift in Sources */, + 017DC5D22E379E1200EC1605 /* PointOfSaleBarcodeScannerSetup.swift in Sources */, 748AD087219F481B00023535 /* UIView+Animation.swift in Sources */, 20FCBCDD2CE223340082DCA3 /* PointOfSaleAggregateModel.swift in Sources */, CE4AFE462CD135B20013C52B /* WooShippingPostPurchaseViewModel.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/POS/Analytics/POSCollectOrderPaymentAnalyticsTests.swift b/WooCommerce/WooCommerceTests/POS/Analytics/POSCollectOrderPaymentAnalyticsTests.swift index 96a0fdebe8a..616d33bc9ea 100644 --- a/WooCommerce/WooCommerceTests/POS/Analytics/POSCollectOrderPaymentAnalyticsTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Analytics/POSCollectOrderPaymentAnalyticsTests.swift @@ -5,12 +5,10 @@ import enum WooFoundation.CountryCode import Testing struct POSCollectOrderPaymentAnalyticsTests { - private let analytics: Analytics - private let analyticsProvider: MockAnalyticsProvider + private let analytics: MockPOSAnalytics init() { - analyticsProvider = MockAnalyticsProvider() - analytics = WooAnalytics(analyticsProvider: analyticsProvider) + analytics = MockPOSAnalytics() } @Test func analytics_when_successful_payment_then_tracks_event_and_properties() { @@ -36,9 +34,9 @@ struct POSCollectOrderPaymentAnalyticsTests { sut.trackSuccessfulCardPayment(capturedPaymentData: capturedPaymentData) // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == expectedEvent }) != nil) + #expect(analytics.events.first(where: { $0.eventName == expectedEvent }) != nil) #expect(expectedProperties.allSatisfy { key in - analyticsProvider.receivedProperties.contains(where: { $0.keys.contains(key) }) + analytics.events.map(\.properties).contains(where: { $0.keys.contains(key) }) }) } } diff --git a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleCouponsControllerTests.swift b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleCouponsControllerTests.swift index 0c5db89701c..7d19e7bbf5c 100644 --- a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleCouponsControllerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleCouponsControllerTests.swift @@ -3,6 +3,7 @@ import Testing import Foundation import struct Yosemite.PointOfSaleCouponFetchStrategyFactory import class WooFoundation.CurrencySettings +import PointOfSale struct PointOfSaleCouponsControllerTests { private let fetchStrategyFactory: PointOfSaleCouponFetchStrategyFactory @@ -19,7 +20,9 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() couponProvider.shouldReturnZeroItems = true - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .empty, itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -36,7 +39,9 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -53,7 +58,7 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() couponProvider.shouldReturnZeroItems = true - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory, analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .empty, itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -71,7 +76,9 @@ struct PointOfSaleCouponsControllerTests { let couponProvider = MockPointOfSaleCouponService() let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -88,7 +95,9 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() couponProvider.shouldReturnZeroItems = true - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .empty, itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -105,7 +114,9 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -123,7 +134,9 @@ struct PointOfSaleCouponsControllerTests { let couponProvider = MockPointOfSaleCouponService() let error = NSError(domain: "test", code: 0) couponProvider.errorToThrow = .couponsLoadingError(underlyingError: error) - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .error(.errorOnLoadingCoupons()), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -141,7 +154,9 @@ struct PointOfSaleCouponsControllerTests { struct MockError: Error {} let couponProvider = MockPointOfSaleCouponService() couponProvider.errorToThrow = .couponsEnablingError(underlyingError: MockError()) - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) // When await sut.enableCoupons() @@ -157,7 +172,9 @@ struct PointOfSaleCouponsControllerTests { @Test func enableCoupons_loads_items_when_successful() async throws { // Given let couponProvider = MockPointOfSaleCouponService() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons() let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -173,7 +190,9 @@ struct PointOfSaleCouponsControllerTests { @Test func loadItems_when_fails_then_sets_inlineError_state_and_preserves_items() async throws { // Given let couponProvider = MockPointOfSaleCouponService() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) await sut.loadItems(base: .root) let currentItems = sut.itemsViewState.itemsStack.root.items let error = NSError(domain: "test", code: 0) @@ -192,7 +211,9 @@ struct PointOfSaleCouponsControllerTests { @Test func loadNextItems_when_loadNextItems_fails_then_sets_inlineError_state_and_preserves_items() async throws { // Given let couponProvider = MockPointOfSaleCouponService() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) couponProvider.shouldSimulateTwoPages = true await sut.loadItems(base: .root) let currentItems = sut.itemsViewState.itemsStack.root.items @@ -215,7 +236,9 @@ struct PointOfSaleCouponsControllerTests { @Test func loadNextItems_when_loadNextItems_then_loads_second_page() async throws { // Given let couponProvider = MockPointOfSaleCouponService() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) couponProvider.shouldSimulateTwoPages = true await sut.loadItems(base: .root) @@ -233,7 +256,9 @@ struct PointOfSaleCouponsControllerTests { @Test func loadNextItems_when_loadNextItems_with_more_items_then_sets_hasMoreItems() async throws { // Given let couponProvider = MockPointOfSaleCouponService() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) couponProvider.shouldSimulateTwoPages = true couponProvider.shouldSimulateMorePages = true await sut.loadItems(base: .root) @@ -253,7 +278,9 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() couponProvider.shouldReturnZeroItems = true - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .empty, itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -270,7 +297,9 @@ struct PointOfSaleCouponsControllerTests { // Given let couponProvider = MockPointOfSaleCouponService() let expectedCoupons = MockPointOfSaleCouponService.makeInitialCoupons() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .loaded(expectedCoupons, hasMoreItems: false), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -288,7 +317,9 @@ struct PointOfSaleCouponsControllerTests { let couponProvider = MockPointOfSaleCouponService() let error = NSError(domain: "test", code: 0) couponProvider.errorToThrow = .couponsLoadingError(underlyingError: error) - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) let expectedItemStackState = ItemsStackState(root: .error(.errorOnLoadingCoupons()), itemStates: [:]) let expectedViewState = ItemsViewState(containerState: .content, itemsStack: expectedItemStackState) @@ -304,7 +335,9 @@ struct PointOfSaleCouponsControllerTests { @Test func searchItems_when_requestCancelled_then_state_unchanged() async throws { // Given let couponProvider = MockPointOfSaleCouponService() - let sut = PointOfSaleCouponsController(itemProvider: couponProvider, fetchStrategyFactory: fetchStrategyFactory) + let sut = PointOfSaleCouponsController(itemProvider: couponProvider, + fetchStrategyFactory: fetchStrategyFactory, + analyticsProvider: MockPOSAnalytics()) await sut.loadItems(base: .root) let initialState = sut.itemsViewState couponProvider.errorToThrow = .requestCancelled diff --git a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleItemsControllerTests.swift b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleItemsControllerTests.swift index fab0b964868..ac5b283aedf 100644 --- a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleItemsControllerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleItemsControllerTests.swift @@ -7,6 +7,7 @@ import enum Yosemite.PointOfSaleItemServiceError import class Yosemite.PointOfSaleItemFetchStrategyFactory @testable import struct Yosemite.PointOfSaleSearchPurchasableItemFetchStrategy import Observation +import PointOfSale final class PointOfSaleItemsControllerTests { @available(iOS 17.0, *) @@ -15,7 +16,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) try #require(sut.itemsViewState.containerState == .loading) @@ -38,7 +40,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let expectedItems = MockPointOfSaleItemService.makeInitialItems() @@ -60,7 +63,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let expectedItems = MockPointOfSaleItemService.makeInitialItems() @@ -82,7 +86,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) try #require(sut.itemsViewState.containerState == .loading) @@ -108,7 +113,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) // When/Then @@ -121,7 +127,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) itemProvider.shouldReturnZeroItems = true @@ -142,7 +149,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let initialItems = MockPointOfSaleItemService.makeInitialItems() @@ -165,7 +173,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) itemProvider.shouldSimulateTwoPages = true @@ -188,7 +197,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) try #require(sut.itemsViewState.containerState == .loading) @@ -208,7 +218,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) itemProvider.shouldSimulateTwoPages = true @@ -233,7 +244,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), @@ -265,7 +277,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), @@ -299,7 +312,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) itemProvider.shouldReturnZeroItems = true @@ -320,7 +334,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) itemProvider.errorToThrow = MockError.requestFailed @@ -340,7 +355,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) try #require(sut.itemsViewState.containerState == .loading) @@ -371,7 +387,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) itemProvider.shouldSimulateTwoPages = true @@ -395,7 +412,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) try #require(sut.itemsViewState.containerState == .loading) @@ -417,7 +435,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let expectedItems = MockPointOfSaleItemService.makeInitialItems() @@ -442,7 +461,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) await sut.loadItems(base: .root) @@ -462,7 +482,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let initialItems = MockPointOfSaleItemService.makeInitialItems() @@ -493,7 +514,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), @@ -519,7 +541,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let parentItem = POSItem.variableParentProduct(POSVariableParentProduct(id: UUID(), @@ -558,7 +581,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) // When @@ -575,7 +599,8 @@ final class PointOfSaleItemsControllerTests { let itemProvider = MockPointOfSaleItemService() let sut = PointOfSaleItemsController( itemProvider: itemProvider, - itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil) + itemFetchStrategyFactory: PointOfSaleItemFetchStrategyFactory(siteID: 1, credentials: nil), + analyticsProvider: MockPOSAnalytics() ) let initialItems = MockPointOfSaleItemService.makeInitialItems() diff --git a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleOrderControllerTests.swift b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleOrderControllerTests.swift index b3a0baae6ea..8c449ea9624 100644 --- a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleOrderControllerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleOrderControllerTests.swift @@ -13,6 +13,7 @@ import class WooFoundation.CurrencySettings import protocol WooFoundation.Analytics import enum Networking.DotcomError import enum Networking.NetworkError +import PointOfSale struct PointOfSaleOrderControllerTests { let mockOrderService = MockPOSOrderService() @@ -22,7 +23,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_without_items_doesnt_call_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) // When await sut.syncOrder(for: .init(), retryHandler: {}) @@ -35,7 +37,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_cart_matching_order_doesnt_call_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let orderItem = OrderItem.fake().copy(quantity: 1) let fakeOrder = Order.fake().copy(items: [orderItem]) let cartItem = makeItem(orderItemsToMatch: [orderItem]) @@ -55,7 +58,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_already_syncing_doesnt_call_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) mockOrderService.simulateSyncing = true Task { await sut.syncOrder(for: .init(purchasableItems: [makeItem(quantity: 1)]), retryHandler: {}) @@ -82,7 +86,8 @@ struct PointOfSaleOrderControllerTests { numberOfDecimals: 2) let sut = PointOfSaleOrderController(orderService: mockOrderService, receiptService: mockReceiptService, - currencySettings: currencySettings) + currencySettings: currencySettings, + analytics: MockPOSAnalytics()) // When await sut.syncOrder(for: .init(purchasableItems: [makeItem()]), retryHandler: {}) @@ -95,7 +100,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_changes_from_previous_order_calls_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let cartItem = makeItem(quantity: 1) let orderItem = OrderItem.fake().copy(quantity: 1) let fakeOrder = Order.fake().copy(items: [orderItem]) @@ -116,7 +122,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_no_previous_order_sets_orderState_syncing_then_loaded() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let fakeOrder = Order.fake() mockOrderService.orderToReturn = fakeOrder var orderStates: [PointOfSaleInternalOrderState] = [sut.orderState] @@ -154,7 +161,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_order_sync_failure_sets_orderState_syncing_then_error() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) mockOrderService.orderToReturn = nil var orderStates: [PointOfSaleInternalOrderState] = [sut.orderState] @@ -191,7 +199,8 @@ struct PointOfSaleOrderControllerTests { @Test func sendReceipt_when_there_is_no_order_then_will_not_trigger() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let email = "test@example.com" // When @@ -209,7 +218,8 @@ struct PointOfSaleOrderControllerTests { @Test func sendReceipt_calls_both_updateOrder_and_sendReceipt() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let order = Order.fake() let recipientEmail = "test@fake.com" mockOrderService.orderToReturn = order @@ -230,7 +240,8 @@ struct PointOfSaleOrderControllerTests { do { // Given/When let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) try await sut.collectCashPayment(changeDueAmount: nil) } catch let error as PointOfSaleOrderController.PointOfSaleOrderControllerError { // Then @@ -245,6 +256,7 @@ struct PointOfSaleOrderControllerTests { let mockPaymentCelebration = MockPaymentCaptureCelebration() let sut = PointOfSaleOrderController(orderService: mockOrderService, receiptService: mockReceiptService, + analytics: MockPOSAnalytics(), celebration: mockPaymentCelebration) let orderItem = OrderItem.fake() @@ -265,7 +277,8 @@ struct PointOfSaleOrderControllerTests { @Test func collectCashPayment_passes_changeDueAmount_to_order_service() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let orderItem = OrderItem.fake() let fakeOrder = Order.fake().copy(items: [orderItem]) @@ -285,7 +298,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_successful_returns_newOrder_result() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let fakeOrderItem = OrderItem.fake().copy(quantity: 1) let fakeOrder = Order.fake() let fakeCartItem = makeItem(orderItemsToMatch: [fakeOrderItem]) @@ -306,7 +320,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_updating_existing_order_returns_newOrder_result() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let fakeOrder = Order.fake() mockOrderService.orderToReturn = fakeOrder @@ -329,7 +344,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_cart_matching_order_then_returns_orderNotChanged_result() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let orderItem = OrderItem.fake().copy(quantity: 1) let fakeOrder = Order.fake().copy(items: [orderItem]) let cartItem = makeItem(orderItemsToMatch: [orderItem]) @@ -353,7 +369,8 @@ struct PointOfSaleOrderControllerTests { @available(iOS 17.0, *) @Test func syncOrder_when_orderService_fails_then_returns_syncOrderState_failure() async throws { let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let cartItem = makeItem(quantity: 1) // When @@ -372,7 +389,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_cart_matching_order_and_coupons_doesnt_call_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let orderItem = OrderItem.fake().copy(quantity: 1) let couponCode = "SAVE10" let coupon = OrderCouponLine.fake().copy(code: couponCode) @@ -396,7 +414,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_matching_items_but_different_coupons_calls_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let orderItem = OrderItem.fake().copy(quantity: 1) let initialCouponCode = "SAVE10" let initialCoupon = OrderCouponLine.fake().copy(code: initialCouponCode) @@ -420,7 +439,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_with_matching_items_but_removed_coupon_calls_orderService() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let orderItem = OrderItem.fake().copy(quantity: 1) let couponCode = "SAVE10" let coupon = OrderCouponLine.fake().copy(code: couponCode) @@ -444,7 +464,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_orderService_fails_with_couponsError_then_sets_invalidCoupon_error() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let errorMessage = "Invalid coupon code" mockOrderService.errorToReturn = DotcomError.unknown(code: "woocommerce_rest_invalid_coupon", message: errorMessage) @@ -484,7 +505,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_orderService_fails_with_networkError_containing_couponsError_then_sets_invalidCoupon_error() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) let errorMessage = "Coupon INVALID does not exist" let errorJSON = """ { @@ -531,7 +553,8 @@ struct PointOfSaleOrderControllerTests { @Test func syncOrder_when_fails_sets_order_to_nil() async throws { // Given let sut = PointOfSaleOrderController(orderService: mockOrderService, - receiptService: mockReceiptService) + receiptService: mockReceiptService, + analytics: MockPOSAnalytics()) // First create a successful order let orderItem = OrderItem.fake().copy(quantity: 1) @@ -574,15 +597,10 @@ struct PointOfSaleOrderControllerTests { @MainActor struct AnalyticsTests { - private let analytics: WooAnalytics - private let analyticsProvider = MockAnalyticsProvider() + private let analytics = MockPOSAnalytics() private let orderService = MockPOSOrderService() private let receiptService = MockReceiptService() - init() { - analytics = WooAnalytics(analyticsProvider: analyticsProvider) - } - @available(iOS 17.0, *) @Test func syncOrder_when_create_order_then_tracks_order_creation_success_event() async throws { // Given @@ -598,7 +616,7 @@ struct PointOfSaleOrderControllerTests { await sut.syncOrder(for: .init(purchasableItems: [fakeCartItem]), retryHandler: { }) // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "order_creation_success" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "order_creation_success" }) != nil) } @available(iOS 17.0, *) @@ -613,15 +631,14 @@ struct PointOfSaleOrderControllerTests { await sut.syncOrder(for: .init(purchasableItems: [makeItem()]), retryHandler: {}) // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "order_creation_failed" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "order_creation_failed" }) != nil) } @MainActor @available(iOS 17.0, *) @Test func collectCashPayment_when_failure_tracks_correct_event() async throws { // Given - let mockAnalyticsProvider = MockAnalyticsProvider() - let mockAnalytics = WooAnalytics(analyticsProvider: mockAnalyticsProvider) + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleOrderController(orderService: orderService, receiptService: MockReceiptService(), @@ -643,7 +660,7 @@ struct PointOfSaleOrderControllerTests { }) // Then - #expect(mockAnalyticsProvider.receivedEvents.first(where: { $0 == "cash_payment_failed" }) != nil) + #expect(mockAnalytics.events.first(where: { $0.eventName == "cash_payment_failed" }) != nil) } @available(iOS 17.0, *) @@ -669,8 +686,8 @@ struct PointOfSaleOrderControllerTests { try await sut.sendReceipt(recipientEmail: "test@example.com") // Then - let indexOfEvent = try #require(analyticsProvider.receivedEvents.firstIndex(where: { $0 == "receipt_email_success" })) - #expect(analyticsProvider.receivedProperties[indexOfEvent]["eligible_for_pos_receipt"] as? Bool == true) + let indexOfEvent = try #require(analytics.events.firstIndex(where: { $0.eventName == "receipt_email_success" })) + #expect(analytics.events.map(\.properties)[indexOfEvent]["eligible_for_pos_receipt"] as? Bool == true) } @available(iOS 17.0, *) @@ -685,8 +702,8 @@ struct PointOfSaleOrderControllerTests { try await sut.sendReceipt(recipientEmail: "test@example.com") } catch { // Then - let indexOfEvent = try #require(analyticsProvider.receivedEvents.firstIndex(where: { $0 == "receipt_email_failed" })) - #expect(analyticsProvider.receivedProperties[indexOfEvent]["eligible_for_pos_receipt"] == nil) + let indexOfEvent = try #require(analytics.events.firstIndex(where: { $0.eventName == "receipt_email_failed" })) + #expect(analytics.events.map(\.properties)[indexOfEvent]["eligible_for_pos_receipt"] == nil) } } @@ -717,9 +734,9 @@ struct PointOfSaleOrderControllerTests { try await sut.sendReceipt(recipientEmail: "test@example.com") } catch { // Then - let indexOfEvent = try #require(analyticsProvider.receivedEvents.firstIndex(where: { $0 == "receipt_email_failed" })) - #expect(analyticsProvider.receivedProperties[indexOfEvent]["eligible_for_pos_receipt"] as? Bool == true) - #expect(analyticsProvider.receivedProperties[indexOfEvent]["error_description"] as? String != nil) + let indexOfEvent = try #require(analytics.events.firstIndex(where: { $0.eventName == "receipt_email_failed" })) + #expect(analytics.events.map(\.properties)[indexOfEvent]["eligible_for_pos_receipt"] as? Bool == true) + #expect(analytics.events.map(\.error)[indexOfEvent] != nil) } } } @@ -743,7 +760,7 @@ struct PointOfSaleOrderControllerTests { let sut = PointOfSaleOrderController(orderService: mockOrderService, receiptService: mockReceiptService, - analytics: ServiceLocator.analytics, + analytics: MockPOSAnalytics(), featureFlagService: mockFeatureFlagService, pluginsService: mockPluginsService) mockOrderService.orderToReturn = Order.fake() @@ -778,7 +795,7 @@ struct PointOfSaleOrderControllerTests { let sut = PointOfSaleOrderController(orderService: mockOrderService, receiptService: mockReceiptService, - analytics: ServiceLocator.analytics, + analytics: MockPOSAnalytics(), featureFlagService: mockFeatureFlagService, pluginsService: mockPluginsService) mockOrderService.orderToReturn = Order.fake() @@ -809,7 +826,7 @@ struct PointOfSaleOrderControllerTests { let sut = PointOfSaleOrderController(orderService: mockOrderService, receiptService: mockReceiptService, - analytics: ServiceLocator.analytics, + analytics: MockPOSAnalytics(), featureFlagService: mockFeatureFlagService, pluginsService: mockPluginsService) mockOrderService.orderToReturn = Order.fake() @@ -841,7 +858,7 @@ struct PointOfSaleOrderControllerTests { let sut = PointOfSaleOrderController(orderService: mockOrderService, receiptService: mockReceiptService, - analytics: ServiceLocator.analytics, + analytics: MockPOSAnalytics(), featureFlagService: mockFeatureFlagService, pluginsService: mockPluginsService) mockOrderService.orderToReturn = Order.fake() diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockAnalytics.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockAnalytics.swift index d796df42a6f..c605b96c6c3 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockAnalytics.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockAnalytics.swift @@ -2,21 +2,28 @@ import Foundation import WooFoundation @testable import WooCommerce -final class MockAnalytics: Analytics { +final class MockPOSAnalytics: POSAnalyticsProviding { struct TrackedEvent { let eventName: String let properties: [AnyHashable: Any] let error: Error? } - func initialize() {} - func refreshUserData() {} - func setUserHasOptedOut(_ optedOut: Bool) {} - var userHasOptedIn: Bool = true - var analyticsProvider: AnalyticsProvider { fatalError("Not implemented") } var events: [TrackedEvent] = [] - func track(_ eventName: String, properties: [AnyHashable: Any]?, error: Error?) { - events.append(TrackedEvent(eventName: eventName, properties: properties ?? [:], error: error)) + func track(event: WooAnalyticsEvent) { + events.append(TrackedEvent(eventName: event.statName.rawValue, properties: event.properties, error: event.error)) + } + + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType] = [:], error: Error) { + events.append(TrackedEvent(eventName: stat.rawValue, properties: parameters, error: error)) + } + + func track(_ stat: WooAnalyticsStat, parameters: [String: WooAnalyticsEventPropertyType] = [:]) { + events.append(TrackedEvent(eventName: stat.rawValue, properties: parameters, error: nil)) + } + + func track(_ stat: WooAnalyticsStat) { + events.append(TrackedEvent(eventName: stat.rawValue, properties: [:], error: nil)) } } diff --git a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift index fc3dffbcda1..a7c384bdf24 100644 --- a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift @@ -68,12 +68,10 @@ struct PointOfSaleAggregateModelTests { } struct CartTests { - private let analytics: WooAnalytics! - private let analyticsProvider: MockAnalyticsProvider! + private let analytics: MockPOSAnalytics init() { - analyticsProvider = MockAnalyticsProvider() - analytics = WooAnalytics(analyticsProvider: analyticsProvider) + analytics = MockPOSAnalytics() } @available(iOS 17.0, *) @@ -822,13 +820,12 @@ struct PointOfSaleAggregateModelTests { } struct AnalyticsTests { - private let analyticsProvider = MockAnalyticsProvider() - private let analytics: WooAnalytics + private let analytics: MockPOSAnalytics private let cardPresentPaymentService = MockCardPresentPaymentService() private let orderController = MockPointOfSaleOrderController() init() { - analytics = WooAnalytics(analyticsProvider: analyticsProvider) + analytics = MockPOSAnalytics() orderController.orderState = makeLoadedOrderState() } @@ -854,8 +851,8 @@ struct PointOfSaleAggregateModelTests { sut.cancelCardPaymentsOnboarding() // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "payments_onboarding_dismissed" }) != nil) - let eventProperties = try #require(analyticsProvider.receivedProperties.first(where: { $0.keys.contains("onboarding_state") + #expect(analytics.events.first(where: { $0.eventName == "payments_onboarding_dismissed" }) != nil) + let eventProperties = try #require(analytics.events.map(\.properties).first(where: { $0.keys.contains("onboarding_state") })) #expect(eventProperties["onboarding_state"] as? String == "no_connection_error") } @@ -876,7 +873,7 @@ struct PointOfSaleAggregateModelTests { sut.trackCardPaymentsOnboardingShown() // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "payments_onboarding_shown" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "payments_onboarding_shown" }) != nil) } @available(iOS 17.0, *) @@ -893,7 +890,7 @@ struct PointOfSaleAggregateModelTests { sut.connectCardReader() // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "card_reader_connection_tapped" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "card_reader_connection_tapped" }) != nil) } @available(iOS 17.0, *) @@ -910,7 +907,7 @@ struct PointOfSaleAggregateModelTests { sut.disconnectCardReader() // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "card_reader_disconnect_tapped" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "card_reader_disconnect_tapped" }) != nil) } @available(iOS 17.0, *) @@ -940,21 +937,19 @@ struct PointOfSaleAggregateModelTests { await sut.cancelCashPayment() // Then - #expect(analyticsProvider.receivedEvents.first(where: { $0 == "back_to_checkout_from_cash" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "back_to_checkout_from_cash" }) != nil) } @available(iOS 17.0, *) @Test func startCashPayment_when_invoked_tracks_expected_event() async throws { // Given - let mockAnalyticsProvider = MockAnalyticsProvider() - let mockAnalytics = WooAnalytics(analyticsProvider: mockAnalyticsProvider) - let sut = makePointOfSaleAggregateModel(analytics: mockAnalytics) + let sut = makePointOfSaleAggregateModel(analytics: analytics) // When await sut.startCashPayment() // Then - #expect(mockAnalyticsProvider.receivedEvents.first(where: { $0 == "cash_payment_tapped" }) != nil) + #expect(analytics.events.first(where: { $0.eventName == "cash_payment_tapped" }) != nil) } } @@ -1017,7 +1012,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: PointOfSaleSearchingItemsControllerProtocol = MockPointOfSaleCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = MockCardPresentPaymentService(), orderController: PointOfSaleOrderControllerProtocol = MockPointOfSaleOrderController(), - analytics: Analytics = WooAnalytics(analyticsProvider: MockAnalyticsProvider()), + analytics: POSAnalyticsProviding = MockPOSAnalytics(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = MockPOSCollectOrderPaymentAnalyticsTracker(), searchHistoryService: POSSearchHistoryProviding = MockPOSSearchHistoryService(), popularPurchasableItemsController: PointOfSaleItemsControllerProtocol = MockPointOfSaleItemsController(), diff --git a/WooCommerce/WooCommerceTests/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManagerTests.swift b/WooCommerce/WooCommerceTests/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManagerTests.swift index 906ca8138af..81bd8fd84e6 100644 --- a/WooCommerce/WooCommerceTests/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManagerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Presentation/Barcode Scanner Setup/PointOfSaleBarcodeScannerSetupFlowManagerTests.swift @@ -2,13 +2,14 @@ import Testing import Foundation import GameController @testable import WooCommerce +import PointOfSale struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_tracks_scanner_selected_when_selectScanner_called() { // Given a flow manager - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics @@ -26,7 +27,7 @@ struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_tracks_dismissal_when_onDisappear_called_on_non_completion_step() { // Given a flow manager with a setup flow in progress - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics @@ -49,7 +50,7 @@ struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_tracks_dismissal_when_onDisappear_called_on_scanner_selection() { // Given a flow manager on scanner selection screen - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics @@ -68,7 +69,7 @@ struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_tracks_keyboard_connected_when_in_setup_flow() { // Given a flow manager with a setup flow - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics @@ -90,7 +91,7 @@ struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_does_not_track_keyboard_connected_when_on_scanner_selection() { // Given a flow manager on scanner selection - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics @@ -106,7 +107,7 @@ struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_returns_correct_state_after_scanner_selection() { // Given a flow manager - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics @@ -132,7 +133,7 @@ struct PointOfSaleBarcodeScannerSetupFlowManagerTests { @available(iOS 17.0, *) @Test func test_flowManager_returns_to_scanner_selection_when_goBackToSelection_called() { // Given a flow manager in setup flow - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let sut = PointOfSaleBarcodeScannerSetupFlowManager( isPresented: .constant(true), analytics: mockAnalytics diff --git a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerFactoryTests.swift b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerFactoryTests.swift index 4b6b23ad983..f81c8e9a76b 100644 --- a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerFactoryTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerFactoryTests.swift @@ -16,7 +16,7 @@ struct POSItemActionHandlerFactoryTests { @Test func products_list_tracks_correct_analytics() async throws { // Given let posModel = MockPointOfSaleAggregateModel() - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let handler = POSItemActionHandlerFactory.itemActionHandler( itemListType: .products(search: false), searchTerm: "", @@ -41,7 +41,7 @@ struct POSItemActionHandlerFactoryTests { @Test func coupons_list_tracks_correct_analytics() async throws { // Given let posModel = MockPointOfSaleAggregateModel() - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let handler = POSItemActionHandlerFactory.itemActionHandler( itemListType: .coupons(search: false), searchTerm: "", @@ -66,7 +66,7 @@ struct POSItemActionHandlerFactoryTests { @Test func products_search_tracks_correct_analytics() async throws { // Given let posModel = MockPointOfSaleAggregateModel() - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let handler = POSItemActionHandlerFactory.itemActionHandler( itemListType: .products(search: true), searchTerm: "shoes", @@ -91,7 +91,7 @@ struct POSItemActionHandlerFactoryTests { @Test func coupons_search_tracks_correct_analytics() async throws { // Given let posModel = MockPointOfSaleAggregateModel() - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let handler = POSItemActionHandlerFactory.itemActionHandler( itemListType: .coupons(search: true), searchTerm: "discount", @@ -116,7 +116,7 @@ struct POSItemActionHandlerFactoryTests { @Test func variation_list_tracks_correct_analytics() async throws { // Given let posModel = MockPointOfSaleAggregateModel() - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let handler = POSItemActionHandlerFactory.variationActionHandler( itemListType: .products(search: false), searchTerm: "", @@ -141,7 +141,7 @@ struct POSItemActionHandlerFactoryTests { @Test func variation_search_tracks_correct_analytics() async throws { // Given let posModel = MockPointOfSaleAggregateModel() - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let handler = POSItemActionHandlerFactory.variationActionHandler( itemListType: .products(search: true), searchTerm: "blue shirt", diff --git a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift index 2fece6e897b..c48b774aec4 100644 --- a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift @@ -13,7 +13,8 @@ struct POSItemActionHandlerTests { let sut = StandardPOSItemActionHandler( posModel: aggregateModel, sourceView: .coupon, - sourceViewType: .list + sourceViewType: .list, + analytics: MockPOSAnalytics() ) let coupon = makeCouponItem(code: "DISCOUNT!") @@ -32,7 +33,8 @@ struct POSItemActionHandlerTests { posModel: aggregateModel, searchTerm: "", itemType: .coupon, - sourceView: .coupon + sourceView: .coupon, + analytics: MockPOSAnalytics() ) let coupon = makeCouponItem(code: "DISCOUNT!") @@ -50,7 +52,8 @@ struct POSItemActionHandlerTests { let sut = StandardPOSItemActionHandler( posModel: aggregateModel, sourceView: .product, - sourceViewType: .list + sourceViewType: .list, + analytics: MockPOSAnalytics() ) let product = makeProductItem() @@ -69,7 +72,8 @@ struct POSItemActionHandlerTests { posModel: aggregateModel, searchTerm: "", itemType: .product, - sourceView: .product + sourceView: .product, + analytics: MockPOSAnalytics() ) let product = makeProductItem() @@ -106,6 +110,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: PointOfSaleSearchingItemsControllerProtocol = MockPointOfSaleCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = MockCardPresentPaymentService(), orderController: PointOfSaleOrderControllerProtocol = MockPointOfSaleOrderController(), + analytics: POSAnalyticsProviding = MockPOSAnalytics(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = MockPOSCollectOrderPaymentAnalyticsTracker(), searchHistoryService: POSSearchHistoryProviding = MockPOSSearchHistoryService(), popularPurchasableItemsController: PointOfSaleItemsControllerProtocol = MockPointOfSaleItemsController(), @@ -119,6 +124,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + analytics: analytics, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, popularPurchasableItemsController: popularPurchasableItemsController, diff --git a/WooCommerce/WooCommerceTests/POS/Presentation/PointOfSaleItemListAnalyticsTrackerTests.swift b/WooCommerce/WooCommerceTests/POS/Presentation/PointOfSaleItemListAnalyticsTrackerTests.swift index ae83b1f8277..b767b7472f0 100644 --- a/WooCommerce/WooCommerceTests/POS/Presentation/PointOfSaleItemListAnalyticsTrackerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Presentation/PointOfSaleItemListAnalyticsTrackerTests.swift @@ -12,7 +12,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackItemListSelected_tracks_correct_event_products_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -27,7 +27,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackItemListSelected_tracks_correct_event_products_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: true), searchTerm: "shoes", analytics: mockAnalytics) // When @@ -42,7 +42,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackItemListSelected_tracks_correct_event_coupons_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -57,7 +57,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackItemListSelected_tracks_correct_event_coupons_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: true), searchTerm: "discount", analytics: mockAnalytics) // When @@ -72,7 +72,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackNextPageWillLoad_tracks_correct_event_products_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -89,7 +89,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackNextPageWillLoad_tracks_correct_event_products_preSearch() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: true), searchTerm: "", analytics: mockAnalytics) // When @@ -105,7 +105,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackNextPageWillLoad_tracks_correct_event_products_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: true), searchTerm: "shoes", analytics: mockAnalytics) // When @@ -121,7 +121,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackNextPageWillLoad_tracks_correct_event_coupons_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -137,7 +137,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackNextPageWillLoad_tracks_correct_event_coupons_preSearch() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: true), searchTerm: "", analytics: mockAnalytics) // When @@ -153,7 +153,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackNextPageWillLoad_tracks_correct_event_coupons_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: true), searchTerm: "discount", analytics: mockAnalytics) // When @@ -169,7 +169,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackRefresh_tracks_correct_event_products_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -185,7 +185,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackRefresh_tracks_correct_event_products_preSearch() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: true), searchTerm: "", analytics: mockAnalytics) // When @@ -201,7 +201,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackRefresh_tracks_correct_event_products_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: true), searchTerm: "shoes", analytics: mockAnalytics) // When @@ -217,7 +217,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackRefresh_tracks_correct_event_coupons_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -233,7 +233,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackRefresh_tracks_correct_event_coupons_preSearch() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: true), searchTerm: "", analytics: mockAnalytics) // When @@ -249,7 +249,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackRefresh_tracks_correct_event_coupons_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: true), searchTerm: "discount", analytics: mockAnalytics) // When @@ -265,7 +265,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackSearchTapped_tracks_correct_event_products_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -280,7 +280,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackSearchTapped_tracks_correct_event_products_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .products(search: true), searchTerm: "shoes", analytics: mockAnalytics) // When @@ -295,7 +295,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackSearchTapped_tracks_correct_event_coupons_list() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: false), searchTerm: "", analytics: mockAnalytics) // When @@ -310,7 +310,7 @@ struct PointOfSaleItemListAnalyticsTrackerTests { @available(iOS 17.0, *) @Test func trackSearchTapped_tracks_correct_event_coupons_search() async throws { // Given - let mockAnalytics = MockAnalytics() + let mockAnalytics = MockPOSAnalytics() let tracker = PointOfSaleItemListAnalyticsTracker(selectedItemListType: .coupons(search: true), searchTerm: "discount", analytics: mockAnalytics) // When diff --git a/WooCommerce/WooCommerceTests/POS/Tools/PointOfSaleAssetsTests.swift b/WooCommerce/WooCommerceTests/POS/Tools/PointOfSaleAssetsTests.swift index 7ad6433ec0a..fad285ad5d1 100644 --- a/WooCommerce/WooCommerceTests/POS/Tools/PointOfSaleAssetsTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Tools/PointOfSaleAssetsTests.swift @@ -1,4 +1,5 @@ import XCTest +import PointOfSale @testable import WooCommerce diff --git a/WooCommerce/WooCommerceTests/POS/ViewHelpers/CartViewHelperTests.swift b/WooCommerce/WooCommerceTests/POS/ViewHelpers/CartViewHelperTests.swift index dc632eddb40..3e817f57297 100644 --- a/WooCommerce/WooCommerceTests/POS/ViewHelpers/CartViewHelperTests.swift +++ b/WooCommerce/WooCommerceTests/POS/ViewHelpers/CartViewHelperTests.swift @@ -1,6 +1,7 @@ import Foundation import Testing @testable import WooCommerce +import PointOfSale struct CartViewHelperTests { let sut = CartViewHelper()