From 5d6eab1f9d5ec54ae4ae8dc660e4c174801181b0 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Mon, 17 Feb 2025 12:36:41 +0800 Subject: [PATCH 1/9] Replace cash view header with `POSPageHeaderView` with adjusted layout to remove extra padding originally for the header. --- .../PointOfSaleCollectCashView.swift | 118 +++++++----------- .../Classes/POS/Presentation/TotalsView.swift | 14 ++- 2 files changed, 55 insertions(+), 77 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index ca80f5050d5..558eb5ea0b2 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -28,63 +28,60 @@ struct PointOfSaleCollectCashView: View { var body: some View { ScrollView { VStack(alignment: .center, spacing: conditionalPadding(8)) { - HStack { + POSPageHeaderView(title: Localization.backNavigationTitle, + subtitle: formattedOrderTotal, + backButtonConfiguration: .init(state: isLoading ? .disabled: .enabled, + action: { + Task { @MainActor in + await posModel.cancelCashPayment() + isTextFieldFocused = false + } + })) + + VStack { + FormattableAmountTextField(viewModel: textFieldViewModel, style: .pos) + .focused($isTextFieldFocused) + .dynamicTypeSize(...DynamicTypeSize.accessibility1) + .onSubmit { + Task { @MainActor in + await submitCashAmount() + } + } + .onChange(of: textFieldViewModel.amount) { newValue in + textFieldAmountInput = newValue + updateChangeDueMessage() + } + + if let changeDue = changeDueMessage { + Text(changeDue) + .font(.posBodyLargeRegular()) + .foregroundColor(.posOnSurfaceVariantHighest) + } + + if let errorMessage = errorMessage { + Text(errorMessage) + .font(POSFontStyle.posBodyLargeRegular()) + .foregroundColor(.red) + .padding(.bottom, Constants.errorMessagePadding) + } + Button(action: { Task { @MainActor in - await posModel.cancelCashPayment() - isTextFieldFocused = false + await submitCashAmount() } }, label: { - navigationHeader + Text(Localization.markPaymentCompletedButtonTitle) }) - .disabled(isLoading) - Spacer() - .renderedIf(!dynamicTypeSize.isAccessibilitySize) - } - - FormattableAmountTextField(viewModel: textFieldViewModel, style: .pos) - .focused($isTextFieldFocused) + .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) + .frame(maxWidth: .infinity) .dynamicTypeSize(...DynamicTypeSize.accessibility1) - .onSubmit { - Task { @MainActor in - await submitCashAmount() - } - } - .onChange(of: textFieldViewModel.amount) { newValue in - textFieldAmountInput = newValue - updateChangeDueMessage() - } - - if let changeDue = changeDueMessage { - Text(changeDue) - .font(.posBodyLargeRegular()) - .foregroundColor(.posOnSurfaceVariantHighest) - } + .disabled(isLoading) - if let errorMessage = errorMessage { - Text(errorMessage) - .font(POSFontStyle.posBodyLargeRegular()) - .foregroundColor(.red) - .padding(.bottom, Constants.errorMessagePadding) + Spacer() } - - Button(action: { - Task { @MainActor in - await submitCashAmount() - } - }, label: { - Text(Localization.markPaymentCompletedButtonTitle) - }) - .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) - .frame(maxWidth: .infinity) - .dynamicTypeSize(...DynamicTypeSize.accessibility1) - .disabled(isLoading) - - Spacer() + .padding([.horizontal, .bottom]) } .background(backgroundColor) - .padding(.top, conditionalPadding(Constants.navigationHeaderTopPadding)) - .padding([.horizontal, .bottom]) .animation(.easeInOut, value: errorMessage) .animation(.easeInOut, value: changeDueMessage) .onChange(of: textFieldAmountInput) { _ in @@ -98,29 +95,6 @@ struct PointOfSaleCollectCashView: View { } } -@available(iOS 17.0, *) -private extension PointOfSaleCollectCashView { - @ViewBuilder - var navigationHeader: some View { - HStack(alignment: .top) { - Image(systemName: "chevron.backward") - .font(.posBodyLargeBold, maximumContentSizeCategory: .accessibilityLarge) - DynamicVStack(horizontalAlignment: .leading, spacing: Constants.navigationButtonSpacing) { - Text(Localization.backNavigationTitle) - .font(.posHeading) - .accessibilityAddTraits(.isHeader) - if dynamicTypeSize.isAccessibilitySize { - Spacer() - } - Text(formattedOrderTotal) - .font(.posBodyLargeRegular()) - } - .padding(.top, -Constants.navigationButtonSpacing) - } - .foregroundColor(navigationForegroundColor) - } -} - @available(iOS 17.0, *) private extension PointOfSaleCollectCashView { private func submitCashAmount() async { @@ -170,10 +144,6 @@ private extension PointOfSaleCollectCashView { .posSurface } - private var navigationForegroundColor: Color { - isLoading ? .posDisabledContainer : .primary - } - enum Localization { static let backNavigationTitle = NSLocalizedString( "pointOfSale.cashview.back.navigation.title", diff --git a/WooCommerce/Classes/POS/Presentation/TotalsView.swift b/WooCommerce/Classes/POS/Presentation/TotalsView.swift index 154830879f1..bec4fce8950 100644 --- a/WooCommerce/Classes/POS/Presentation/TotalsView.swift +++ b/WooCommerce/Classes/POS/Presentation/TotalsView.swift @@ -262,7 +262,14 @@ private extension TotalsView { let backgroundColor: Color let topPadding: CGFloat? let bottomPadding: CGFloat? - let sidePadding: CGFloat = 8 + let sidePadding: CGFloat + + init(backgroundColor: Color, topPadding: CGFloat?, bottomPadding: CGFloat?, sidePadding: CGFloat = 8) { + self.backgroundColor = backgroundColor + self.topPadding = topPadding + self.bottomPadding = bottomPadding + self.sidePadding = sidePadding + } static let primary = PaymentViewLayout( backgroundColor: .clear, @@ -332,8 +339,9 @@ private extension TotalsView { } case .cash: return PaymentViewLayout(backgroundColor: backgroundColor, - topPadding: nil, - bottomPadding: nil) + topPadding: 0, + bottomPadding: nil, + sidePadding: 0) } } } From fd65feedcba6bd755c80cb3b1ca0ee8905d2871a Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Mon, 17 Feb 2025 12:47:01 +0800 Subject: [PATCH 2/9] Keep the same VStack parameters in the nested VStack. --- .../Classes/POS/Presentation/PointOfSaleCollectCashView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift index 558eb5ea0b2..13094a29efa 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleCollectCashView.swift @@ -38,7 +38,7 @@ struct PointOfSaleCollectCashView: View { } })) - VStack { + VStack(alignment: .center, spacing: conditionalPadding(8)) { FormattableAmountTextField(viewModel: textFieldViewModel, style: .pos) .focused($isTextFieldFocused) .dynamicTypeSize(...DynamicTypeSize.accessibility1) From 557a28d73a611dd0c924b533f7d2925a61677e8c Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Mon, 17 Feb 2025 12:48:06 +0800 Subject: [PATCH 3/9] Replace receipt page header with `POSPageHeaderView` with adjusted layout. --- .../Reusable Views/POSSendReceiptView.swift | 88 ++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift index 77ed1cd9021..93b4b64a516 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSSendReceiptView.swift @@ -18,61 +18,51 @@ struct POSSendReceiptView: View { var body: some View { VStack(alignment: .center, spacing: conditionalPadding(8)) { - HStack { - Button(action: { - withAnimation { - isShowingSendReceiptView = false - isTextFieldFocused = false - } - }, label: { - HStack { - Image(systemName: "chevron.backward") - Text(Localization.emailReceiptNavigationText) + POSPageHeaderView(title: Localization.emailReceiptNavigationText, + backButtonConfiguration: .init(state: isLoading ? .disabled: .enabled, + action: { + withAnimation { + isShowingSendReceiptView = false + isTextFieldFocused = false + } + })) + + VStack(alignment: .center, spacing: conditionalPadding(8)) { + TextField(Localization.textfieldPlaceholder, text: $textFieldInput) + .dynamicTypeSize(...DynamicTypeSize.accessibility1) + .keyboardType(.emailAddress) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .multilineTextAlignment(.center) + .font(POSFontStyle.posBodyXLarge) + .focused() + .focused($isTextFieldFocused) + .padding() + .onSubmit { + sendReceipt() } - .font(.posHeading) - .foregroundColor(.posOnSurface) - .dynamicTypeSize(...DynamicTypeSize.accessibility3) - .accessibilityAddTraits(.isHeader) - }) - Spacer() - } - .buttonStyle(.plain) - .disabled(isLoading) - TextField(Localization.textfieldPlaceholder, text: $textFieldInput) - .dynamicTypeSize(...DynamicTypeSize.accessibility1) - .keyboardType(.emailAddress) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .multilineTextAlignment(.center) - .font(POSFontStyle.posBodyXLarge) - .focused() - .focused($isTextFieldFocused) - .padding() - .onSubmit { - sendReceipt() + if let errorMessage = errorMessage { + Text(errorMessage) + .font(POSFontStyle.posBodyLargeRegular()) + .foregroundColor(.red) + .padding(.bottom, Constants.errorMessagePadding) } - if let errorMessage = errorMessage { - Text(errorMessage) - .font(POSFontStyle.posBodyLargeRegular()) - .foregroundColor(.red) - .padding(.bottom, Constants.errorMessagePadding) - } - - Button(action: { - sendReceipt() - }, label: { - Text(Localization.buttonTitle) - }) - .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) - .dynamicTypeSize(...DynamicTypeSize.accessibility3) - .frame(maxWidth: .infinity) - .disabled(isLoading) + Button(action: { + sendReceipt() + }, label: { + Text(Localization.buttonTitle) + }) + .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) + .dynamicTypeSize(...DynamicTypeSize.accessibility3) + .frame(maxWidth: .infinity) + .disabled(isLoading) - Spacer() + Spacer() + } + .padding([.horizontal, .bottom]) } - .padding([.horizontal, .bottom]) .animation(.easeInOut, value: errorMessage) .onChange(of: textFieldInput) { _ in errorMessage = nil From 11a0a9d4e6c760a1f22528f386be82c210942e47 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Mon, 17 Feb 2025 12:52:55 +0800 Subject: [PATCH 4/9] Replace cart view header with `POSPageHeaderView`. --- .../Classes/POS/Presentation/CartView.swift | 51 +++++-------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/CartView.swift b/WooCommerce/Classes/POS/Presentation/CartView.swift index d8345d9c99d..582b54825b5 100644 --- a/WooCommerce/Classes/POS/Presentation/CartView.swift +++ b/WooCommerce/Classes/POS/Presentation/CartView.swift @@ -18,33 +18,16 @@ struct CartView: View { var body: some View { VStack { - DynamicHStack(spacing: Constants.cartHeaderSpacing) { - HStack(spacing: Constants.cartHeaderElementSpacing) { - backAddMoreButton - .disabled(shouldPreventCartEditing) - .shimmering(active: shouldPreventCartEditing) - - HStack { - Text(Localization.cartTitle) - .font(Constants.primaryFont) - .foregroundColor(.posOnSurface) - .accessibilityAddTraits(.isHeader) - - Spacer() - - if let itemsInCartLabel = viewHelper.itemsInCartLabel(for: posModel.cart.count) { - Text(itemsInCartLabel) - .font(Constants.itemsFont) - .foregroundColor(Color.posOnSurfaceVariantLowest) - } - } - .accessibilityElement(children: .combine) + POSPageHeaderView(title: Localization.cartTitle, + backButtonConfiguration: backButtonConfiguration, + trailingContent: { + if let itemsInCartLabel = viewHelper.itemsInCartLabel(for: posModel.cart.count) { + Text(itemsInCartLabel) + .font(Constants.itemsFont) + .foregroundColor(Color.posOnSurfaceVariantLowest) } HStack { - Spacer() - .renderedIf(dynamicTypeSize.isAccessibilitySize) - Button { posModel.removeAllItemsFromCart() } label: { @@ -53,10 +36,7 @@ struct CartView: View { .buttonStyle(POSOutlinedButtonStyle(size: .extraSmall)) .renderedIf(shouldShowClearCartButton) } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, POSHeaderLayoutConstants.sectionHorizontalPadding) - .padding(.vertical, POSHeaderLayoutConstants.sectionVerticalPadding) + }) .if(shouldApplyHeaderBottomShadow, transform: { $0.applyBottomShadow(backgroundColor: backgroundColor) }) if posModel.cart.isNotEmpty { @@ -204,7 +184,6 @@ private extension CartView { static let scrollViewCoordinateSpaceIdentifier: String = "CartScrollView" static let emptyViewImageTextSpacing: CGFloat = 30 // This should be 40 by designs, but the overlay technique means we have to tweak it static let cartHeaderSpacing: CGFloat = 8 - static let backButtonSymbol: String = "chevron.backward" static let cartHeaderElementSpacing: CGFloat = 16 static let cartAnimation: Animation = .spring(duration: 0.2) static let checkoutButtonVerticalPadding: CGFloat = 16 @@ -247,20 +226,16 @@ private extension CartView { .buttonStyle(POSFilledButtonStyle(size: .normal)) } - @ViewBuilder - var backAddMoreButton: some View { + var backButtonConfiguration: POSPageHeaderBackButtonConfiguration? { switch posModel.orderStage { case .building: - EmptyView() + return nil case .finalizing: - Button { + let state: POSPageHeaderBackButtonConfiguration.State = shouldPreventCartEditing ? .shimmering : .enabled + return .init(state: state, action: { ServiceLocator.analytics.track(.pointOfSaleBackToCartTapped) posModel.addMoreToCart() - } label: { - Image(systemName: Constants.backButtonSymbol) - .font(.posBodyLargeBold, maximumContentSizeCategory: .accessibilityLarge) - .foregroundColor(.posOnSurface) - } + }) } } From c05c584d8dcda17b5315334a42f301e62d67167c Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Mon, 17 Feb 2025 13:28:26 +0800 Subject: [PATCH 5/9] Fix cash payment success view having incorrect padding. --- .../Classes/POS/Presentation/TotalsView.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/TotalsView.swift b/WooCommerce/Classes/POS/Presentation/TotalsView.swift index bec4fce8950..15d4fa5a4ca 100644 --- a/WooCommerce/Classes/POS/Presentation/TotalsView.swift +++ b/WooCommerce/Classes/POS/Presentation/TotalsView.swift @@ -337,11 +337,18 @@ private extension TotalsView { return .primary } } - case .cash: - return PaymentViewLayout(backgroundColor: backgroundColor, - topPadding: 0, - bottomPadding: nil, - sidePadding: 0) + case .cash(let cashPaymentState): + switch cashPaymentState { + case .collectingCash: + return PaymentViewLayout(backgroundColor: backgroundColor, + topPadding: 0, + bottomPadding: nil, + sidePadding: 0) + case .paymentSuccess: + return PaymentViewLayout(backgroundColor: backgroundColor, + topPadding: nil, + bottomPadding: nil) + } } } } From 740da7f7c1ab6e8fcc9647aacf8f0ce2a3f0055c Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 18 Feb 2025 09:49:47 +0800 Subject: [PATCH 6/9] Remove unnecessary HStack. --- .../Classes/POS/Presentation/CartView.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/CartView.swift b/WooCommerce/Classes/POS/Presentation/CartView.swift index d41953d782e..e8fb7f82770 100644 --- a/WooCommerce/Classes/POS/Presentation/CartView.swift +++ b/WooCommerce/Classes/POS/Presentation/CartView.swift @@ -30,15 +30,13 @@ struct CartView: View { .foregroundColor(Color.posOnSurfaceVariantLowest) } - HStack { - Button { - posModel.removeAllItemsFromCart() - } label: { - Text(Localization.clearButtonTitle) - } - .buttonStyle(POSOutlinedButtonStyle(size: .extraSmall)) - .renderedIf(shouldShowClearCartButton) + Button { + posModel.removeAllItemsFromCart() + } label: { + Text(Localization.clearButtonTitle) } + .buttonStyle(POSOutlinedButtonStyle(size: .extraSmall)) + .renderedIf(shouldShowClearCartButton) }) .if(shouldApplyHeaderBottomShadow, transform: { $0.applyBottomShadow(backgroundColor: backgroundColor) }) From 0ad942c445456157a71706988427693f0652214c Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 18 Feb 2025 09:54:35 +0800 Subject: [PATCH 7/9] Remove vertical info icon padding so that it does not increase the height that can result in misalignment with the cart page header. --- WooCommerce/Classes/POS/Presentation/ItemListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index fe1d08db9a7..508ab1ae8f5 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -180,7 +180,7 @@ private extension ItemListView { static let bannerVerticalPadding: CGFloat = 26 static let bannerTextSpacing: CGFloat = 4 static let bannerTitleSpacing: CGFloat = 8 - static let infoIconInset: EdgeInsets = .init(top: 8, leading: 6, bottom: 8, trailing: 6) + static let infoIconInset: EdgeInsets = .init(top: 0, leading: 6, bottom: 0, trailing: 6) static let iconPadding: CGFloat = 26 static let itemListPadding: CGFloat = 16 static let bannerCardPadding: CGFloat = 16 From aea0fafc26b9378031c1c32545b962664d904206 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 18 Feb 2025 10:00:54 +0800 Subject: [PATCH 8/9] Use `DynamicHStack` for cart trailing content. --- .../Classes/POS/Presentation/CartView.swift | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/CartView.swift b/WooCommerce/Classes/POS/Presentation/CartView.swift index e8fb7f82770..87c21783978 100644 --- a/WooCommerce/Classes/POS/Presentation/CartView.swift +++ b/WooCommerce/Classes/POS/Presentation/CartView.swift @@ -21,22 +21,24 @@ struct CartView: View { POSPageHeaderView(title: Localization.cartTitle, backButtonConfiguration: backButtonConfiguration, trailingContent: { - if let itemsInCartLabel = viewHelper.itemsInCartLabel(for: posModel.cart.count) { - Text(itemsInCartLabel) - .font(Constants.itemsFont) - .lineLimit(1) - .minimumScaleFactor(0.5) - .dynamicTypeSize(...DynamicTypeSize.accessibility2) - .foregroundColor(Color.posOnSurfaceVariantLowest) - } + DynamicHStack(horizontalAlignment: .trailing, verticalAlignment: .center, spacing: Constants.cartHeaderElementSpacing) { + if let itemsInCartLabel = viewHelper.itemsInCartLabel(for: posModel.cart.count) { + Text(itemsInCartLabel) + .font(Constants.itemsFont) + .lineLimit(1) + .minimumScaleFactor(0.5) + .dynamicTypeSize(...DynamicTypeSize.accessibility2) + .foregroundColor(Color.posOnSurfaceVariantLowest) + } - Button { - posModel.removeAllItemsFromCart() - } label: { - Text(Localization.clearButtonTitle) + Button { + posModel.removeAllItemsFromCart() + } label: { + Text(Localization.clearButtonTitle) + } + .buttonStyle(POSOutlinedButtonStyle(size: .extraSmall)) + .renderedIf(shouldShowClearCartButton) } - .buttonStyle(POSOutlinedButtonStyle(size: .extraSmall)) - .renderedIf(shouldShowClearCartButton) }) .if(shouldApplyHeaderBottomShadow, transform: { $0.applyBottomShadow(backgroundColor: backgroundColor) }) From e08e36d440b18b3bcd8eaabd94d2fcccea665561 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 18 Feb 2025 14:36:21 +0800 Subject: [PATCH 9/9] Update page header subtitle color. --- .../POS/Presentation/Reusable Views/POSPageHeaderView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift index cb21dd89be7..35ac8f42ba5 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift @@ -61,7 +61,7 @@ struct POSPageHeaderView: View { .lineLimit(1) .minimumScaleFactor(0.5) .dynamicTypeSize(...DynamicTypeSize.accessibility2) - .foregroundColor(.posOnSurfaceVariantHighest) + .foregroundColor(.posOnSurface) } }