-
Notifications
You must be signed in to change notification settings - Fork 121
[Woo POS] Coupons: Add Coupon(s) to Cart (dummy UI) #15407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
3b1754f
d3d1f1a
f293ea4
34bea54
11e49a9
d5563c2
fac9b49
61e3423
94d203a
d888004
26b4ece
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import Foundation | ||
| import protocol Yosemite.POSOrderableItem | ||
| import enum Yosemite.POSItem | ||
|
|
||
| struct Cart { | ||
| var items: [CartProductItem] = [] | ||
| var coupons: [CartCouponItem] = [] | ||
| } | ||
|
|
||
| protocol CartItem: Identifiable { | ||
| var id: UUID { get } | ||
| } | ||
|
|
||
| struct CartProductItem: CartItem { | ||
| let id: UUID | ||
| let item: POSOrderableItem | ||
| let title: String | ||
| let subtitle: String? | ||
| let quantity: Int | ||
| } | ||
|
|
||
| struct CartCouponItem: CartItem { | ||
| let id: UUID | ||
| let code: String | ||
| } | ||
|
|
||
| // MARK: - Helper Methods | ||
|
|
||
| extension Cart { | ||
| mutating func add(_ posItem: POSItem) { | ||
| switch posItem { | ||
| case .simpleProduct(let simpleProduct): | ||
| let productItem = CartProductItem(id: UUID(), item: simpleProduct, title: simpleProduct.name, subtitle: nil, quantity: 1) | ||
| items.insert(productItem, at: items.startIndex) | ||
| case .variation(let variation): | ||
| let productItem = CartProductItem(id: UUID(), item: variation, title: variation.parentProductName, subtitle: variation.name, quantity: 1) | ||
| items.insert(productItem, at: items.startIndex) | ||
| case .variableParentProduct: | ||
| return | ||
| case .coupon(let coupon): | ||
| let couponItem = CartCouponItem(id: UUID(), code: coupon.code) | ||
| coupons.insert(couponItem, at: coupons.startIndex) | ||
| } | ||
| } | ||
|
|
||
| mutating func remove(_ cartItem: any CartItem) { | ||
| switch cartItem { | ||
| case _ as CartProductItem: | ||
| items.removeAll { $0.id == cartItem.id } | ||
| case _ as CartCouponItem: | ||
| coupons.removeAll { $0.id == cartItem.id } | ||
| default: | ||
| break | ||
| } | ||
| } | ||
|
|
||
| mutating func removeAll() { | ||
| items.removeAll() | ||
| coupons.removeAll() | ||
| } | ||
|
|
||
| var isEmpty: Bool { | ||
| items.isEmpty && coupons.isEmpty | ||
| } | ||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ struct CartView: View { | |
| @State private var offSetPosition: CGFloat = 0.0 | ||
| private var coordinateSpace: CoordinateSpace = .named(Constants.scrollViewCoordinateSpaceIdentifier) | ||
| private var shouldApplyHeaderBottomShadow: Bool { | ||
| posModel.cart.isNotEmpty && offSetPosition < 0 | ||
| !posModel.cart.isEmpty && offSetPosition < 0 | ||
|
||
| } | ||
|
|
||
| @State private var shouldShowItemImages: Bool = false | ||
|
|
@@ -22,7 +22,7 @@ struct CartView: View { | |
| backButtonConfiguration: backButtonConfiguration, | ||
| trailingContent: { | ||
| DynamicHStack(horizontalAlignment: .trailing, verticalAlignment: .center, spacing: Constants.cartHeaderElementSpacing) { | ||
| if let itemsInCartLabel = viewHelper.itemsInCartLabel(for: posModel.cart.count) { | ||
| if let itemsInCartLabel = viewHelper.itemsInCartLabel(for: posModel.cart.items.count) { | ||
| Text(itemsInCartLabel) | ||
| .font(Constants.itemsFont) | ||
| .lineLimit(1) | ||
|
|
@@ -43,11 +43,26 @@ struct CartView: View { | |
| }) | ||
| .if(shouldApplyHeaderBottomShadow, transform: { $0.applyBottomShadow(backgroundColor: backgroundColor) }) | ||
|
|
||
| if posModel.cart.isNotEmpty { | ||
| if !posModel.cart.isEmpty { | ||
| ScrollViewReader { proxy in | ||
| ScrollView { | ||
| VStack(spacing: Constants.cartItemSpacing) { | ||
| ForEach(posModel.cart, id: \.id) { cartItem in | ||
|
|
||
| /// WIP: Behind the feature flag | ||
| if posModel.cart.coupons.isNotEmpty { | ||
| ForEach(posModel.cart.coupons, id: \.id) { couponItem in | ||
| CouponRowView(couponItem: couponItem, | ||
| onItemRemoveTapped: posModel.orderStage == .building ? { | ||
| posModel.remove(cartItem: couponItem) | ||
| } : nil) | ||
| .id(couponItem.id) | ||
| .transition(.opacity) | ||
| } | ||
|
|
||
| Spacer(minLength: 64) | ||
| } | ||
|
||
|
|
||
| ForEach(posModel.cart.items, id: \.id) { cartItem in | ||
| ItemRowView(cartItem: cartItem, | ||
| showImage: $shouldShowItemImages, | ||
| onItemRemoveTapped: posModel.orderStage == .building ? { | ||
|
|
@@ -58,7 +73,8 @@ struct CartView: View { | |
| .transition(.opacity) | ||
| } | ||
| } | ||
| .animation(Constants.cartAnimation, value: posModel.cart.map(\.id)) | ||
| .animation(Constants.cartAnimation, value: posModel.cart.items.map(\.id)) | ||
| .animation(Constants.cartAnimation, value: posModel.cart.coupons.map(\.id)) | ||
| .background(GeometryReader { geometry in | ||
| Color.clear.preference(key: ScrollOffSetPreferenceKey.self, | ||
| value: geometry.frame(in: coordinateSpace).origin.y) | ||
|
|
@@ -81,7 +97,7 @@ struct CartView: View { | |
| .renderedIf(posModel.orderStage == .finalizing) | ||
| } | ||
| .coordinateSpace(name: Constants.scrollViewCoordinateSpaceIdentifier) | ||
| .onChange(of: posModel.cart.first?.id) { itemToScrollTo in | ||
| .onChange(of: posModel.cart.items.first?.id) { itemToScrollTo in | ||
| if posModel.orderStage == .building { | ||
| withAnimation { | ||
| proxy.scrollTo(itemToScrollTo) | ||
|
|
@@ -268,7 +284,7 @@ private extension CartView { | |
| @available(iOS 17.0, *) | ||
| private extension CartView { | ||
| func trackCheckoutTapped() { | ||
| let itemsInCart = posModel.cart.count | ||
| let itemsInCart = posModel.cart.items.count | ||
| ServiceLocator.analytics.track(event: .PointOfSale.checkoutTapped(itemsInCart)) | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import SwiftUI | ||
|
|
||
| struct CouponRowView: View { | ||
| private let couponItem: CartCouponItem | ||
| private let onItemRemoveTapped: (() -> Void)? | ||
|
|
||
| @ScaledMetric private var scale: CGFloat = 1.0 | ||
|
|
||
| init(couponItem: CartCouponItem, onItemRemoveTapped: (() -> Void)? = nil) { | ||
| self.couponItem = couponItem | ||
| self.onItemRemoveTapped = onItemRemoveTapped | ||
| } | ||
|
|
||
| var body: some View { | ||
| HStack(spacing: Constants.horizontalElementSpacing) { | ||
| Rectangle() | ||
| .foregroundColor(.posSurfaceDim) | ||
| .overlay { | ||
| Text(Image(systemName: "tag.square.fill")) | ||
| .font(.posButtonSymbolLarge) | ||
| .foregroundColor(.posOnSurfaceVariantLowest) | ||
| } | ||
| .frame(width: Constants.couponCardSize, height: Constants.couponCardSize) | ||
|
|
||
| VStack(alignment: .leading) { | ||
| Text(couponItem.code) | ||
| .foregroundColor(PointOfSaleItemListCardConstants.titleColor) | ||
| .font(Constants.itemTitleFont) | ||
| } | ||
| .frame(maxWidth: .infinity, alignment: .leading) | ||
|
|
||
| if let onItemRemoveTapped { | ||
| Button(action: { | ||
| onItemRemoveTapped() | ||
| }, label: { | ||
| Text(Image(systemName: "xmark.circle")) | ||
| .font(.posButtonSymbolMedium) | ||
| }) | ||
| .foregroundColor(Color.posOnSurfaceVariantLowest) | ||
| } | ||
| } | ||
| .padding(.trailing, Constants.cardContentHorizontalPadding) | ||
| .frame(maxWidth: .infinity, idealHeight: Constants.couponCardSize * scale) | ||
| .background(Color.posSurfaceContainerLowest) | ||
| .posItemCardBorderStyles() | ||
| .padding(.horizontal, Constants.horizontalPadding) | ||
| } | ||
| } | ||
|
|
||
| private extension CouponRowView { | ||
| enum Constants { | ||
| static let couponCardSize: CGFloat = 96 | ||
| static let horizontalPadding: CGFloat = POSPadding.medium | ||
| static let horizontalElementSpacing: CGFloat = POSSpacing.medium | ||
| static let cardContentHorizontalPadding: CGFloat = POSPadding.medium | ||
| static let itemTitleFont: POSFontStyle = .posBodySmallBold | ||
| } | ||
| } | ||
|
|
||
| #if DEBUG | ||
| @available(iOS 17.0, *) | ||
| #Preview(traits: .sizeThatFitsLayout) { | ||
| CouponRowView(couponItem: CartCouponItem(id: UUID(), code: "10-Discount")) {} | ||
| } | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the
Cartstruct, should we update the name ofitemstoproductssince we're starting to be more specific about the type?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I'm confused about this as well 😀 I chose this name only because we refer to products as items within the
Orderobject. But then it's a how to call the type. Maybe I should renameCardProductItemtoCartItemagain, and remove theprotocol CartItemsince its value is questionable. Or call thisCartItemProtocol🤔