Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
18c84df
Return coupons as PagedItems, hook to item list
iamgabrielma Mar 14, 2025
b888782
Make providePointOfSaleCoupons throwable
iamgabrielma Mar 14, 2025
a37563b
switch coupons feature flag to false
iamgabrielma Mar 14, 2025
35d6193
Make CouponCardView
iamgabrielma Mar 14, 2025
7d9305f
Remove unnecessary state. Collapse func duplication
iamgabrielma Mar 14, 2025
9f5c228
lint
iamgabrielma Mar 14, 2025
6d7dfa9
Invert fetch logic, make products first case
iamgabrielma Mar 14, 2025
b39973e
lint
iamgabrielma Mar 14, 2025
a5f58da
make test target compile
iamgabrielma Mar 14, 2025
e8fba94
Merge branch 'trunk' into task/15346-pos-allow-switch-product-vs-coup…
iamgabrielma Mar 25, 2025
9523774
Make PointOfSaleCouponsController
iamgabrielma Mar 25, 2025
a10bdf1
Create PointOfSaleCouponService
iamgabrielma Mar 25, 2025
ad84397
Delete coupon-specific logic from itemservice
iamgabrielma Mar 25, 2025
1aa3efc
Inject couponsController into POS entry point
iamgabrielma Mar 25, 2025
2b30a41
Hook UI item type selection with switch in aggregate model
iamgabrielma Mar 25, 2025
d8945f5
Inject couponsController in aggregate model and update item view stat…
iamgabrielma Mar 25, 2025
7462a04
Hook switching current viewstate to different controller
iamgabrielma Mar 25, 2025
4b07017
lint
iamgabrielma Mar 25, 2025
9d73d73
Revert dummy CouponCardView
iamgabrielma Mar 25, 2025
f716199
cleanup unused
iamgabrielma Mar 25, 2025
7417b66
Merge branch 'trunk' into task/15346-pos-allow-switch-product-vs-coup…
iamgabrielma Mar 25, 2025
5a26eb1
ammend merge conflict
iamgabrielma Mar 25, 2025
249f75f
remove unnecessary prop
iamgabrielma Mar 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Experiments/Experiments/DefaultFeatureFlagService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Observation
import enum Yosemite.POSItem
import protocol Yosemite.PointOfSaleItemServiceProtocol

@available(iOS 17.0, *)
@Observable final class PointOfSaleCouponsController: PointOfSaleItemsControllerProtocol {
let itemType: ItemType = .coupons
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ItemType supposed to be used in Controllers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem necessary, nope. Removed! 249f75f


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")
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: [:]))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ import enum Yosemite.PointOfSaleItemServiceError
import struct Yosemite.POSVariableParentProduct
import class Yosemite.Store

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
Expand All @@ -17,8 +25,11 @@ protocol PointOfSaleItemsControllerProtocol {
func loadNextItems(base: ItemListBaseItem) 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: [:]))
Expand Down Expand Up @@ -158,14 +169,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) {
Expand Down Expand Up @@ -202,6 +205,7 @@ private extension PointOfSaleItemsController {
func fetchItems(pageNumber: Int, appendToExistingItems: Bool = true) async throws -> Bool {
do {
let pagedItems = try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber)

let newItems = pagedItems.items
var allItems = appendToExistingItems ? itemsViewState.itemsStack.root.items : []
let uniqueNewItems = newItems.filter { newItem in
Expand Down
20 changes: 16 additions & 4 deletions WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -72,12 +74,15 @@ protocol PointOfSaleAggregateModelProtocol {
private var cancellables: Set<AnyCancellable> = []

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
Expand All @@ -94,17 +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) async {
let newController = type == .products ? itemsController : couponsController
currentController = newController
await refreshItems(base: .root)
}
}

Expand All @@ -120,6 +131,7 @@ private extension POSItem {
case .variableParentProduct:
return nil
case .coupon:
debugPrint("Not implemented. TODO: Make POSCoupon POSOrderable")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: May not need to be POSOrderable, since for coupons it's enough to pass code to the API.

return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ private extension CardReaderConnectionStatusView {
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ private extension PointOfSalePaymentSuccessView {
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
2 changes: 2 additions & 0 deletions WooCommerce/Classes/POS/Presentation/CartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ private extension CartView {
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand All @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ private extension ChildItemList {
], hasMoreItems: false)]))
let posModel = PointOfSaleAggregateModel(
itemsController: itemsController,
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand All @@ -172,6 +173,7 @@ private extension ChildItemList {
]))
let posModel = PointOfSaleAggregateModel(
itemsController: itemsController,
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ private extension ItemListRow {
#Preview("Loading") {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
31 changes: 31 additions & 0 deletions WooCommerce/Classes/POS/Presentation/ItemListView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import SwiftUI
import enum Yosemite.POSItem
import protocol Yosemite.POSOrderableItem
import struct Yosemite.POSCoupon

@available(iOS 17.0, *)
struct ItemListView: View {
Expand All @@ -16,6 +17,12 @@ struct ItemListView: View {
@AppStorage(BannerState.isSimpleProductsOnlyBannerDismissedKey)
private var isHeaderBannerDismissed: Bool = false

private var shouldShowCoupons: Bool {
ServiceLocator.featureFlagService.isFeatureFlagEnabled(.enableCouponsInPointOfSale)
}

@State private var selectedItemType: ItemType = .products

var body: some View {
if #available(iOS 18.0, *) {
NavigationStack {
Expand All @@ -33,6 +40,21 @@ struct ItemListView: View {
var content: some View {
VStack {
headerView

HStack {
Button(action: {
displayItemType(.products)
}, label: {
Text("Products")
})
Button(action: {
displayItemType(.coupons)
}, label: {
Text("Coupons")
})
}
.renderedIf(shouldShowCoupons)

switch itemListState {
case .loading(let items),
.loaded(let items, _),
Expand Down Expand Up @@ -139,6 +161,13 @@ private extension ItemListView {
var shouldShowHeaderBanner: Bool {
itemListState.eligibleToShowSimpleProductsBanner && !isHeaderBannerDismissed
}

func displayItemType(_ itemType: ItemType) {
selectedItemType = itemType
Task { @MainActor in
await posModel.switchToItemType(itemType)
}
}
}

private extension ItemListState {
Expand Down Expand Up @@ -223,6 +252,7 @@ private extension ItemListView {
}
let posModel = PointOfSaleAggregateModel(
itemsController: itemsController,
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand All @@ -234,6 +264,7 @@ private extension ItemListView {
#Preview("Loading") {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ private extension POSFloatingControlView {
#Preview("Reader Disconnected") {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand All @@ -152,6 +153,7 @@ private extension POSFloatingControlView {
let paymentService = CardPresentPaymentPreviewService()
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand All @@ -165,6 +167,7 @@ private extension POSFloatingControlView {
#Preview("Secondary/disabled Background") {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
1 change: 1 addition & 0 deletions WooCommerce/Classes/POS/Presentation/PaymentButtons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ private extension PaymentsActionButtons {
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ private extension PointOfSaleCollectCashView {
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ private extension PointOfSaleDashboardView {
#Preview("Container loading state") {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand All @@ -206,6 +207,7 @@ private extension PointOfSaleDashboardView {
let itemsController = PointOfSalePreviewItemsController()
let posModel = PointOfSaleAggregateModel(
itemsController: itemsController,
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ 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,
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking) {
self.onPointOfSaleModeActiveStateChange = onPointOfSaleModeActiveStateChange

self.itemsController = itemsController
self.couponsController = couponsController
self.cardPresentPaymentService = cardPresentPaymentService
self.orderController = orderController
self.collectOrderPaymentAnalyticsTracker = collectOrderPaymentAnalyticsTracker
Expand All @@ -40,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)
Expand All @@ -60,6 +64,7 @@ struct PointOfSaleEntryPointView: View {
@available(iOS 17.0, *)
#Preview {
PointOfSaleEntryPointView(itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
onPointOfSaleModeActiveStateChange: { _ in },
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ private extension POSSendReceiptView {
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
couponsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController(),
collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalytics())
Expand Down
Loading