Skip to content
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

[Woo POS][Cash & Receipts] Render cash button and navigate to cash view #14724

Merged
merged 16 commits into from
Jan 6, 2025
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
138 changes: 138 additions & 0 deletions WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import SwiftUI

struct PointOfSaleCollectCashView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var posModel: PointOfSaleAggregateModel

@State private var textFieldAmountInput: String = ""
@State private var isLoading: Bool = false
@State private var errorMessage: String?

let orderTotal: String

private var formattedOrderTotal: String {
String.localizedStringWithFormat(Localization.backNavigationSubtitle, orderTotal)
}

var body: some View {
VStack(alignment: .center, spacing: 20) {
HStack {
Button(action: {
dismiss()
}, label: {
VStack {
HStack {
Image(systemName: "chevron.left")
Text(Localization.backNavigationTitle)
}
.font(.posTitleRegular)
.bold()
.foregroundColor(.primary)

Text(formattedOrderTotal)
.font(.posBodyRegular)
.foregroundColor(.primary)
}
})
Spacer()
}
.padding()

TextField("$0.00", text: $textFieldAmountInput)
.keyboardType(.numbersAndPunctuation)
.textInputAutocapitalization(.none)
.autocorrectionDisabled()
.multilineTextAlignment(.center)
.font(POSFontStyle.posTitleRegular)
.focused()
.padding()
.onSubmit {
Task { @MainActor in
await markComplete()
}
}

if let errorMessage = errorMessage {
Text(errorMessage)
.font(POSFontStyle.posBodyRegular)
.foregroundColor(.red)
}

Button(action: {
Task { @MainActor in
await markComplete()
}
}, label: {
HStack(spacing: Constants.buttonSpacing) {
if isLoading {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.tint(Color.posPrimaryTextInverted)
} else {
Text(Localization.markPaymentCompletedButtonTitle)
.font(Constants.buttonFont)
}
}
.frame(maxWidth: .infinity)
})
Comment on lines +61 to +77
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps another place where an AsyncButton would be good? It'd need a binding so that the text field's onSubmit also triggered the progress view, but that seems like a useful option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree! I'll make the change with #14602

.padding(Constants.buttonPadding)
.frame(maxWidth: .infinity)
.foregroundColor(Color.posPrimaryTextInverted)
.background(Color.posOverlayFillInverted)
.cornerRadius(Constants.buttonCornerRadius)
.contentShape(Rectangle())
Comment on lines +78 to +83
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm guessing some of these are shared with other buttons? It'd be good to put them in a button style for sharing and to make the view easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we have an existing issue to make the async button a reusable component, I've logged it along: #14626

.disabled(isLoading)

Spacer()
}
.padding()
.animation(.easeInOut, value: errorMessage)
.onChange(of: textFieldAmountInput) { amount in
errorMessage = nil
}
}

private func markComplete() async {
// TODO:
// https://github.com/woocommerce/woocommerce-ios/issues/14602
}
}

private extension PointOfSaleCollectCashView {
enum Constants {
static let buttonSpacing: CGFloat = 12
static let buttonPadding: CGFloat = 32
static let buttonFont: POSFontStyle = .posBodyEmphasized
static let buttonCornerRadius: CGFloat = 8
}

enum Localization {
static let backNavigationTitle = NSLocalizedString(
"pointOfSale.cashview.back.navigation.title",
value: "Cash payment",
comment: "Title for the cash payment view navigation back button"
)
static let backNavigationSubtitle = NSLocalizedString(
"pointOfSale.cashview.back.navigation.subtitle",
value: "Total: %1$@",
comment: "Subtitle for the cash payment view navigation back button" +
"Reads as 'Total: $1.23'"
)
static let markPaymentCompletedButtonTitle = NSLocalizedString(
"pointOfSale.cashview.button.markpaymentcompleted.title",
value: "Mark payment as complete",
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a weird way to word it... I can see it's in the designs, but maybe it's worth discussing some more? It feels very programmery, I think the merchant action is more like Accept payment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'll raise it with Wagner along the navigation icons for each platform

comment: "Button to mark a cash payment as completed"
)
}
}

#if DEBUG
#Preview {
let posModel = PointOfSaleAggregateModel(
itemsController: PointOfSalePreviewItemsController(),
cardPresentPaymentService: CardPresentPaymentPreviewService(),
orderController: PointOfSalePreviewOrderController())
PointOfSaleCollectCashView(orderTotal: "$1.23")
.environmentObject(posModel)
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct POSSendReceiptView: View {
isShowingSendReceiptView = false
}, label: {
HStack {
Image(systemName: "arrow.backward")
Image(systemName: "chevron.left")
Text(Localization.emailReceiptNavigationText)
}
.font(.title)
Expand Down
33 changes: 33 additions & 0 deletions WooCommerce/Classes/POS/Presentation/TotalsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ struct TotalsView: View {
viewHelper.shouldShowTotalsFields(for: posModel.paymentState)
}

private var shouldShowCollectCashPaymentButton: Bool {
ServiceLocator.featureFlagService.isFeatureFlagEnabled(.acceptCashForPointOfSale) &&
posModel.orderState != .syncing &&
(posModel.paymentState == .idle || posModel.paymentState == .acceptingCard)
}

@Environment(\.dynamicTypeSize) var dynamicTypeSize
@Environment(\.colorScheme) var colorScheme

@State private var shouldShowCollectCashPayment: Bool = false

var body: some View {
HStack {
switch posModel.orderState {
Expand Down Expand Up @@ -52,6 +60,17 @@ struct TotalsView: View {
.opacity(viewHelper.shouldShowTotalsFields(for: posModel.paymentState) ? 1 : 0)
.layoutPriority(2)
}
Button(action: {
shouldShowCollectCashPayment = true
}, label: {
Text(Localization.cashPaymentButtonTitle)
.font(POSFontStyle.posBodyEmphasized)
.foregroundColor(.posPrimaryText)
.frame(height: Constants.buttonHeight)
})
.buttonStyle(SecondaryButtonStyle())
.padding(.horizontal, Constants.buttonHorizontalPadding)
.renderedIf(shouldShowCollectCashPaymentButton)
}
.animation(.default, value: posModel.cardPresentPaymentInlineMessage)
Spacer()
Expand All @@ -69,6 +88,13 @@ struct TotalsView: View {
isShowingTotalsFields = shouldShowTotalsFields
}
.onChange(of: shouldShowTotalsFields, perform: hideTotalsFieldsWithDelay)
.fullScreenCover(isPresented: $shouldShowCollectCashPayment) {
if case .loaded(let total) = posModel.orderState {
PointOfSaleCollectCashView(orderTotal: total.orderTotal)
.matchedGeometryEffect(id: Constants.matchedGeometryCashId,
in: totalsFieldAnimation)
}
}
.geometryGroupIfSupported()
}

Expand Down Expand Up @@ -291,6 +317,8 @@ private extension TotalsView {
enum Constants {
static let pricesIdealWidth: CGFloat = 382
static let verticalSpacing: CGFloat = 56
static let buttonHeight: CGFloat = 56
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: fixed button height might result in text being clipped with a large dynamic font size, and/or multiline text. If a fixed height is needed, @ScaledMetric can be considered.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I attempted to make the changes here but I found several other issues through the view when using dynamic font sizes, so I'll tackle these separately, as an example some additional space is starting to show up at the top of the view:
Simulator Screenshot - iPad Air 11 - iOS 17 5 M2 - 2024-12-23 at 18 56 25

static let buttonHorizontalPadding: CGFloat = 48

static let totalsLineViewPadding: EdgeInsets = .init(top: 20, leading: 24, bottom: 20, trailing: 24)
static let subtotalsVerticalSpacing: CGFloat = 8
Expand All @@ -311,6 +339,7 @@ private extension TotalsView {
static let matchedGeometrySubtotalId: String = "pos_totals_view_subtotal_matched_geometry_id"
static let matchedGeometryTaxId: String = "pos_totals_view_tax_matched_geometry_id"
static let matchedGeometryTotalId: String = "pos_totals_view_total_matched_geometry_id"
static let matchedGeometryCashId: String = "pos_totals_view_cash_matched_geometry_id"

static let totalsFieldsHideAnimationDelay: CGFloat = 0.3
}
Expand All @@ -328,6 +357,10 @@ private extension TotalsView {
"pos.totalsView.taxes",
value: "Taxes",
comment: "Title for taxes amount field")
static let cashPaymentButtonTitle = NSLocalizedString(
"pos.totalsView.cash.button.title",
value: "Cash payment",
comment: "Title for the cash payment button title")
}
}

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 @@ -1591,6 +1591,7 @@
68D8FBD12BFEF9C700477C42 /* TotalsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D8FBD02BFEF9C700477C42 /* TotalsView.swift */; };
68DF5A8D2CB38EEA000154C9 /* EditableOrderCouponLineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68DF5A8C2CB38EEA000154C9 /* EditableOrderCouponLineViewModel.swift */; };
68DF5A8F2CB38F20000154C9 /* OrderCouponSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68DF5A8E2CB38F20000154C9 /* OrderCouponSectionView.swift */; };
68E141DB2D13107400A70D5B /* PointOfSaleCollectCashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E141DA2D13107200A70D5B /* PointOfSaleCollectCashView.swift */; };
68E4E8B52C0EF39D00CFA0C3 /* PreviewHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E4E8B42C0EF39D00CFA0C3 /* PreviewHelpers.swift */; };
68E6749F2A4DA01C0034BA1E /* WooWPComPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E6749E2A4DA01C0034BA1E /* WooWPComPlan.swift */; };
68E674A12A4DA0B30034BA1E /* InAppPurchasesError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E674A02A4DA0B30034BA1E /* InAppPurchasesError.swift */; };
Expand Down Expand Up @@ -4686,6 +4687,7 @@
68D8FBD02BFEF9C700477C42 /* TotalsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalsView.swift; sourceTree = "<group>"; };
68DF5A8C2CB38EEA000154C9 /* EditableOrderCouponLineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableOrderCouponLineViewModel.swift; sourceTree = "<group>"; };
68DF5A8E2CB38F20000154C9 /* OrderCouponSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponSectionView.swift; sourceTree = "<group>"; };
68E141DA2D13107200A70D5B /* PointOfSaleCollectCashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCollectCashView.swift; sourceTree = "<group>"; };
68E4E8B42C0EF39D00CFA0C3 /* PreviewHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewHelpers.swift; sourceTree = "<group>"; };
68E6749E2A4DA01C0034BA1E /* WooWPComPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooWPComPlan.swift; sourceTree = "<group>"; };
68E674A02A4DA0B30034BA1E /* InAppPurchasesError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesError.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6989,6 +6991,7 @@
026826A72BF59DF70036F959 /* PointOfSaleEntryPointView.swift */,
68A345632D029E09002EE324 /* PaymentButtons.swift */,
026826A52BF59DF60036F959 /* PointOfSaleDashboardView.swift */,
68E141DA2D13107200A70D5B /* PointOfSaleCollectCashView.swift */,
DA013F502C65125100D9A391 /* PointOfSaleExitPosAlertView.swift */,
DA0DBE2E2C4FC61D00DF14C0 /* POSFloatingControlView.swift */,
20D3D42A2C64D7CC004CE6E3 /* SimpleProductsOnlyInformation.swift */,
Expand Down Expand Up @@ -15820,6 +15823,7 @@
EE19058C2B5F744300617C53 /* BlazeAddPaymentMethodWebView.swift in Sources */,
D83F5933225B2EB900626E75 /* ManualTrackingViewController.swift in Sources */,
3142663F2645E2AB00500598 /* PaymentSettingsFlowViewModelPresenter.swift in Sources */,
68E141DB2D13107400A70D5B /* PointOfSaleCollectCashView.swift in Sources */,
DEDB886B26E8531E00981595 /* ShippingLabelPackageAttributes.swift in Sources */,
AEC95D412774C5AE001571F5 /* AddressFormViewModelProtocol.swift in Sources */,
B6E851F5276330200041D1BA /* RefundCustomAmountsDetailsTableViewCell.swift in Sources */,
Expand Down