Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
Expand Up @@ -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 }
Expand All @@ -15,6 +20,8 @@ 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, *)
Expand All @@ -25,6 +32,7 @@ protocol PointOfSaleItemsControllerProtocol {
private let paginationTracker: AsyncPaginationTracker
private var childPaginationTrackers: [POSItem: AsyncPaginationTracker] = [:]
private let itemProvider: PointOfSaleItemServiceProtocol
private var itemType: POSItemType = .products
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on a discussion p1742206171389629/1741916795.290219-slack-C070SJRA8DP, we could also consider how ItemListState could be used to represent the Products | Coupons data structure.

Maybe something like this? 🤔

image

The current selection could be tracked on a state or separately.


init(itemProvider: PointOfSaleItemServiceProtocol) {
self.itemProvider = itemProvider
Expand All @@ -42,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 {
Expand Down Expand Up @@ -158,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) {
Expand Down Expand Up @@ -201,7 +206,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 == .products
? try await itemProvider.providePointOfSaleItems(pageNumber: pageNumber)
: try itemProvider.providePointOfSaleCoupons()

let newItems = pagedItems.items
var allItems = appendToExistingItems ? itemsViewState.itemsStack.root.items : []
let uniqueNewItems = newItems.filter { newItem in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ 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)
Expand Down Expand Up @@ -120,6 +125,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
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,12 @@ private struct ItemListRow: View {
}, label: {
VariationCardView(variation: variation)
})
case .coupon:
EmptyView()
case let .coupon(coupon):
Button(action: {
posModel.addToCart(item)
}, label: {
CouponCardView(coupon: coupon)
})
}
}
}
Expand Down
26 changes: 26 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,10 @@ struct ItemListView: View {
@AppStorage(BannerState.isSimpleProductsOnlyBannerDismissedKey)
private var isHeaderBannerDismissed: Bool = false

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

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

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

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

func toggleItemType() {
Task {
await posModel.toggleItemType()
}
}
}

private extension ItemListState {
Expand Down
8 changes: 6 additions & 2 deletions WooCommerce/Classes/POS/Utils/PreviewHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ final class PointOfSalePreviewItemService: PointOfSaleItemServiceProtocol {
formattedPrice: "$1.00")
}

func providePointOfSaleCoupons() -> [POSItem] {
[]
func providePointOfSaleCoupons() throws -> PagedItems<POSItem> {
.init(items: [], hasMorePages: true)
}
}

Expand Down Expand Up @@ -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] {
Expand Down
4 changes: 4 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -4752,6 +4753,7 @@
68600A902C65BC9C00252EDD /* PointOfSaleItemListEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleItemListEmptyView.swift; sourceTree = "<group>"; };
68625DE52D4134D50042B231 /* DynamicVStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicVStack.swift; sourceTree = "<group>"; };
68674D302B6C895D00E93FBD /* ReceiptEligibilityUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptEligibilityUseCaseTests.swift; sourceTree = "<group>"; };
6868153A2D83F2A2006778A3 /* CouponCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponCardView.swift; sourceTree = "<group>"; };
68709D3C2A2ED94900A7FA6C /* UpgradesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradesView.swift; sourceTree = "<group>"; };
68709D3F2A2EE2DC00A7FA6C /* UpgradesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradesViewModel.swift; sourceTree = "<group>"; };
6879B8DA287AFFA100A0F9A8 /* CardReaderManualsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualsViewModelTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7513,6 +7515,7 @@
029149792D2682DF00F7B3B3 /* Item Selector */ = {
isa = PBXGroup;
children = (
6868153A2D83F2A2006778A3 /* CouponCardView.swift */,
0291497A2D2682FF00F7B3B3 /* ItemList.swift */,
20EFAEA52D35337F00D35F9C /* ItemListErrorCardView.swift */,
026826A42BF59DF60036F959 /* SimpleProductCardView.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ final class MockPointOfSaleItemService: PointOfSaleItemServiceProtocol {
return .init(items: MockPointOfSaleItemService.makeInitialVariationItems(), hasMorePages: shouldSimulateTwoPagesOfVariations)
}

func providePointOfSaleCoupons() -> [Yosemite.POSItem] {
[]
func providePointOfSaleCoupons() throws -> PagedItems<POSItem> {
.init(items: [], hasMorePages: true)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ final class MockPointOfSaleItemsController: PointOfSaleItemsControllerProtocol {
func refreshItems(base: WooCommerce.ItemListBaseItem) async { }

func loadNextItems(base: ItemListBaseItem) async { }

func toggleItemType() async { }
}
19 changes: 7 additions & 12 deletions Yosemite/Yosemite/PointOfSale/PointOfSaleItemService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Storage
public enum PointOfSaleItemServiceError: Error, Equatable {
case requestFailed
case requestCancelled
case storageFailure
case unknown
}

Expand Down Expand Up @@ -99,11 +100,9 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol {
}
}

// TODO:
// gh-15326 - Return PagedItems<POSItem> instead.
public func providePointOfSaleCoupons() -> [POSItem] {
public func providePointOfSaleCoupons() throws -> PagedItems<POSItem> {
guard let storage = storage else {
return []
throw PointOfSaleItemServiceError.storageFailure
}
let predicate = NSPredicate(format: "siteID == %lld", siteID)
let descriptor = NSSortDescriptor(keyPath: \StorageCoupon.dateCreated,
Expand All @@ -113,14 +112,10 @@ public final class PointOfSaleItemService: PointOfSaleItemServiceProtocol {
matching: predicate,
sortedBy: [descriptor])

do {
try resultsController.performFetch()
let storeCoupons = resultsController.fetchedObjects
return mapCouponsToPOSItems(coupons: storeCoupons)
} catch {
debugPrint(error)
return []
}
try resultsController.performFetch()
let storeCoupons = resultsController.fetchedObjects
let posCoupons = mapCouponsToPOSItems(coupons: storeCoupons)
return .init(items: posCoupons, hasMorePages: false)
}

private func mapCouponsToPOSItems(coupons: [Coupon]) -> [POSItem] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public extension Sequence where Element == POSOrderableItem {
public protocol PointOfSaleItemServiceProtocol {
func providePointOfSaleItems(pageNumber: Int) async throws -> PagedItems<POSItem>
func providePointOfSaleVariationItems(for parentProduct: POSVariableParentProduct, pageNumber: Int) async throws -> PagedItems<POSItem>
func providePointOfSaleCoupons() -> [POSItem]
func providePointOfSaleCoupons() throws -> PagedItems<POSItem>
}

// Default implementation for convenience, so we do not need to pass the first page explicitly
Expand Down
Loading