Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,29 @@ extension WooAnalyticsEvent {
}

static func addItemToCart(
sourceView: WooAnalyticsEvent.PointOfSale.SourceView,
sourceView: WooAnalyticsEvent.PointOfSale.SourceView? = nil,
sourceViewType: WooAnalyticsEvent.PointOfSale.SourceViewType,
itemType: WooAnalyticsEvent.PointOfSale.ItemType,
productType: WooAnalyticsEvent.PointOfSale.CartItemProductType? = nil
productType: WooAnalyticsEvent.PointOfSale.CartItemProductType? = nil,
error: Error? = nil
) -> WooAnalyticsEvent {
var properties: [String: String] = [
Key.sourceView: sourceView.rawValue,
Key.sourceViewType: sourceViewType.rawValue,
Key.itemType: itemType.rawValue
]

if let sourceView {
properties[Key.sourceView] = sourceView.rawValue
}

if let productType {
properties[Key.productType] = productType.rawValue
}

return WooAnalyticsEvent(
statName: .pointOfSaleAddItemToCart,
properties: properties
properties: properties,
error: error
)
}

Expand Down Expand Up @@ -217,6 +222,7 @@ extension WooAnalyticsEvent.PointOfSale {
case list
case search
case preSearch = "pre_search"
case scanner

init(isSearching: Bool, searchTerm: String = "") {
switch (isSearching, searchTerm.isEmpty) {
Expand All @@ -235,6 +241,19 @@ extension WooAnalyticsEvent.PointOfSale {
enum ItemType: String {
case product
case coupon
case loading
case error

init(cartItem: Cart.PurchasableItem) {
switch cartItem.state {
case .loaded:
self = .product
case .loading:
self = .loading
case .error:
self = .error
}
}
}

/// Types of products supported in the POS
Expand Down
19 changes: 13 additions & 6 deletions WooCommerce/Classes/POS/Models/Cart+BarcodeScanError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import Foundation
import enum Yosemite.PointOfSaleBarcodeScanError

extension Cart {
mutating func updateLoadingItem(id: UUID, with error: PointOfSaleBarcodeScanError) {
guard let index = purchasableItems.firstIndex(where: { $0.id == id }) else { return }
@discardableResult
mutating func updateLoadingItem(id: UUID, with error: PointOfSaleBarcodeScanError) -> Cart.PurchasableItem? {
guard let index = purchasableItems.firstIndex(where: { $0.id == id }) else { return nil }

purchasableItems[index] = Cart.PurchasableItem(
id: id,
Expand All @@ -12,6 +13,8 @@ extension Cart {
quantity: 1,
state: .error
)

return purchasableItems[index]
}

private func title(for error: PointOfSaleBarcodeScanError) -> String {
Expand All @@ -30,7 +33,13 @@ extension Cart {
}

private func subtitle(for error: PointOfSaleBarcodeScanError) -> String {
switch error {
return error.localizedDescription
}
}

extension PointOfSaleBarcodeScanError {
var localizedDescription: String {
switch self {
case .notFound, .unknown:
return Localization.notFound
case .downloadableProduct, .unsupportedProductType:
Expand All @@ -45,10 +54,8 @@ extension Cart {
}
}
}
}

private extension Cart {
enum Localization {
private enum Localization {
static let notFound = NSLocalizedString(
"pointOfSale.barcodeScan.error.notFound",
value: "Unknown scanned item",
Expand Down
11 changes: 7 additions & 4 deletions WooCommerce/Classes/POS/Models/Cart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,23 @@ extension Cart {
}
}

mutating func addLoadingItem() -> UUID {
mutating func addLoadingItem() -> Cart.PurchasableItem {
let id = UUID()
let loadingItem = PurchasableItem.loading(id: id)
purchasableItems.insert(loadingItem, at: purchasableItems.startIndex)
return id
return loadingItem
}

mutating func updateLoadingItem(id: UUID, with posItem: POSItem) {
guard let index = purchasableItems.firstIndex(where: { $0.id == id }) else { return }
@discardableResult
mutating func updateLoadingItem(id: UUID, with posItem: POSItem) -> Cart.PurchasableItem? {
guard let index = purchasableItems.firstIndex(where: { $0.id == id }) else { return nil }

if let productItem = createPurchasableItem(id: id, from: posItem) {
purchasableItems[index] = productItem
return productItem
} else {
purchasableItems.remove(at: index)
return nil
}
}

Expand Down
34 changes: 30 additions & 4 deletions WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,40 @@ extension PointOfSaleAggregateModel {
extension PointOfSaleAggregateModel {
func barcodeScanned(_ barcode: String) {
Task {
let placeholderItemID = cart.addLoadingItem()
let placeholderItemID = cart.addLoadingItem().id

analytics.track(
event: .PointOfSale.addItemToCart(
sourceViewType: .scanner,
itemType: .loading
)
)

do throws(PointOfSaleBarcodeScanError) {
let item = try await barcodeScanService.getItem(barcode: barcode)
cart.updateLoadingItem(id: placeholderItemID, with: item)
if let cartItem = cart.updateLoadingItem(id: placeholderItemID, with: item) {
analytics.track(
event: .PointOfSale.addItemToCart(
sourceViewType: .scanner,
itemType: .product,
productType: .init(cartItem: cartItem)
)
)
}
} catch {
DDLogInfo("Failed to find item by barcode: \(error)")
cart.updateLoadingItem(id: placeholderItemID, with: error)
await soundPlayer.playSound(.barcodeScanFailure)
if let _ = cart.updateLoadingItem(id: placeholderItemID, with: error) {
// Only play a sound and track analytics if the item still exists in the cart.
await soundPlayer.playSound(.barcodeScanFailure)

analytics.track(
event: .PointOfSale.addItemToCart(
sourceViewType: .scanner,
itemType: .error,
error: error
)
)
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion WooCommerce/Classes/POS/Presentation/CartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,19 @@ struct CartView: View {
ServiceLocator.analytics.track(
event: .PointOfSale.itemRemovedFromCart(
sourceView: .cart,
itemType: .product,
itemType: .init(cartItem: cartItem),
productType: .init(cartItem: cartItem)
)
)
posModel.remove(cartItem: cartItem)
} : nil,
onCancelLoading: {
ServiceLocator.analytics.track(
event: .PointOfSale.itemRemovedFromCart(
sourceView: .cart,
itemType: .loading
)
)
posModel.cancelLoadingItem(id: cartItem.id)
})
.id(cartItem.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ struct PointOfSaleAggregateModelTests {
try #require(cart.purchasableItems.isEmpty)

// When
let id = cart.addLoadingItem()
let loadingItem = cart.addLoadingItem()

// Then
#expect(cart.purchasableItems.count == 1)
let item = try #require(cart.purchasableItems.first)
#expect(item.id == id)
#expect(item.id == loadingItem.id)
guard case .loading = item.state else {
throw CartTestError.unexpectedItemStateInCart
}
Expand All @@ -140,11 +140,11 @@ struct PointOfSaleAggregateModelTests {
@Test func updateLoadingItem_updates_loading_item_with_simple_product() async throws {
// Given
var cart = Cart()
let id = cart.addLoadingItem()
let loadingItem = cart.addLoadingItem()
let purchasableItem = makePurchasableItem(name: "Test Product")

// When
cart.updateLoadingItem(id: id, with: purchasableItem)
cart.updateLoadingItem(id: loadingItem.id, with: purchasableItem)

// Then
#expect(cart.purchasableItems.count == 1)
Expand Down