From 18c84dfac244d0881c28c802de93296dc8999ced Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 10:33:32 +0700 Subject: [PATCH 01/21] Return coupons as PagedItems, hook to item list --- .../PointOfSaleItemsController.swift | 17 ++++++++++- .../Models/PointOfSaleAggregateModel.swift | 4 +++ .../Presentation/Item Selector/ItemList.swift | 12 ++++++-- .../POS/Presentation/ItemListView.swift | 28 +++++++++++++++++++ .../Classes/POS/Utils/PreviewHelpers.swift | 8 ++++-- .../PointOfSale/PointOfSaleItemService.swift | 11 +++++--- .../PointOfSaleItemServiceProtocol.swift | 2 +- 7 files changed, 72 insertions(+), 10 deletions(-) diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift index 29ca6511cc0..a1e6a03d022 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift @@ -6,6 +6,11 @@ import enum Yosemite.PointOfSaleItemServiceError import struct Yosemite.POSVariableParentProduct import class Yosemite.Store +enum POSItemType { + case products + case coupons +} + @available(iOS 17.0, *) protocol PointOfSaleItemsControllerProtocol { var itemsViewState: ItemsViewState { get } @@ -15,10 +20,17 @@ protocol PointOfSaleItemsControllerProtocol { func refreshItems(base: ItemListBaseItem) async /// Loads the next page of items for a given base item. func loadNextItems(base: ItemListBaseItem) async + /// + func toggleItemType() async } @available(iOS 17.0, *) @Observable final class PointOfSaleItemsController: PointOfSaleItemsControllerProtocol { + private var itemType: POSItemType = .products + func toggleItemType() async { + itemType = (itemType == .products) ? .coupons : .products + await loadRootItems() + } var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) @@ -201,7 +213,10 @@ private extension PointOfSaleItemsController { @MainActor func fetchItems(pageNumber: Int, appendToExistingItems: Bool = true) async throws -> Bool { do { - let pagedItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) + let pagedItems = itemType == .coupons + ? itemProvider.providePointOfSaleCoupons() + : try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) + let newItems = pagedItems.items var allItems = appendToExistingItems ? itemsViewState.itemsStack.root.items : [] let uniqueNewItems = newItems.filter { newItem in diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 1b2e86d038f..cc7904abdbd 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -92,6 +92,10 @@ protocol PointOfSaleAggregateModelProtocol { // MARK: - ItemList @available(iOS 17.0, *) extension PointOfSaleAggregateModel { + @MainActor + func toggleItemType() async { + await itemsController.toggleItemType() + } @MainActor func loadItems(base: ItemListBaseItem) async { await itemsController.loadItems(base: base) diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift index 35fe9f862af..3e3aeffbe54 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift @@ -116,8 +116,16 @@ private struct ItemListRow: View { }, label: { VariationCardView(variation: variation) }) - case .coupon: - EmptyView() + case let .coupon(coupon): + Button(action: { + debugPrint("Added to cart!") + }, label: { + HStack { + Text("Coupon:") + Text(coupon.id.uuidString) + Text(coupon.couponID.description) + } + }) } } } diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 808cbcadffa..ee825c8167e 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -2,6 +2,8 @@ import SwiftUI import enum Yosemite.POSItem import protocol Yosemite.POSOrderableItem +import struct Yosemite.POSCoupon + @available(iOS 17.0, *) struct ItemListView: View { @Environment(\.dynamicTypeSize) private var dynamicTypeSize @@ -16,6 +18,11 @@ struct ItemListView: View { @AppStorage(BannerState.isSimpleProductsOnlyBannerDismissedKey) private var isHeaderBannerDismissed: Bool = false + private var shouldShowCoupons: Bool { + ServiceLocator.featureFlagService.isFeatureFlagEnabled(.enableCouponsInPointOfSale) + } + @State var shouldReload: Bool = false + var body: some View { if #available(iOS 18.0, *) { NavigationStack { @@ -33,6 +40,27 @@ struct ItemListView: View { var content: some View { VStack { headerView + + HStack { + Button(action: { + shouldReload.toggle() + Task { + await posModel.toggleItemType() + } + }, label: { + Text("Products") + }) + Button(action: { + shouldReload.toggle() + Task { + await posModel.toggleItemType() + } + }, label: { + Text("Coupons") + }) + } + .renderedIf(shouldShowCoupons) + switch itemListState { case .loading(let items), .loaded(let items, _), diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index 5d15af6a027..c086150d725 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -57,8 +57,8 @@ final class PointOfSalePreviewItemService: PointOfSaleItemServiceProtocol { formattedPrice: "$1.00") } - func providePointOfSaleCoupons() -> [POSItem] { - [] + func providePointOfSaleCoupons() -> PagedItems { + .init(items: [], hasMorePages: true) } } @@ -91,6 +91,10 @@ final class PointOfSalePreviewItemsController: PointOfSaleItemsControllerProtoco private func loadInitialChildItems(for parent: POSItem) async { // Set `itemsViewState` instead. } + + func toggleItemType() async { + // + } } private var mockItems: [POSItem] { diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift index da8bb81a0c1..c69d0039acd 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift @@ -101,9 +101,10 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol { // TODO: // gh-15326 - Return PagedItems instead. - public func providePointOfSaleCoupons() -> [POSItem] { + public func providePointOfSaleCoupons() -> PagedItems { guard let storage = storage else { - return [] + // TODO: Error handling + return .init(items: [], hasMorePages: false) } let predicate = NSPredicate(format: "siteID == %lld", siteID) let descriptor = NSSortDescriptor(keyPath: \StorageCoupon.dateCreated, @@ -116,10 +117,12 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol { do { try resultsController.performFetch() let storeCoupons = resultsController.fetchedObjects - return mapCouponsToPOSItems(coupons: storeCoupons) + let posCoupons = mapCouponsToPOSItems(coupons: storeCoupons) + return .init(items: posCoupons, hasMorePages: false) } catch { + // TODO: Error handling debugPrint(error) - return [] + return .init(items: [], hasMorePages: false) } } diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift index be69ba5915c..2ca0b298e1b 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift @@ -58,7 +58,7 @@ public extension Sequence where Element == POSOrderableItem { public protocol PointOfSaleItemServiceProtocol { func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems - func providePointOfSaleCoupons() -> [POSItem] + func providePointOfSaleCoupons() -> PagedItems } // Default implementation for convenience, so we do not need to pass the first page explicitly From b888782e3e273a5faa1b825c8799d5f0f2132fc6 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:08:17 +0700 Subject: [PATCH 02/21] Make providePointOfSaleCoupons throwable Fetching from storage can fail, also we can reuse the error handling from items controller --- .../PointOfSaleItemsController.swift | 23 +++++++------------ .../Classes/POS/Utils/PreviewHelpers.swift | 2 +- .../POS/Mocks/MockPOSItemProvider.swift | 4 ++-- .../PointOfSale/PointOfSaleItemService.swift | 22 ++++++------------ .../PointOfSaleItemServiceProtocol.swift | 2 +- 5 files changed, 19 insertions(+), 34 deletions(-) diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift index a1e6a03d022..f7295b1ae28 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift @@ -20,23 +20,19 @@ protocol PointOfSaleItemsControllerProtocol { func refreshItems(base: ItemListBaseItem) async /// Loads the next page of items for a given base item. func loadNextItems(base: ItemListBaseItem) async - /// + /// Toggles between item types func toggleItemType() async } @available(iOS 17.0, *) @Observable final class PointOfSaleItemsController: PointOfSaleItemsControllerProtocol { - private var itemType: POSItemType = .products - func toggleItemType() async { - itemType = (itemType == .products) ? .coupons : .products - await loadRootItems() - } var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) private let paginationTracker: AsyncPaginationTracker private var childPaginationTrackers: [POSItem: AsyncPaginationTracker] = [:] private let itemProvider: PointOfSaleItemServiceProtocol + private var itemType: POSItemType = .products init(itemProvider: PointOfSaleItemServiceProtocol) { self.itemProvider = itemProvider @@ -54,6 +50,11 @@ protocol PointOfSaleItemsControllerProtocol { await loadFirstPage(base: base) } + func toggleItemType() async { + itemType = (itemType == .products) ? .coupons : .products + await loadFirstPage(base: .root) + } + @MainActor private func loadFirstPage(base: ItemListBaseItem) async { switch base { @@ -170,14 +171,6 @@ protocol PointOfSaleItemsControllerProtocol { } } -@available(iOS 17.0, *) -private extension PointOfSaleItemsController { - func loadPointOfSaleCoupons() { - let posCoupons = itemProvider.providePointOfSaleCoupons() - debugPrint(posCoupons) - } -} - @available(iOS 17.0, *) private extension PointOfSaleItemsController { func setLoadingState(base: ItemListBaseItem) { @@ -214,7 +207,7 @@ private extension PointOfSaleItemsController { func fetchItems(pageNumber: Int, appendToExistingItems: Bool = true) async throws -> Bool { do { let pagedItems = itemType == .coupons - ? itemProvider.providePointOfSaleCoupons() + ? try itemProvider.providePointOfSaleCoupons() : try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) let newItems = pagedItems.items diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index c086150d725..f77138d707c 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -57,7 +57,7 @@ final class PointOfSalePreviewItemService: PointOfSaleItemServiceProtocol { formattedPrice: "$1.00") } - func providePointOfSaleCoupons() -> PagedItems { + func providePointOfSaleCoupons() throws -> PagedItems { .init(items: [], hasMorePages: true) } } diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift index 729a4cc7f65..0e8584414d7 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift @@ -46,8 +46,8 @@ final class MockPointOfSaleItemService: PointOfSaleItemServiceProtocol { return .init(items: MockPointOfSaleItemService.makeInitialVariationItems(), hasMorePages: shouldSimulateTwoPagesOfVariations) } - func providePointOfSaleCoupons() -> [Yosemite.POSItem] { - [] + func providePointOfSaleCoupons() throws -> PagedItems { + .init(items: [], hasMorePages: true) } } diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift index c69d0039acd..ed02fdedce3 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift @@ -12,6 +12,7 @@ import Storage public enum PointOfSaleItemServiceError: Error, Equatable { case requestFailed case requestCancelled + case storageFailure case unknown } @@ -99,12 +100,9 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol { } } - // TODO: - // gh-15326 - Return PagedItems instead. - public func providePointOfSaleCoupons() -> PagedItems { + public func providePointOfSaleCoupons() throws -> PagedItems { guard let storage = storage else { - // TODO: Error handling - return .init(items: [], hasMorePages: false) + throw PointOfSaleItemServiceError.storageFailure } let predicate = NSPredicate(format: "siteID == %lld", siteID) let descriptor = NSSortDescriptor(keyPath: \StorageCoupon.dateCreated, @@ -114,16 +112,10 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol { matching: predicate, sortedBy: [descriptor]) - do { - try resultsController.performFetch() - let storeCoupons = resultsController.fetchedObjects - let posCoupons = mapCouponsToPOSItems(coupons: storeCoupons) - return .init(items: posCoupons, hasMorePages: false) - } catch { - // TODO: Error handling - debugPrint(error) - return .init(items: [], hasMorePages: false) - } + try resultsController.performFetch() + let storeCoupons = resultsController.fetchedObjects + let posCoupons = mapCouponsToPOSItems(coupons: storeCoupons) + return .init(items: posCoupons, hasMorePages: false) } private func mapCouponsToPOSItems(coupons: [Coupon]) -> [POSItem] { diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift index 2ca0b298e1b..726cb4e8558 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift @@ -58,7 +58,7 @@ public extension Sequence where Element == POSOrderableItem { public protocol PointOfSaleItemServiceProtocol { func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems - func providePointOfSaleCoupons() -> PagedItems + func providePointOfSaleCoupons() throws -> PagedItems } // Default implementation for convenience, so we do not need to pass the first page explicitly From a37563bdd116425e9acccea30abb5d5d2d939045 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:11:30 +0700 Subject: [PATCH 03/21] switch coupons feature flag to false Now that we have dummy UI updates we should keep the flag off in trunk --- Experiments/Experiments/DefaultFeatureFlagService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experiments/Experiments/DefaultFeatureFlagService.swift b/Experiments/Experiments/DefaultFeatureFlagService.swift index b1d9069dcca..a5749eae12c 100644 --- a/Experiments/Experiments/DefaultFeatureFlagService.swift +++ b/Experiments/Experiments/DefaultFeatureFlagService.swift @@ -72,7 +72,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService { case .pointOfSale: return buildConfig == .localDeveloper || buildConfig == .alpha case .enableCouponsInPointOfSale: - return buildConfig == .localDeveloper || buildConfig == .alpha + return false case .googleAdsCampaignCreationOnWebView: return true case .backgroundTasks: From 35d6193afa0a81911c2e85143a59a5ee06227260 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:18:56 +0700 Subject: [PATCH 04/21] Make CouponCardView --- .../POS/Models/PointOfSaleAggregateModel.swift | 1 + .../Item Selector/CouponCardView.swift | 17 +++++++++++++++++ .../Presentation/Item Selector/ItemList.swift | 8 ++------ .../WooCommerce.xcodeproj/project.pbxproj | 4 ++++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index cc7904abdbd..90c20235b81 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -124,6 +124,7 @@ private extension POSItem { case .variableParentProduct: return nil case .coupon: + debugPrint("Not implemented. TODO: Make POSCoupon POSOrderable") return nil } } diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift new file mode 100644 index 00000000000..99ad157c75f --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift @@ -0,0 +1,17 @@ +import SwiftUI +import struct Yosemite.POSCoupon + +struct CouponCardView: View { + private let coupon: POSCoupon + + init(coupon: POSCoupon) { + self.coupon = coupon + } + + var body: some View { + HStack { + Text(coupon.id.uuidString) + Text(coupon.couponID.description) + } + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift index 3e3aeffbe54..5ace62aee06 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift @@ -118,13 +118,9 @@ private struct ItemListRow: View { }) case let .coupon(coupon): Button(action: { - debugPrint("Added to cart!") + posModel.addToCart(item) }, label: { - HStack { - Text("Coupon:") - Text(coupon.id.uuidString) - Text(coupon.couponID.description) - } + CouponCardView(coupon: coupon) }) } } diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 1f7c50dea89..c438acdc888 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1590,6 +1590,7 @@ 68600A912C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68600A902C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift */; }; 68625DE62D4134D70042B231 /* DynamicVStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68625DE52D4134D50042B231 /* DynamicVStack.swift */; }; 68674D312B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68674D302B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift */; }; + 6868153B2D83F2A4006778A3 /* CouponCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6868153A2D83F2A2006778A3 /* CouponCardView.swift */; }; 68709D3D2A2ED94900A7FA6C /* UpgradesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68709D3C2A2ED94900A7FA6C /* UpgradesView.swift */; }; 68709D402A2EE2DC00A7FA6C /* UpgradesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68709D3F2A2EE2DC00A7FA6C /* UpgradesViewModel.swift */; }; 6879B8DB287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */; }; @@ -4752,6 +4753,7 @@ 68600A902C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleItemListEmptyView.swift; sourceTree = ""; }; 68625DE52D4134D50042B231 /* DynamicVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicVStack.swift; sourceTree = ""; }; 68674D302B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptEligibilityUseCaseTests.swift; sourceTree = ""; }; + 6868153A2D83F2A2006778A3 /* CouponCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponCardView.swift; sourceTree = ""; }; 68709D3C2A2ED94900A7FA6C /* UpgradesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradesView.swift; sourceTree = ""; }; 68709D3F2A2EE2DC00A7FA6C /* UpgradesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradesViewModel.swift; sourceTree = ""; }; 6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualsViewModelTests.swift; sourceTree = ""; }; @@ -7513,6 +7515,7 @@ 029149792D2682DF00F7B3B3 /* Item Selector */ = { isa = PBXGroup; children = ( + 6868153A2D83F2A2006778A3 /* CouponCardView.swift */, 0291497A2D2682FF00F7B3B3 /* ItemList.swift */, 20EFAEA52D35337F00D35F9C /* ItemListErrorCardView.swift */, 026826A42BF59DF60036F959 /* SimpleProductCardView.swift */, @@ -16206,6 +16209,7 @@ DED039292BC7A04B005D0571 /* StorePerformanceView.swift in Sources */, 03E471C4293A1F8D001A58AD /* BuiltInReaderConnectionAlertsProvider.swift in Sources */, 20CC1EDD2AFA99DF006BD429 /* InPersonPaymentsMenuViewModel.swift in Sources */, + 6868153B2D83F2A4006778A3 /* CouponCardView.swift in Sources */, 011DF3462C53A919000AFDD9 /* PointOfSaleCardPresentPaymentActivityIndicatingMessageView.swift in Sources */, D8736B7522F1FE1600A14A29 /* BadgeLabel.swift in Sources */, EE4C45652C352D60001A3D94 /* AIToneVoice.swift in Sources */, From 7d9305f0e1cd2cd57b77f06b4e54656061a4fe1f Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:22:55 +0700 Subject: [PATCH 05/21] Remove unnecessary state. Collapse func duplication --- .../Classes/POS/Presentation/ItemListView.swift | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index ee825c8167e..8381814c709 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -21,7 +21,6 @@ struct ItemListView: View { private var shouldShowCoupons: Bool { ServiceLocator.featureFlagService.isFeatureFlagEnabled(.enableCouponsInPointOfSale) } - @State var shouldReload: Bool = false var body: some View { if #available(iOS 18.0, *) { @@ -43,18 +42,12 @@ struct ItemListView: View { HStack { Button(action: { - shouldReload.toggle() - Task { - await posModel.toggleItemType() - } + toggleItemType() }, label: { Text("Products") }) Button(action: { - shouldReload.toggle() - Task { - await posModel.toggleItemType() - } + toggleItemType() }, label: { Text("Coupons") }) @@ -167,6 +160,12 @@ private extension ItemListView { var shouldShowHeaderBanner: Bool { itemListState.eligibleToShowSimpleProductsBanner && !isHeaderBannerDismissed } + + func toggleItemType() { + Task { + await posModel.toggleItemType() + } + } } private extension ItemListState { From 9f5c228c6c01c450648bcaed1b0cd3c71e1960c6 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:23:43 +0700 Subject: [PATCH 06/21] lint --- .../POS/Presentation/Item Selector/CouponCardView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift index 99ad157c75f..e5222469ae2 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift @@ -1,9 +1,9 @@ import SwiftUI import struct Yosemite.POSCoupon - + struct CouponCardView: View { private let coupon: POSCoupon - + init(coupon: POSCoupon) { self.coupon = coupon } From 6d7dfa91800c8aa1744c08dd31f827a6c46fecad Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:26:00 +0700 Subject: [PATCH 07/21] Invert fetch logic, make products first case --- .../POS/Controllers/PointOfSaleItemsController.swift | 6 +++--- .../Classes/POS/Models/PointOfSaleAggregateModel.swift | 7 ++++--- WooCommerce/Classes/POS/Presentation/ItemListView.swift | 1 - 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift index f7295b1ae28..6ba8f5f8fd6 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift @@ -206,9 +206,9 @@ private extension PointOfSaleItemsController { @MainActor func fetchItems(pageNumber: Int, appendToExistingItems: Bool = true) async throws -> Bool { do { - let pagedItems = itemType == .coupons - ? try itemProvider.providePointOfSaleCoupons() - : try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) + let pagedItems = itemType == .products + ? try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) + : try itemProvider.providePointOfSaleCoupons() let newItems = pagedItems.items var allItems = appendToExistingItems ? itemsViewState.itemsStack.root.items : [] diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 90c20235b81..347b481334d 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -93,9 +93,10 @@ protocol PointOfSaleAggregateModelProtocol { @available(iOS 17.0, *) extension PointOfSaleAggregateModel { @MainActor - func toggleItemType() async { - await itemsController.toggleItemType() - } + func toggleItemType() async { + await itemsController.toggleItemType() + } + @MainActor func loadItems(base: ItemListBaseItem) async { await itemsController.loadItems(base: base) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 8381814c709..d17d0292c1d 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -1,7 +1,6 @@ import SwiftUI import enum Yosemite.POSItem import protocol Yosemite.POSOrderableItem - import struct Yosemite.POSCoupon @available(iOS 17.0, *) From b39973e81d340f980c3b44529def3c16e36dfdf1 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 12:26:30 +0700 Subject: [PATCH 08/21] lint --- WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 347b481334d..fb181375566 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -96,7 +96,7 @@ extension PointOfSaleAggregateModel { func toggleItemType() async { await itemsController.toggleItemType() } - + @MainActor func loadItems(base: ItemListBaseItem) async { await itemsController.loadItems(base: base) From a5f58da222e1d4dd50e477c0ddeb5b46c33eedaa Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Fri, 14 Mar 2025 16:07:21 +0700 Subject: [PATCH 09/21] make test target compile --- .../POS/Mocks/MockPointOfSaleItemsService.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift index d2a43c035d2..27ce1fa68ad 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift @@ -14,4 +14,6 @@ final class MockPointOfSaleItemsController: PointOfSaleItemsControllerProtocol { func refreshItems(base: WooCommerce.ItemListBaseItem) async { } func loadNextItems(base: ItemListBaseItem) async { } + + func toggleItemType() async { } } From 9523774bd79e4038a60ac6f25fb901f4bfce402e Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 10:29:37 +0700 Subject: [PATCH 10/21] Make PointOfSaleCouponsController --- .../PointOfSaleCouponsController.swift | 33 +++++++++++++++++++ .../PointOfSaleItemsController.swift | 20 +++++------ .../Models/PointOfSaleAggregateModel.swift | 5 --- .../POS/Presentation/ItemListView.swift | 4 +-- .../Classes/POS/Utils/PreviewHelpers.swift | 10 ++---- .../WooCommerce.xcodeproj/project.pbxproj | 4 +++ .../POS/Mocks/MockPOSItemProvider.swift | 4 --- .../Mocks/MockPointOfSaleItemsService.swift | 3 +- 8 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift new file mode 100644 index 00000000000..e33910195ee --- /dev/null +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift @@ -0,0 +1,33 @@ +import Observation +import enum Yosemite.POSItem +import protocol Yosemite.PointOfSaleItemServiceProtocol + +@available(iOS 17.0, *) +@Observable final class PointOfSaleCouponsController: PointOfSaleItemsControllerProtocol { + let itemType: ItemType = .coupons + + var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, + itemsStack: ItemsStackState(root: .loading([]), + itemStates: [:])) + private let paginationTracker: AsyncPaginationTracker + private var childPaginationTrackers: [POSItem: AsyncPaginationTracker] = [:] + private let itemProvider: PointOfSaleItemServiceProtocol + + init(itemProvider: PointOfSaleItemServiceProtocol) { + self.itemProvider = itemProvider + self.paginationTracker = .init() + } + + @MainActor + func loadItems(base: ItemListBaseItem) async { + debugPrint("🍍 CouponsController::loadItems called") + } + + func refreshItems(base: ItemListBaseItem) async { + debugPrint("🍍 CouponsController::refreshItems called") + } + + func loadNextItems(base: ItemListBaseItem) async { + debugPrint("🍍 CouponsController::loadNextItems called") + } +} diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift index 6ba8f5f8fd6..b146cb9e040 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift @@ -6,13 +6,16 @@ import enum Yosemite.PointOfSaleItemServiceError import struct Yosemite.POSVariableParentProduct import class Yosemite.Store -enum POSItemType { +enum ItemType { case products case coupons } @available(iOS 17.0, *) protocol PointOfSaleItemsControllerProtocol { + /// + var itemType: ItemType { get } + /// var itemsViewState: ItemsViewState { get } /// Loads the first page of items for a given base item. func loadItems(base: ItemListBaseItem) async @@ -20,19 +23,19 @@ protocol PointOfSaleItemsControllerProtocol { func refreshItems(base: ItemListBaseItem) async /// Loads the next page of items for a given base item. func loadNextItems(base: ItemListBaseItem) async - /// Toggles between item types - func toggleItemType() async } + + @available(iOS 17.0, *) @Observable final class PointOfSaleItemsController: PointOfSaleItemsControllerProtocol { + let itemType: ItemType = .products var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) private let paginationTracker: AsyncPaginationTracker private var childPaginationTrackers: [POSItem: AsyncPaginationTracker] = [:] private let itemProvider: PointOfSaleItemServiceProtocol - private var itemType: POSItemType = .products init(itemProvider: PointOfSaleItemServiceProtocol) { self.itemProvider = itemProvider @@ -50,11 +53,6 @@ protocol PointOfSaleItemsControllerProtocol { await loadFirstPage(base: base) } - func toggleItemType() async { - itemType = (itemType == .products) ? .coupons : .products - await loadFirstPage(base: .root) - } - @MainActor private func loadFirstPage(base: ItemListBaseItem) async { switch base { @@ -206,9 +204,7 @@ private extension PointOfSaleItemsController { @MainActor func fetchItems(pageNumber: Int, appendToExistingItems: Bool = true) async throws -> Bool { do { - let pagedItems = itemType == .products - ? try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) - : try itemProvider.providePointOfSaleCoupons() + let pagedItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber) let newItems = pagedItems.items var allItems = appendToExistingItems ? itemsViewState.itemsStack.root.items : [] diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index fb181375566..3929f396a90 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -92,11 +92,6 @@ protocol PointOfSaleAggregateModelProtocol { // MARK: - ItemList @available(iOS 17.0, *) extension PointOfSaleAggregateModel { - @MainActor - func toggleItemType() async { - await itemsController.toggleItemType() - } - @MainActor func loadItems(base: ItemListBaseItem) async { await itemsController.loadItems(base: base) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index d17d0292c1d..f1e6cc39ec2 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -161,9 +161,7 @@ private extension ItemListView { } func toggleItemType() { - Task { - await posModel.toggleItemType() - } + debugPrint("🍍 Toggle ItemType tapped") } } diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index f77138d707c..ea50477ff24 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -56,14 +56,12 @@ final class PointOfSalePreviewItemService: PointOfSaleItemServiceProtocol { name: "Product 1", formattedPrice: "$1.00") } - - func providePointOfSaleCoupons() throws -> PagedItems { - .init(items: [], hasMorePages: true) - } } @available(iOS 17.0, *) final class PointOfSalePreviewItemsController: PointOfSaleItemsControllerProtocol { + let itemType: ItemType = .products + @Published var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) @@ -91,10 +89,6 @@ final class PointOfSalePreviewItemsController: PointOfSaleItemsControllerProtoco private func loadInitialChildItems(for parent: POSItem) async { // Set `itemsViewState` instead. } - - func toggleItemType() async { - // - } } private var mockItems: [POSItem] { diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index b43a66bbfe5..d54959aa7e8 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1607,6 +1607,7 @@ 68A905012ACCFC13004C71D3 /* CollapsibleProductCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A905002ACCFC13004C71D3 /* CollapsibleProductCard.swift */; }; 68AC9D292ACE598B0042F784 /* ProductImageThumbnail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */; }; 68AF3C3B2D01481C006F1ED2 /* POSReceiptEligibilityBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */; }; + 68B681162D9257810098D5CD /* PointOfSaleCouponsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */; }; 68B6F22B2ADE7ED500D171FC /* TooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B6F22A2ADE7ED500D171FC /* TooltipView.swift */; }; 68C31B712A8617C500AE5C5A /* NewNoteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C31B702A8617C500AE5C5A /* NewNoteViewModel.swift */; }; 68C53CBE2C1FE59B00C6D80B /* ItemListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */; }; @@ -4784,6 +4785,7 @@ 68A905002ACCFC13004C71D3 /* CollapsibleProductCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleProductCard.swift; sourceTree = ""; }; 68AC9D282ACE598B0042F784 /* ProductImageThumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageThumbnail.swift; sourceTree = ""; }; 68AF3C3A2D01481A006F1ED2 /* POSReceiptEligibilityBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSReceiptEligibilityBanner.swift; sourceTree = ""; }; + 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponsController.swift; sourceTree = ""; }; 68B6F22A2ADE7ED500D171FC /* TooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipView.swift; sourceTree = ""; }; 68C31B702A8617C500AE5C5A /* NewNoteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNoteViewModel.swift; sourceTree = ""; }; 68C53CBD2C1FE59B00C6D80B /* ItemListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListView.swift; sourceTree = ""; }; @@ -8199,6 +8201,7 @@ 200BA1572CF092150006DC5B /* Controllers */ = { isa = PBXGroup; children = ( + 68B681152D92577F0098D5CD /* PointOfSaleCouponsController.swift */, 200BA1582CF092280006DC5B /* PointOfSaleItemsController.swift */, 20CF75B92CF4E69000ACCF4A /* PointOfSaleOrderController.swift */, ); @@ -16760,6 +16763,7 @@ 268EC46126D3F67800716F5C /* EditCustomerNoteViewModel.swift in Sources */, DE5746362B4522ED0034B10D /* BlazeBudgetSettingViewModel.swift in Sources */, B555530D21B57DC300449E71 /* UserNotificationsCenterAdapter.swift in Sources */, + 68B681162D9257810098D5CD /* PointOfSaleCouponsController.swift in Sources */, 45C8B2662316AB460002FA77 /* BillingAddressTableViewCell.swift in Sources */, 26BCA0422C35EDBF000BE96C /* OrderListSyncBackgroundTask.swift in Sources */, E11228BC2707161E004E9F2D /* CardPresentModalUpdateFailed.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift index 0e8584414d7..f4e4bb1e942 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSItemProvider.swift @@ -45,10 +45,6 @@ final class MockPointOfSaleItemService: PointOfSaleItemServiceProtocol { return .init(items: MockPointOfSaleItemService.makeInitialVariationItems(), hasMorePages: shouldSimulateTwoPagesOfVariations) } - - func providePointOfSaleCoupons() throws -> PagedItems { - .init(items: [], hasMorePages: true) - } } extension MockPointOfSaleItemService { diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift index 27ce1fa68ad..88d91e7ae62 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift @@ -5,6 +5,7 @@ import enum Yosemite.POSItem @available(iOS 17.0, *) final class MockPointOfSaleItemsController: PointOfSaleItemsControllerProtocol { + let itemType: ItemType = .products var itemsViewState: ItemsViewState = .init(containerState: .empty, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:])) @@ -14,6 +15,4 @@ final class MockPointOfSaleItemsController: PointOfSaleItemsControllerProtocol { func refreshItems(base: WooCommerce.ItemListBaseItem) async { } func loadNextItems(base: ItemListBaseItem) async { } - - func toggleItemType() async { } } From a10bdf11f75e4f4723eae4d3a8858ea8045127fc Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 10:34:26 +0700 Subject: [PATCH 11/21] Create PointOfSaleCouponService --- Yosemite/Yosemite.xcodeproj/project.pbxproj | 4 + .../PointOfSaleCouponService.swift | 81 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift diff --git a/Yosemite/Yosemite.xcodeproj/project.pbxproj b/Yosemite/Yosemite.xcodeproj/project.pbxproj index a414f3bbbcf..16bbf41d7c1 100644 --- a/Yosemite/Yosemite.xcodeproj/project.pbxproj +++ b/Yosemite/Yosemite.xcodeproj/project.pbxproj @@ -272,6 +272,7 @@ 6898F3742C0842150039F10A /* PointOfSaleItemServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6898F3732C0842150039F10A /* PointOfSaleItemServiceProtocol.swift */; }; 689D11D52891B9A400F6A83F /* WooFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 689D11D42891B9A400F6A83F /* WooFoundation.framework */; }; 68A70DD22D0BF6F60013B807 /* POSReceiptService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68A70DD12D0BF6F30013B807 /* POSReceiptService.swift */; }; + 68B681182D925B190098D5CD /* PointOfSaleCouponService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68B681172D925B170098D5CD /* PointOfSaleCouponService.swift */; }; 68BD37B528DB2E9800C2A517 /* CustomerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BD37B428DB2E9800C2A517 /* CustomerStore.swift */; }; 68BD37B928DB323D00C2A517 /* CustomerStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BD37B828DB323D00C2A517 /* CustomerStoreTests.swift */; }; 68EA25342C08734900C49AE2 /* POSSimpleProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68EA25332C08734800C49AE2 /* POSSimpleProduct.swift */; }; @@ -813,6 +814,7 @@ 6898F3732C0842150039F10A /* PointOfSaleItemServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleItemServiceProtocol.swift; sourceTree = ""; }; 689D11D42891B9A400F6A83F /* WooFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WooFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 68A70DD12D0BF6F30013B807 /* POSReceiptService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSReceiptService.swift; sourceTree = ""; }; + 68B681172D925B170098D5CD /* PointOfSaleCouponService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCouponService.swift; sourceTree = ""; }; 68BD37B428DB2E9800C2A517 /* CustomerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerStore.swift; sourceTree = ""; }; 68BD37B828DB323D00C2A517 /* CustomerStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerStoreTests.swift; sourceTree = ""; }; 68EA25332C08734800C49AE2 /* POSSimpleProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSSimpleProduct.swift; sourceTree = ""; }; @@ -1534,6 +1536,7 @@ 6898F3722C0840FE0039F10A /* PointOfSale */ = { isa = PBXGroup; children = ( + 68B681172D925B170098D5CD /* PointOfSaleCouponService.swift */, 6823533E2D82A90A00F24470 /* POSCoupon.swift */, 6898F3732C0842150039F10A /* PointOfSaleItemServiceProtocol.swift */, 68EA25332C08734800C49AE2 /* POSSimpleProduct.swift */, @@ -2345,6 +2348,7 @@ CECE6BBE2BA9DE3200A57C1F /* WCAnalyticsCustomer+ReadOnlyConvertible.swift in Sources */, 02FF054F23D983F30058E6E7 /* FileManager+URL.swift in Sources */, 03F3AFE728097D6400E328BE /* CardPresentPaymentsPlugin.swift in Sources */, + 68B681182D925B190098D5CD /* PointOfSaleCouponService.swift in Sources */, B52E0032211A440D00700FDE /* Order+ReadOnlyType.swift in Sources */, CE606D9C2BE3BCC4001CB424 /* ShippingMethod+ReadOnlyConvertible.swift in Sources */, 261F94E4242EFA6D00762B58 /* ProductCategoryAction.swift in Sources */, diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift new file mode 100644 index 00000000000..d5106f7eee7 --- /dev/null +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift @@ -0,0 +1,81 @@ +import protocol Networking.Network +import protocol Networking.ProductVariationsRemoteProtocol +import class Networking.ProductsRemote +import class Networking.ProductVariationsRemote +import class Networking.AlamofireNetwork +import class WooFoundation.CurrencyFormatter +import class WooFoundation.CurrencySettings +import Storage + +public final class PointOfSaleCouponService: PointOfSaleItemServiceProtocol { + private var siteID: Int64 + private let currencyFormatter: CurrencyFormatter + private let productsRemote: ProductsRemote + private let variationRemote: ProductVariationsRemoteProtocol + private let storage: StorageManagerType? + + public init(siteID: Int64, + currencySettings: CurrencySettings, + network: Network, + storage: StorageManagerType? = nil) { + self.siteID = siteID + self.currencyFormatter = CurrencyFormatter(currencySettings: currencySettings) + self.productsRemote = ProductsRemote(network: network) + self.variationRemote = ProductVariationsRemote(network: network) + self.storage = storage + } + + public convenience init(siteID: Int64, + currencySettings: CurrencySettings, + credentials: Credentials?, + storage: StorageManagerType) { + self.init(siteID: siteID, + currencySettings: currencySettings, + network: AlamofireNetwork(credentials: credentials), + storage: storage) + } + + // TODO: + // gh-15326 - Return PagedItems instead. + @MainActor + public func providePointOfSaleCoupons() -> [POSItem] { + guard let storage = storage else { + return [] + } + let predicate = NSPredicate(format: "siteID == %lld", siteID) + let descriptor = NSSortDescriptor(keyPath: \StorageCoupon.dateCreated, + ascending: false) + + let resultsController = ResultsController(storageManager: storage, + matching: predicate, + sortedBy: [descriptor]) + + do { + try resultsController.performFetch() + let storeCoupons = resultsController.fetchedObjects + return mapCouponsToPOSItems(coupons: storeCoupons) + } catch { + debugPrint(error) + return [] + } + } + + private func mapCouponsToPOSItems(coupons: [Coupon]) -> [POSItem] { + coupons.compactMap { coupon in + .coupon(POSCoupon(id: UUID(), couponID: coupon.couponID)) + } + } + + // TODO: Remove this conformance + public func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems { + return .init(items: [], hasMorePages: false) + } + + @MainActor + public func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems { + // Lands here when invoked from coupons controller -> Error, as won't show any in the view if we return empty. + //return .init(items: [], hasMorePages: false) + let coupons = providePointOfSaleCoupons() + return .init(items: coupons, hasMorePages: false) + } +} From ad84397eb79f34ab054f2d297253b51f24388733 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 10:39:45 +0700 Subject: [PATCH 12/21] Delete coupon-specific logic from itemservice --- .../PointOfSale/PointOfSaleItemService.swift | 24 ------------------- .../PointOfSaleItemServiceProtocol.swift | 1 - 2 files changed, 25 deletions(-) diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift index ed02fdedce3..ec4fd5fa065 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift @@ -100,30 +100,6 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol { } } - public func providePointOfSaleCoupons() throws -> PagedItems { - guard let storage = storage else { - throw PointOfSaleItemServiceError.storageFailure - } - let predicate = NSPredicate(format: "siteID == %lld", siteID) - let descriptor = NSSortDescriptor(keyPath: \StorageCoupon.dateCreated, - ascending: false) - - let resultsController = ResultsController(storageManager: storage, - matching: predicate, - sortedBy: [descriptor]) - - try resultsController.performFetch() - let storeCoupons = resultsController.fetchedObjects - let posCoupons = mapCouponsToPOSItems(coupons: storeCoupons) - return .init(items: posCoupons, hasMorePages: false) - } - - private func mapCouponsToPOSItems(coupons: [Coupon]) -> [POSItem] { - coupons.compactMap { coupon in - .coupon(POSCoupon(id: UUID(), couponID: coupon.couponID)) - } - } - // Maps result to POSItem, and populate the output with: // - Formatted price based on store's currency settings. // - Product thumbnail, if any. diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift index 726cb4e8558..110212b14a9 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemServiceProtocol.swift @@ -58,7 +58,6 @@ public extension Sequence where Element == POSOrderableItem { public protocol PointOfSaleItemServiceProtocol { func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems - func providePointOfSaleCoupons() throws -> PagedItems } // Default implementation for convenience, so we do not need to pass the first page explicitly From 1aa3efc0beb14a91245e35127920bde3580a33d2 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 10:47:07 +0700 Subject: [PATCH 13/21] Inject couponsController into POS entry point --- .../POS/Presentation/PointOfSaleEntryPointView.swift | 4 ++++ WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift | 1 + .../ViewRelated/Hub Menu/HubMenuViewModel.swift | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift index 2e4f53ce5d9..3e15395fe1f 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift @@ -8,11 +8,13 @@ struct PointOfSaleEntryPointView: View { private let onPointOfSaleModeActiveStateChange: ((Bool) -> Void) private let itemsController: PointOfSaleItemsControllerProtocol + private let couponsController: PointOfSaleItemsControllerProtocol private let cardPresentPaymentService: CardPresentPaymentFacade private let orderController: PointOfSaleOrderControllerProtocol private let collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking init(itemsController: PointOfSaleItemsControllerProtocol, + couponsController: PointOfSaleItemsControllerProtocol, onPointOfSaleModeActiveStateChange: @escaping ((Bool) -> Void), cardPresentPaymentService: CardPresentPaymentFacade, orderController: PointOfSaleOrderControllerProtocol, @@ -20,6 +22,7 @@ struct PointOfSaleEntryPointView: View { self.onPointOfSaleModeActiveStateChange = onPointOfSaleModeActiveStateChange self.itemsController = itemsController + self.couponsController = couponsController self.cardPresentPaymentService = cardPresentPaymentService self.orderController = orderController self.collectOrderPaymentAnalyticsTracker = collectOrderPaymentAnalyticsTracker @@ -60,6 +63,7 @@ struct PointOfSaleEntryPointView: View { @available(iOS 17.0, *) #Preview { PointOfSaleEntryPointView(itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), onPointOfSaleModeActiveStateChange: { _ in }, cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift index ad0ccaedc7d..ff054b558c8 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenu.swift @@ -40,6 +40,7 @@ struct HubMenu: View { #available(iOS 17.0, *) { PointOfSaleEntryPointView( itemsController: PointOfSaleItemsController(itemProvider: viewModel.posItemProvider), + couponsController: PointOfSaleCouponsController(itemProvider: viewModel.posCouponProvider), onPointOfSaleModeActiveStateChange: { isEnabled in viewModel.updateDefaultConfigurationForPointOfSale(isEnabled) }, diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift index f9ec4f2cf57..8f649946752 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift @@ -103,6 +103,16 @@ final class HubMenuViewModel: ObservableObject { credentials: credentials, storage: storage) }() + + private(set) lazy var posCouponProvider: PointOfSaleItemServiceProtocol = { + let storage = ServiceLocator.storageManager + let currencySettings = ServiceLocator.currencySettings + + return PointOfSaleCouponService(siteID: siteID, + currencySettings: currencySettings, + credentials: credentials, + storage: storage) + }() private(set) lazy var inboxViewModel = InboxViewModel(siteID: siteID) From 2b30a41c110b7d6eb5b647c466324c8abecec659 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 11:00:00 +0700 Subject: [PATCH 14/21] Hook UI item type selection with switch in aggregate model --- .../POS/Models/PointOfSaleAggregateModel.swift | 5 +++++ .../Classes/POS/Presentation/ItemListView.swift | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 3929f396a90..9f1f7bcb44c 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -106,6 +106,11 @@ extension PointOfSaleAggregateModel { func loadNextItems(base: ItemListBaseItem) async { await itemsController.loadNextItems(base: base) } + + func switchToItemType(_ type: ItemType) { + // TODO: Switch between controllers controller: products or coupons + debugPrint("🍍 Switching to \(type)") + } } // MARK: - Cart diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index f1e6cc39ec2..06f2003345b 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -20,6 +20,8 @@ struct ItemListView: View { private var shouldShowCoupons: Bool { ServiceLocator.featureFlagService.isFeatureFlagEnabled(.enableCouponsInPointOfSale) } + + @State private var selectedItemType: ItemType = .products var body: some View { if #available(iOS 18.0, *) { @@ -41,12 +43,12 @@ struct ItemListView: View { HStack { Button(action: { - toggleItemType() + displayItemType(.products) }, label: { Text("Products") }) Button(action: { - toggleItemType() + displayItemType(.coupons) }, label: { Text("Coupons") }) @@ -160,8 +162,9 @@ private extension ItemListView { itemListState.eligibleToShowSimpleProductsBanner && !isHeaderBannerDismissed } - func toggleItemType() { - debugPrint("🍍 Toggle ItemType tapped") + func displayItemType(_ itemType: ItemType) { + selectedItemType = itemType + posModel.switchToItemType(itemType) } } From d8945f5c7b00a2230ce07de90263830ca22624d7 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 11:15:11 +0700 Subject: [PATCH 15/21] Inject couponsController in aggregate model and update item view state to use current controller --- .../Models/PointOfSaleAggregateModel.swift | 7 +++- .../CardReaderConnectionStatusView.swift | 1 + .../PointOfSalePaymentSuccessView.swift | 1 + .../Classes/POS/Presentation/CartView.swift | 2 + .../Item Selector/ChildItemList.swift | 2 + .../Presentation/Item Selector/ItemList.swift | 1 + .../POS/Presentation/ItemListView.swift | 2 + .../Presentation/POSFloatingControlView.swift | 3 ++ .../POS/Presentation/PaymentButtons.swift | 1 + .../PointOfSaleCollectCashView.swift | 1 + .../PointOfSaleDashboardView.swift | 2 + .../PointOfSaleEntryPointView.swift | 1 + .../Reusable Views/POSSendReceiptView.swift | 1 + .../Classes/POS/Presentation/TotalsView.swift | 1 + .../PointOfSaleAggregateModelTests.swift | 41 +++++++++++++++++++ 15 files changed, 66 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 9f1f7bcb44c..1360d653d94 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -52,14 +52,16 @@ protocol PointOfSaleAggregateModelProtocol { var cardPresentPaymentOnboardingViewModel: CardPresentPaymentsOnboardingViewModel? private var onOnboardingCancellation: (() -> Void)? - var itemsViewState: ItemsViewState { itemsController.itemsViewState } + var itemsViewState: ItemsViewState { currentController.itemsViewState } private(set) var cart: [CartItem] = [] var orderState: PointOfSaleOrderState { orderController.orderState.externalState } private var internalOrderState: PointOfSaleInternalOrderState { orderController.orderState } + private var currentController: PointOfSaleItemsControllerProtocol private let itemsController: PointOfSaleItemsControllerProtocol + private let couponsController: PointOfSaleItemsControllerProtocol private let cardPresentPaymentService: CardPresentPaymentFacade private let orderController: PointOfSaleOrderControllerProtocol @@ -72,12 +74,15 @@ protocol PointOfSaleAggregateModelProtocol { private var cancellables: Set = [] init(itemsController: PointOfSaleItemsControllerProtocol, + couponsController: PointOfSaleItemsControllerProtocol, cardPresentPaymentService: CardPresentPaymentFacade, orderController: PointOfSaleOrderControllerProtocol, analytics: Analytics = ServiceLocator.analytics, collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking, paymentState: PointOfSalePaymentState = .card(.idle)) { + self.currentController = itemsController // Default current controller set to products self.itemsController = itemsController + self.couponsController = couponsController self.cardPresentPaymentService = cardPresentPaymentService self.orderController = orderController self.analytics = analytics diff --git a/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift b/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift index 188b4166fd3..3995afdc807 100644 --- a/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift +++ b/WooCommerce/Classes/POS/Presentation/CardReaderConnection/CardReaderConnectionStatusView.swift @@ -171,6 +171,7 @@ private extension CardReaderConnectionStatusView { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics() diff --git a/WooCommerce/Classes/POS/Presentation/CardReaderConnection/UI States/Reader Messages/PointOfSalePaymentSuccessView.swift b/WooCommerce/Classes/POS/Presentation/CardReaderConnection/UI States/Reader Messages/PointOfSalePaymentSuccessView.swift index 5ca3d568be8..4badbe5dd11 100644 --- a/WooCommerce/Classes/POS/Presentation/CardReaderConnection/UI States/Reader Messages/PointOfSalePaymentSuccessView.swift +++ b/WooCommerce/Classes/POS/Presentation/CardReaderConnection/UI States/Reader Messages/PointOfSalePaymentSuccessView.swift @@ -125,6 +125,7 @@ private extension PointOfSalePaymentSuccessView { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/CartView.swift b/WooCommerce/Classes/POS/Presentation/CartView.swift index f4b6b43204b..3ef01a0c0c5 100644 --- a/WooCommerce/Classes/POS/Presentation/CartView.swift +++ b/WooCommerce/Classes/POS/Presentation/CartView.swift @@ -278,6 +278,7 @@ private extension CartView { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) @@ -289,6 +290,7 @@ private extension CartView { #Preview("Cart with one item") { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift index 2d70f9d11e0..c175229b059 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ChildItemList.swift @@ -147,6 +147,7 @@ private extension ChildItemList { ], hasMoreItems: false)])) let posModel = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) @@ -172,6 +173,7 @@ private extension ChildItemList { ])) let posModel = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift index 5ace62aee06..f128bcaa5b7 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift @@ -171,6 +171,7 @@ private extension ItemListRow { #Preview("Loading") { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 06f2003345b..62c7e2ab9de 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -250,6 +250,7 @@ private extension ItemListView { } let posModel = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) @@ -261,6 +262,7 @@ private extension ItemListView { #Preview("Loading") { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift b/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift index a3dcbcc9819..aee962956d1 100644 --- a/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift +++ b/WooCommerce/Classes/POS/Presentation/POSFloatingControlView.swift @@ -139,6 +139,7 @@ private extension POSFloatingControlView { #Preview("Reader Disconnected") { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) @@ -152,6 +153,7 @@ private extension POSFloatingControlView { let paymentService = CardPresentPaymentPreviewService() let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) @@ -165,6 +167,7 @@ private extension POSFloatingControlView { #Preview("Secondary/disabled Background") { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift b/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift index 156b880541f..dd454101a62 100644 --- a/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift +++ b/WooCommerce/Classes/POS/Presentation/PaymentButtons.swift @@ -90,6 +90,7 @@ private extension PaymentsActionButtons { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 3753e88e8c8..d674bc9875c 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -199,6 +199,7 @@ private extension PointOfSaleCollectCashView { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift index 843ed164271..a0fe45a0d66 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift @@ -191,6 +191,7 @@ private extension PointOfSaleDashboardView { #Preview("Container loading state") { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) @@ -206,6 +207,7 @@ private extension PointOfSaleDashboardView { let itemsController = PointOfSalePreviewItemsController() let posModel = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift index 3e15395fe1f..d7365c2677a 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift @@ -43,6 +43,7 @@ struct PointOfSaleEntryPointView: View { // See https://developer.apple.com/documentation/swiftui/state#Store-observable-objects for details. posModel = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: couponsController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker) diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift index 63ecc1ac9f6..899d7c43cce 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift @@ -160,6 +160,7 @@ private extension POSSendReceiptView { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/Classes/POS/Presentation/TotalsView.swift b/WooCommerce/Classes/POS/Presentation/TotalsView.swift index ae434d7e935..62c9b028a43 100644 --- a/WooCommerce/Classes/POS/Presentation/TotalsView.swift +++ b/WooCommerce/Classes/POS/Presentation/TotalsView.swift @@ -440,6 +440,7 @@ private extension TotalsView { #Preview { let posModel = PointOfSaleAggregateModel( itemsController: PointOfSalePreviewItemsController(), + couponsController: PointOfSalePreviewItemsController(), cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()) diff --git a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift index 8e6962e44a3..82e5b2a571a 100644 --- a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift @@ -13,6 +13,7 @@ struct PointOfSaleAggregateModelTests { @Test func inits_with_building_order_stage() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -24,6 +25,7 @@ struct PointOfSaleAggregateModelTests { @Test func startNewCart_removes_all_items_from_cart_and_moves_back_to_building() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -44,6 +46,7 @@ struct PointOfSaleAggregateModelTests { @Test func checkOut_moves_to_finalizing_order_stage() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -60,6 +63,7 @@ struct PointOfSaleAggregateModelTests { @Test func addMoreToCart_moves_to_building_order_stage() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -89,6 +93,7 @@ struct PointOfSaleAggregateModelTests { @Test func addItem_results_in_a_non_empty_cart() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: analytics, @@ -107,6 +112,7 @@ struct PointOfSaleAggregateModelTests { @Test func addItem_puts_new_items_first_in_the_cart() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: analytics, @@ -124,6 +130,7 @@ struct PointOfSaleAggregateModelTests { @Test func removeItem_after_adding_two_items_removes_item_correctly() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: analytics, @@ -148,6 +155,7 @@ struct PointOfSaleAggregateModelTests { @Test func removeAllItemsFromCart_removes_everything() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: analytics, @@ -176,6 +184,7 @@ struct PointOfSaleAggregateModelTests { func addToCart_tracks_analytics_event() async throws { // Given let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: analytics, @@ -205,6 +214,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -224,6 +234,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -246,6 +257,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -267,6 +279,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -288,6 +301,7 @@ struct PointOfSaleAggregateModelTests { // Given let orderController = MockPointOfSaleOrderController() let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -307,6 +321,7 @@ struct PointOfSaleAggregateModelTests { let expectedError = NSError(domain: "some error", code: -1) let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -328,6 +343,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -354,6 +370,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -368,6 +385,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker(), @@ -386,6 +404,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -406,6 +425,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker(), @@ -424,6 +444,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -447,6 +468,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -465,6 +487,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -482,6 +505,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -501,6 +525,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -530,6 +555,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -551,6 +577,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -578,6 +605,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -608,6 +636,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -637,6 +666,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -656,6 +686,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -675,6 +706,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -705,6 +737,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -730,6 +763,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: MockPOSCollectOrderPaymentAnalyticsTracker()) @@ -762,6 +796,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, analytics: analytics, @@ -788,6 +823,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, analytics: analytics, @@ -808,6 +844,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, analytics: analytics, @@ -826,6 +863,7 @@ struct PointOfSaleAggregateModelTests { let itemsController = MockPointOfSaleItemsController() let sut = PointOfSaleAggregateModel( itemsController: itemsController, + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, analytics: analytics, @@ -843,6 +881,7 @@ struct PointOfSaleAggregateModelTests { // Given let analyticsTracker = MockPOSCollectOrderPaymentAnalyticsTracker() let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, collectOrderPaymentAnalyticsTracker: analyticsTracker) @@ -859,6 +898,7 @@ struct PointOfSaleAggregateModelTests { // Given let analyticsTracker = MockPOSCollectOrderPaymentAnalyticsTracker() let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: analytics, @@ -876,6 +916,7 @@ struct PointOfSaleAggregateModelTests { let mockAnalyticsProvider = MockAnalyticsProvider() let mockAnalytics = WooAnalytics(analyticsProvider: mockAnalyticsProvider) let sut = PointOfSaleAggregateModel(itemsController: MockPointOfSaleItemsController(), + couponsController: MockPointOfSaleItemsController(), cardPresentPaymentService: MockCardPresentPaymentService(), orderController: MockPointOfSaleOrderController(), analytics: mockAnalytics, From 7462a042485475a2f813bfe1e107fd8cf4db4ddb Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 11:34:51 +0700 Subject: [PATCH 16/21] Hook switching current viewstate to different controller --- .../Controllers/PointOfSaleCouponsController.swift | 3 +++ .../POS/Models/PointOfSaleAggregateModel.swift | 13 +++++++------ .../Classes/POS/Presentation/ItemListView.swift | 4 +++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift index e33910195ee..f101b77884f 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift @@ -21,13 +21,16 @@ import protocol Yosemite.PointOfSaleItemServiceProtocol @MainActor func loadItems(base: ItemListBaseItem) async { debugPrint("🍍 CouponsController::loadItems called") + itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:])) } func refreshItems(base: ItemListBaseItem) async { debugPrint("🍍 CouponsController::refreshItems called") + itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:])) } func loadNextItems(base: ItemListBaseItem) async { debugPrint("🍍 CouponsController::loadNextItems called") + itemsViewState = ItemsViewState(containerState: .content, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:])) } } diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 1360d653d94..cbd21899dff 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -99,22 +99,23 @@ protocol PointOfSaleAggregateModelProtocol { extension PointOfSaleAggregateModel { @MainActor func loadItems(base: ItemListBaseItem) async { - await itemsController.loadItems(base: base) + await currentController.loadItems(base: base) } @MainActor func refreshItems(base: ItemListBaseItem) async { - await itemsController.refreshItems(base: base) + await currentController.refreshItems(base: base) } @MainActor func loadNextItems(base: ItemListBaseItem) async { - await itemsController.loadNextItems(base: base) + await currentController.loadNextItems(base: base) } - func switchToItemType(_ type: ItemType) { - // TODO: Switch between controllers controller: products or coupons - debugPrint("🍍 Switching to \(type)") + func switchToItemType(_ type: ItemType) async { + let newController = type == .products ? itemsController : couponsController + currentController = newController + await refreshItems(base: .root) } } diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 62c7e2ab9de..0e9f2c34df4 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -164,7 +164,9 @@ private extension ItemListView { func displayItemType(_ itemType: ItemType) { selectedItemType = itemType - posModel.switchToItemType(itemType) + Task { @MainActor in + await posModel.switchToItemType(itemType) + } } } From 4b070176360bead6aecae9af17042fe6d5332b0d Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 11:35:37 +0700 Subject: [PATCH 17/21] lint --- WooCommerce/Classes/POS/Presentation/ItemListView.swift | 2 +- .../Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index 0e9f2c34df4..cec11ae7185 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -20,7 +20,7 @@ struct ItemListView: View { private var shouldShowCoupons: Bool { ServiceLocator.featureFlagService.isFeatureFlagEnabled(.enableCouponsInPointOfSale) } - + @State private var selectedItemType: ItemType = .products var body: some View { diff --git a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift index 8f649946752..a826298e2a5 100644 --- a/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Hub Menu/HubMenuViewModel.swift @@ -103,11 +103,11 @@ final class HubMenuViewModel: ObservableObject { credentials: credentials, storage: storage) }() - + private(set) lazy var posCouponProvider: PointOfSaleItemServiceProtocol = { let storage = ServiceLocator.storageManager let currencySettings = ServiceLocator.currencySettings - + return PointOfSaleCouponService(siteID: siteID, currencySettings: currencySettings, credentials: credentials, From 9d73d73d54a4f4ab0876a5b59f1a297afc31ec39 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 11:41:47 +0700 Subject: [PATCH 18/21] Revert dummy CouponCardView It's unnecessary in this PR as we do not handle rendering from the new coupon service, neither adding to cart --- .../Item Selector/CouponCardView.swift | 17 ----------------- .../Presentation/Item Selector/ItemList.swift | 8 ++------ .../WooCommerce.xcodeproj/project.pbxproj | 4 ---- 3 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift deleted file mode 100644 index e5222469ae2..00000000000 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/CouponCardView.swift +++ /dev/null @@ -1,17 +0,0 @@ -import SwiftUI -import struct Yosemite.POSCoupon - -struct CouponCardView: View { - private let coupon: POSCoupon - - init(coupon: POSCoupon) { - self.coupon = coupon - } - - var body: some View { - HStack { - Text(coupon.id.uuidString) - Text(coupon.couponID.description) - } - } -} diff --git a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift index f128bcaa5b7..46af33e0d82 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Selector/ItemList.swift @@ -116,12 +116,8 @@ private struct ItemListRow: View { }, label: { VariationCardView(variation: variation) }) - case let .coupon(coupon): - Button(action: { - posModel.addToCart(item) - }, label: { - CouponCardView(coupon: coupon) - }) + case .coupon: + EmptyView() } } } diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index d54959aa7e8..4cd70adf476 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1590,7 +1590,6 @@ 68600A912C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68600A902C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift */; }; 68625DE62D4134D70042B231 /* DynamicVStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68625DE52D4134D50042B231 /* DynamicVStack.swift */; }; 68674D312B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68674D302B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift */; }; - 6868153B2D83F2A4006778A3 /* CouponCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6868153A2D83F2A2006778A3 /* CouponCardView.swift */; }; 68709D3D2A2ED94900A7FA6C /* UpgradesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68709D3C2A2ED94900A7FA6C /* UpgradesView.swift */; }; 68709D402A2EE2DC00A7FA6C /* UpgradesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68709D3F2A2EE2DC00A7FA6C /* UpgradesViewModel.swift */; }; 6879B8DB287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */; }; @@ -4768,7 +4767,6 @@ 68600A902C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleItemListEmptyView.swift; sourceTree = ""; }; 68625DE52D4134D50042B231 /* DynamicVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicVStack.swift; sourceTree = ""; }; 68674D302B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptEligibilityUseCaseTests.swift; sourceTree = ""; }; - 6868153A2D83F2A2006778A3 /* CouponCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponCardView.swift; sourceTree = ""; }; 68709D3C2A2ED94900A7FA6C /* UpgradesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradesView.swift; sourceTree = ""; }; 68709D3F2A2EE2DC00A7FA6C /* UpgradesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradesViewModel.swift; sourceTree = ""; }; 6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualsViewModelTests.swift; sourceTree = ""; }; @@ -7545,7 +7543,6 @@ 029149792D2682DF00F7B3B3 /* Item Selector */ = { isa = PBXGroup; children = ( - 6868153A2D83F2A2006778A3 /* CouponCardView.swift */, 0291497A2D2682FF00F7B3B3 /* ItemList.swift */, 20EFAEA52D35337F00D35F9C /* ItemListErrorCardView.swift */, 026826A42BF59DF60036F959 /* SimpleProductCardView.swift */, @@ -16280,7 +16277,6 @@ DED039292BC7A04B005D0571 /* StorePerformanceView.swift in Sources */, 03E471C4293A1F8D001A58AD /* BuiltInReaderConnectionAlertsProvider.swift in Sources */, 20CC1EDD2AFA99DF006BD429 /* InPersonPaymentsMenuViewModel.swift in Sources */, - 6868153B2D83F2A4006778A3 /* CouponCardView.swift in Sources */, 011DF3462C53A919000AFDD9 /* PointOfSaleCardPresentPaymentActivityIndicatingMessageView.swift in Sources */, D8736B7522F1FE1600A14A29 /* BadgeLabel.swift in Sources */, EE4C45652C352D60001A3D94 /* AIToneVoice.swift in Sources */, From f7161991a0327c8ffbb6e3447294ceb063e5dc9a Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 11:57:50 +0700 Subject: [PATCH 19/21] cleanup unused --- Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift | 2 -- Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift | 1 - 2 files changed, 3 deletions(-) diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift index d5106f7eee7..3b734e80cf6 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift @@ -73,8 +73,6 @@ public final class PointOfSaleCouponService: PointOfSaleItemServiceProtocol { @MainActor public func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems { - // Lands here when invoked from coupons controller -> Error, as won't show any in the view if we return empty. - //return .init(items: [], hasMorePages: false) let coupons = providePointOfSaleCoupons() return .init(items: coupons, hasMorePages: false) } diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift index ec4fd5fa065..5113f5aabae 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift @@ -12,7 +12,6 @@ import Storage public enum PointOfSaleItemServiceError: Error, Equatable { case requestFailed case requestCancelled - case storageFailure case unknown } From 5a26eb12bc30e1ec26cdb298546ebb8c1cbe3031 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 17:31:36 +0700 Subject: [PATCH 20/21] ammend merge conflict --- Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift b/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift index 3b734e80cf6..fb00c40250e 100644 --- a/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift +++ b/Yosemite/Yosemite/PointOfSale/PointOfSaleCouponService.swift @@ -62,7 +62,7 @@ public final class PointOfSaleCouponService: PointOfSaleItemServiceProtocol { private func mapCouponsToPOSItems(coupons: [Coupon]) -> [POSItem] { coupons.compactMap { coupon in - .coupon(POSCoupon(id: UUID(), couponID: coupon.couponID)) + .coupon(POSCoupon(id: UUID(), code: coupon.code)) } } From 249f75fe038b77b6f026966e1e01f7d758cc1368 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 25 Mar 2025 17:39:57 +0700 Subject: [PATCH 21/21] remove unnecessary prop --- .../Classes/POS/Controllers/PointOfSaleCouponsController.swift | 2 -- .../Classes/POS/Controllers/PointOfSaleItemsController.swift | 3 --- WooCommerce/Classes/POS/Utils/PreviewHelpers.swift | 2 -- .../POS/Mocks/MockPointOfSaleItemsService.swift | 1 - 4 files changed, 8 deletions(-) diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift index f101b77884f..9be0a37baae 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleCouponsController.swift @@ -4,8 +4,6 @@ import protocol Yosemite.PointOfSaleItemServiceProtocol @available(iOS 17.0, *) @Observable final class PointOfSaleCouponsController: PointOfSaleItemsControllerProtocol { - let itemType: ItemType = .coupons - var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) diff --git a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift index b146cb9e040..faf58ef0013 100644 --- a/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift +++ b/WooCommerce/Classes/POS/Controllers/PointOfSaleItemsController.swift @@ -13,8 +13,6 @@ enum ItemType { @available(iOS 17.0, *) protocol PointOfSaleItemsControllerProtocol { - /// - var itemType: ItemType { get } /// var itemsViewState: ItemsViewState { get } /// Loads the first page of items for a given base item. @@ -29,7 +27,6 @@ protocol PointOfSaleItemsControllerProtocol { @available(iOS 17.0, *) @Observable final class PointOfSaleItemsController: PointOfSaleItemsControllerProtocol { - let itemType: ItemType = .products var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index ea50477ff24..e05f6639d74 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -60,8 +60,6 @@ final class PointOfSalePreviewItemService: PointOfSaleItemServiceProtocol { @available(iOS 17.0, *) final class PointOfSalePreviewItemsController: PointOfSaleItemsControllerProtocol { - let itemType: ItemType = .products - @Published var itemsViewState: ItemsViewState = ItemsViewState(containerState: .loading, itemsStack: ItemsStackState(root: .loading([]), itemStates: [:])) diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift index 88d91e7ae62..d2a43c035d2 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockPointOfSaleItemsService.swift @@ -5,7 +5,6 @@ import enum Yosemite.POSItem @available(iOS 17.0, *) final class MockPointOfSaleItemsController: PointOfSaleItemsControllerProtocol { - let itemType: ItemType = .products var itemsViewState: ItemsViewState = .init(containerState: .empty, itemsStack: .init(root: .loaded([], hasMoreItems: false), itemStates: [:]))