From 13cfee8d7e8adcf99f397fc4abd46c155c8834ce Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:01:30 +0300 Subject: [PATCH 01/31] Update POSOrderListRow style according to designs --- .../Orders/POSOrderListView.swift | 156 +++++++++++------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 4d2273bca91..3b34eabfcb1 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -137,6 +137,7 @@ struct POSOrderListView: View { footerRows } .padding(.horizontal) + .padding(.top, POSPadding.xSmall) .padding(.bottom, POSPadding.medium) } ) @@ -180,44 +181,59 @@ private struct OrderRowView: View { } var body: some View { - HStack(alignment: .center, spacing: POSSpacing.medium) { - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { - Text("#\(order.number)") // TODO: WOOMOB-1142 - .font(.posBodySmallBold) - .foregroundStyle(Color.posOnSurface) - .fixedSize(horizontal: false, vertical: true) + VStack(alignment: .leading, spacing: POSSpacing.small) { + orderHeaderRow + orderDetailsColumn + orderBadgeRow + } + .padding(.horizontal, POSPadding.medium * (1 / scale)) + .padding(.vertical, POSPadding.medium * (1 / scale)) + .frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, alignment: .leading) + .background(isSelected ? Color.posSurfaceDim : Color.posSurfaceContainerLowest) + .posItemCardBorderStyles() + } + + @ViewBuilder + private var orderHeaderRow: some View { + HStack(alignment: .center) { + Text("#\(order.number)") + .font(.posBodySmallBold) + .foregroundStyle(Color.posOnSurface) + .fixedSize(horizontal: false, vertical: true) + + Spacer() + + Text(order.formattedTotal) + .font(.posBodySmallRegular()) + .foregroundStyle(Color.posOnSurfaceVariantHighest) + .fixedSize(horizontal: false, vertical: true) + } + } - Text(DateFormatter.dateAndTimeFormatter.string(from: order.dateCreated)) + @ViewBuilder + private var orderDetailsColumn: some View { + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + Text(DateFormatter.dateAndTimeFormatter.string(from: order.dateCreated)) + .font(.posBodySmallRegular()) + .foregroundStyle(Color.posOnSurfaceVariantHighest) + .fixedSize(horizontal: false, vertical: true) + + if let customerEmail = order.customerEmail, customerEmail.isNotEmpty { + Text(customerEmail) .font(.posBodySmallRegular()) .foregroundStyle(Color.posOnSurfaceVariantHighest) .fixedSize(horizontal: false, vertical: true) - - - if let customerEmail = order.customerEmail, customerEmail.isNotEmpty { - Text(customerEmail) - .font(.posBodySmallRegular()) - .foregroundStyle(Color.posOnSurfaceVariantHighest) - .fixedSize(horizontal: false, vertical: true) - } } - .multilineTextAlignment(.leading) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + @ViewBuilder + private var orderBadgeRow: some View { + HStack { + PointOfSaleOrderBadgeView(order: order) Spacer() - - VStack(alignment: .trailing, spacing: POSSpacing.xSmall) { - Text(order.formattedTotal) - .font(.posBodyLargeBold) - .foregroundStyle(Color.posOnSurface) - - PointOfSaleOrderBadgeView(order: order) - } - .multilineTextAlignment(.trailing) } - .padding(.horizontal, POSPadding.medium * (1 / scale)) - .padding(.vertical, POSPadding.medium * (1 / scale)) - .frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, alignment: .leading) - .background(isSelected ? Color.posSurfaceDim : Color.posSurfaceContainerLowest) - .posItemCardBorderStyles() } } @@ -231,40 +247,10 @@ private struct GhostOrderRowView: View { var body: some View { GeometryReader { geometry in - VStack { - Spacer() - HStack(alignment: .center, spacing: POSSpacing.medium) { - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.2, height: 16) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.4, height: 14) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - } - - Spacer() - - VStack(alignment: .trailing, spacing: POSSpacing.xSmall) { - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.25, height: 18) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.28, height: 14) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - } - } - Spacer() + VStack(alignment: .leading, spacing: POSSpacing.small) { + ghostHeaderRow(geometry: geometry) + ghostDetailsColumn(geometry: geometry) + ghostBadgeRow(geometry: geometry) } } .padding(.horizontal, POSPadding.medium * (1 / scale)) @@ -274,6 +260,48 @@ private struct GhostOrderRowView: View { .posItemCardBorderStyles() .geometryGroup() } + + @ViewBuilder + private func ghostHeaderRow(geometry: GeometryProxy) -> some View { + HStack(alignment: .center) { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: geometry.size.width * 0.25, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + + Spacer() + + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: geometry.size.width * 0.25, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + } + } + + @ViewBuilder + private func ghostDetailsColumn(geometry: GeometryProxy) -> some View { + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: geometry.size.width * 0.4, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + } + } + + @ViewBuilder + private func ghostBadgeRow(geometry: GeometryProxy) -> some View { + HStack { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: geometry.size.width * 0.28, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + Spacer() + } + } } // MARK: - Order Badge View From 2eb6e424ef36b29dd671d3d79b4e8fbf3da2c88d Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Mon, 29 Sep 2025 19:27:51 +0300 Subject: [PATCH 02/31] Update POSOrderBadgeView to reuse a coloring schema from the main app --- .../Presentation/Orders/POSDetailsView.swift | 3 +- .../Orders/POSOrderBadgeView.swift | 36 +++++++++++++ .../Orders/POSOrderListView.swift | 52 +------------------ .../WooCommerce.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 43 insertions(+), 52 deletions(-) create mode 100644 WooCommerce/Classes/POS/Presentation/Orders/POSOrderBadgeView.swift diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index a6f8752a64c..51a9a4094b7 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -29,7 +29,7 @@ struct POSOrderDetailsView: View { POSPageHeaderView( title: Localization.orderTitle(order.number), backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack) : nil, - trailingContent: { PointOfSaleOrderBadgeView(order: order) }, + trailingContent: { POSOrderBadgeView(order: order) }, bottomContent: { headerBottomContent(for: order) } ) @@ -440,5 +440,6 @@ private enum Localization { order: POSPreviewHelpers.makePreviewOrder(), onBack: {} ) + .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) } #endif diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderBadgeView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderBadgeView.swift new file mode 100644 index 00000000000..119f42ffb51 --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderBadgeView.swift @@ -0,0 +1,36 @@ +import SwiftUI +import WooFoundation +import struct Yosemite.POSOrder + +struct POSOrderBadgeView: View { + private let order: POSOrder + + init(order: POSOrder) { + self.order = order + } + + var body: some View { + Text(order.status.localizedName) + .font(.posCaptionRegular) + .foregroundStyle(.black) + .padding(.horizontal, POSPadding.small) + .padding(.vertical, POSPadding.xSmall) + .background(statusBackgroundColor) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + } + + private var statusBackgroundColor: Color { + switch order.status { + case .completed: + return Color(uiColor: .withColorStudio(.blue, shade: .shade5)) + case .failed: + return Color(uiColor: .withColorStudio(.red, shade: .shade5)) + case .processing: + return Color(uiColor: .withColorStudio(.green, shade: .shade5)) + case .onHold: + return Color(uiColor: .withColorStudio(.orange, shade: .shade5)) + default: + return Color(uiColor: .gray(.shade5)) + } + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 3b34eabfcb1..9627bbea7bf 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -231,7 +231,7 @@ private struct OrderRowView: View { @ViewBuilder private var orderBadgeRow: some View { HStack { - PointOfSaleOrderBadgeView(order: order) + POSOrderBadgeView(order: order) Spacer() } } @@ -304,56 +304,6 @@ private struct GhostOrderRowView: View { } } -// MARK: - Order Badge View - -struct PointOfSaleOrderBadgeView: View { - let order: POSOrder - - init(order: POSOrder) { - self.order = order - } - - var body: some View { - HStack(spacing: POSSpacing.xSmall) { - if let paymentMethodIcon = paymentMethodIcon { - Image(systemName: paymentMethodIcon) - .foregroundStyle(statusColor) - .font(.caption) - } - Text(order.status.localizedName) - .font(.posCaptionRegular) - .foregroundStyle(statusColor) - } - .padding(.horizontal, POSPadding.small) - .padding(.vertical, POSPadding.xSmall) - .background(statusColor.opacity(0.1)) - .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) - } - - private var paymentMethodIcon: String? { - let paymentMethod = OrderPaymentMethod(rawValue: order.paymentMethodID) - switch paymentMethod { - case .cod: - return "banknote" - case .stripe, .woocommercePayments: - return "creditcard" - default: - return nil - } - } - - private var statusColor: Color { - switch order.status { - case .completed: - return .posSuccess - case .failed: - return .posError - default: - return .posOnSurfaceVariantLowest - } - } -} - // MARK: - Search private extension POSOrderListView { diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index cc2dc21359d..d5c91cb0c3c 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -136,6 +136,7 @@ 01C21AB82E66EC26008E4D77 /* POSOrderDetailsEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C21AB72E66EC14008E4D77 /* POSOrderDetailsEmptyView.swift */; }; 01C9C59F2DA3D98400CD81D8 /* CartRowRemoveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C9C59E2DA3D97E00CD81D8 /* CartRowRemoveButton.swift */; }; 01D082402C5B9EAB007FE81F /* POSBackgroundAppearanceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D0823F2C5B9EAB007FE81F /* POSBackgroundAppearanceKey.swift */; }; + 01DE96902E8AE83200E07E1A /* POSOrderBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DE968F2E8AE83200E07E1A /* POSOrderBadgeView.swift */; }; 01E62EC82DFADF56003A6D9E /* Cart+BarcodeScanError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E62EC72DFADF4B003A6D9E /* Cart+BarcodeScanError.swift */; }; 01F067ED2D0C5D59001C5805 /* MockLocationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F067EC2D0C5D56001C5805 /* MockLocationService.swift */; }; 01F3D1302E741F3B00D867F1 /* POSCardPresentPaymentMessageViewImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3D12F2E741F3B00D867F1 /* POSCardPresentPaymentMessageViewImage.swift */; }; @@ -3355,6 +3356,7 @@ 01C21AB72E66EC14008E4D77 /* POSOrderDetailsEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSOrderDetailsEmptyView.swift; sourceTree = ""; }; 01C9C59E2DA3D97E00CD81D8 /* CartRowRemoveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CartRowRemoveButton.swift; sourceTree = ""; }; 01D0823F2C5B9EAB007FE81F /* POSBackgroundAppearanceKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSBackgroundAppearanceKey.swift; sourceTree = ""; }; + 01DE968F2E8AE83200E07E1A /* POSOrderBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSOrderBadgeView.swift; sourceTree = ""; }; 01E62EC72DFADF4B003A6D9E /* Cart+BarcodeScanError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Cart+BarcodeScanError.swift"; sourceTree = ""; }; 01F067EC2D0C5D56001C5805 /* MockLocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationService.swift; sourceTree = ""; }; 01F3D12F2E741F3B00D867F1 /* POSCardPresentPaymentMessageViewImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCardPresentPaymentMessageViewImage.swift; sourceTree = ""; }; @@ -6624,6 +6626,7 @@ 01ABA0272E57579300829DC0 /* Orders */ = { isa = PBXGroup; children = ( + 01DE968F2E8AE83200E07E1A /* POSOrderBadgeView.swift */, 01B7C9C92E71C8D00004BE9D /* POSOrderListEmptyViewModel.swift */, 01C21AB72E66EC14008E4D77 /* POSOrderDetailsEmptyView.swift */, 01C21AB52E66EB70008E4D77 /* POSOrderDetailsLoadingView.swift */, @@ -16000,6 +16003,7 @@ 7E7C5F792719A8F900315B61 /* EditProductCategoryListViewModel.swift in Sources */, DE69C55527C5E317000BB888 /* ShippingLabelSampleData.swift in Sources */, DE525499268C8B32007A5829 /* UIRefreshControl+Woo.swift in Sources */, + 01DE96902E8AE83200E07E1A /* POSOrderBadgeView.swift in Sources */, 204415912CE622BA0070BF54 /* PointOfSaleOrderTotals.swift in Sources */, EEC259442B43EF3B004D703C /* BlazeEditAdViewModel.swift in Sources */, B541B2172189EED4008FE7C1 /* NSMutableAttributedString+Helpers.swift in Sources */, From adacedf9ebdea2980e6ee602c2a30560235461c3 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:40:40 +0300 Subject: [PATCH 03/31] Update POSOrderRowView sizing according to designs --- .../Orders/POSOrderListView.swift | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 9627bbea7bf..e3fd7502fe5 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -128,7 +128,7 @@ struct POSOrderListView: View { Button(action: { orderListModel.ordersController.selectOrder(order) }) { - OrderRowView(order: order, isSelected: orderListModel.ordersController.selectedOrder?.id == order.id) + POSOrderRowView(order: order, isSelected: orderListModel.ordersController.selectedOrder?.id == order.id) } .buttonStyle(PlainButtonStyle()) } @@ -150,12 +150,12 @@ struct POSOrderListView: View { case .loading(let orders): if orders.isEmpty { ForEach(0..<8, id: \.self) { _ in - GhostOrderRowView() + POSGhostOrderRowView() } .opacity(orders.isEmpty ? 1 : 0) .animation(.default, value: orders.isEmpty) } else { - GhostOrderRowView() + POSGhostOrderRowView() } case .inlineError(_, let errorState, .pagination): POSListInlineErrorView(errorState: errorState) { @@ -169,7 +169,7 @@ struct POSOrderListView: View { } } -private struct OrderRowView: View { +private struct POSOrderRowView: View { let order: POSOrder let isSelected: Bool @@ -181,10 +181,11 @@ private struct OrderRowView: View { } var body: some View { - VStack(alignment: .leading, spacing: POSSpacing.small) { + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { orderHeaderRow orderDetailsColumn - orderBadgeRow + Spacer().frame(height: POSSpacing.xSmall) + POSOrderBadgeView(order: order) } .padding(.horizontal, POSPadding.medium * (1 / scale)) .padding(.vertical, POSPadding.medium * (1 / scale)) @@ -227,17 +228,9 @@ private struct OrderRowView: View { } .frame(maxWidth: .infinity, alignment: .leading) } - - @ViewBuilder - private var orderBadgeRow: some View { - HStack { - POSOrderBadgeView(order: order) - Spacer() - } - } } -private struct GhostOrderRowView: View { +private struct POSGhostOrderRowView: View { @ScaledMetric private var scale: CGFloat = 1.0 @Environment(\.dynamicTypeSize) var dynamicTypeSize @@ -247,15 +240,18 @@ private struct GhostOrderRowView: View { var body: some View { GeometryReader { geometry in - VStack(alignment: .leading, spacing: POSSpacing.small) { + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + Spacer().frame(minHeight: 0) ghostHeaderRow(geometry: geometry) ghostDetailsColumn(geometry: geometry) + Spacer().frame(height: POSSpacing.xSmall) ghostBadgeRow(geometry: geometry) + Spacer().frame(minHeight: 0) } } .padding(.horizontal, POSPadding.medium * (1 / scale)) .padding(.vertical, POSPadding.medium * (1 / scale)) - .frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, alignment: .leading) + .frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, maxHeight: .infinity, alignment: .leading) .background(Color.posSurfaceContainerLowest) .posItemCardBorderStyles() .geometryGroup() @@ -293,14 +289,11 @@ private struct GhostOrderRowView: View { @ViewBuilder private func ghostBadgeRow(geometry: GeometryProxy) -> some View { - HStack { - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.28, height: POSPadding.medium) - .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) - .shimmering() - Spacer() - } + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: geometry.size.width * 0.28, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() } } @@ -346,7 +339,7 @@ final class POSOrderSearchable: POSSearchable { // MARK: - Constants private enum Constants { - static let orderCardMinHeight: CGFloat = 90 + static let orderCardMinHeight: CGFloat = 112 static let maximumOrderCardHeight: CGFloat = Constants.orderCardMinHeight * 2 static let animationDuration: CGFloat = 0.2 static let searchControlID = "searchControl" From 5ffe5a9c5b883ffa7047604d4c9410360eb8b7a0 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:07:05 +0300 Subject: [PATCH 04/31] Add preview helper for order with refund scenario Added makePreviewOrderWithRefund() method to support preview of order details with refunds including formatted net amount. --- .../Classes/POS/Utils/PreviewHelpers.swift | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index 420e650a805..9e15cd5f81b 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -301,6 +301,54 @@ struct POSPreviewHelpers { formattedNetAmount: nil ) } + + static func makePreviewOrderWithRefund() -> POSOrder { + return POSOrder( + id: 2, + number: "1002", + dateCreated: Date().addingTimeInterval(-3600), + status: .completed, + formattedTotal: "$89.50", + formattedSubtotal: "$89.96", + customerEmail: "customer.with.refund@example.com", + paymentMethodID: "woocommerce_payments", + paymentMethodTitle: "WooCommerce In-Person Payments", + lineItems: [ + POSOrderItem( + itemID: 3, + name: "Artisan Chocolate Box", + quantity: 3.0, + formattedPrice: "$19.99", + formattedTotal: "$59.97", + imageSrc: nil, + attributes: [] + ), + POSOrderItem( + itemID: 4, + name: "Gourmet Cookie Set - Mixed", + quantity: 1.0, + formattedPrice: "$29.99", + formattedTotal: "$29.99", + imageSrc: nil, + attributes: [ + OrderItemAttribute(metaID: 3, name: "Flavor", value: "Mixed"), + OrderItemAttribute(metaID: 4, name: "Packaging", value: "Gift Box") + ] + ) + ], + refunds: [ + POSOrderRefund( + refundID: 1, + formattedTotal: "-$19.99", + reason: "Customer requested partial refund" + ) + ], + formattedDiscountTotal: "-$15.00", + formattedTotalTax: "$8.95", + formattedPaymentTotal: "$89.50", + formattedNetAmount: "$69.51" + ) + } } // MARK: - Preview Orders Controller From c80be386be3a7caf5b51ba37b53289cca2a4125a Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:07:32 +0300 Subject: [PATCH 05/31] Update header to use POSPageHeaderView with email receipt button Modified header title format to show order number directly and moved email receipt action to trailingContent. Updated back button to use xmark icon and apply POSSpacing.none. --- .../Presentation/Orders/POSDetailsView.swift | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 51a9a4094b7..3d6b01afa82 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -25,12 +25,25 @@ struct POSOrderDetailsView: View { } var body: some View { - VStack(spacing: 0) { + VStack(spacing: POSSpacing.none) { POSPageHeaderView( - title: Localization.orderTitle(order.number), - backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack) : nil, - trailingContent: { POSOrderBadgeView(order: order) }, - bottomContent: { headerBottomContent(for: order) } + title: "#\(order.number)", + backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack, buttonIcon: "xmark") : nil, + trailingContent: { + if order.status == .completed { + VStack { + Button(action: { + isShowingEmailReceiptView = true + }) { + Text(Localization.emailReceiptActionTitle) + } + .buttonStyle(POSFilledButtonStyle(size: .extraSmall)) + } + } + }, + bottomContent: { + headerBottomContent(for: order) + } ) ScrollView { From f232038398663efd9cb9128af71fccbbdcb160af Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:07:50 +0300 Subject: [PATCH 06/31] Remove actions section and update layout padding Removed separate actions section since email receipt is now in header. Updated scroll view padding to use POSPadding constants and changed background to posSurfaceBright. --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 3d6b01afa82..d7456200c8f 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -48,19 +48,17 @@ struct POSOrderDetailsView: View { ScrollView { VStack(alignment: .leading, spacing: POSSpacing.medium) { - if actions.isNotEmpty { - actionsSection(actions) - } - if !order.lineItems.isEmpty { productsSection(order) } totalsSection(order) } + .padding(.top, POSPadding.xSmall) .padding(.horizontal, POSPadding.medium) + .padding(.bottom, POSPadding.medium) } } - .background(Color.posSurface) + .background(Color.posSurfaceBright) .navigationBarHidden(true) .posFullScreenCover(isPresented: $isShowingEmailReceiptView) { POSSendReceiptView(isShowingSendReceiptView: $isShowingEmailReceiptView) { email in From e2799f9840d138079ca298775e6df6bb908e68d5 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:08:09 +0300 Subject: [PATCH 07/31] Add order badge to header bottom content Moved order badge from trailingContent to bottomContent within header with proper spacing and top padding. --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index d7456200c8f..e469aa05105 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -134,7 +134,11 @@ private extension POSOrderDetailsView { .foregroundStyle(Color.posOnSurfaceVariantHighest) .fixedSize(horizontal: false, vertical: true) } + + Spacer().frame(height: POSSpacing.xSmall) + POSOrderBadgeView(order: order) } + .padding(.top, POSSpacing.xSmall) .multilineTextAlignment(.leading) } From 728b9336ef7a452eb8f7ab4912712dc30a4c8df5 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:08:22 +0300 Subject: [PATCH 08/31] Add dividers between product items Updated products section to include dividers between line items with enumerated array and conditional divider display. --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index e469aa05105..021bb17a984 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -79,9 +79,13 @@ private extension POSOrderDetailsView { .font(.posBodyLargeBold) .foregroundStyle(Color.posOnSurface) - VStack(spacing: POSSpacing.small) { - ForEach(order.lineItems, id: \.itemID) { item in + VStack(spacing: POSSpacing.none) { + ForEach(Array(order.lineItems.enumerated()), id: \.element.itemID) { index, item in productRow(item: item) + + if index < order.lineItems.count - 1 { + divider + } } } } From 0693b0c68befa205b72659ed8d39a2b7630fed49 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:08:35 +0300 Subject: [PATCH 09/31] Update product row alignment and remove padding Changed product row alignment from top to center and removed vertical padding for cleaner layout. --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 021bb17a984..e7673b30767 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -153,13 +153,12 @@ private extension POSOrderDetailsView { private extension POSOrderDetailsView { @ViewBuilder func productRow(item: POSOrderItem) -> some View { - HStack(alignment: .top, spacing: POSSpacing.medium) { + HStack(alignment: .center, spacing: POSSpacing.medium) { productImageView(item: item) productDetailsView(item: item) Spacer() productTotalView(item: item) } - .padding(.vertical, POSPadding.small) } @ViewBuilder From 4d1b113037c6f7dacbc805f71ebca035a2f952a3 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:08:55 +0300 Subject: [PATCH 10/31] Update product details layout and fonts Removed product attributes display, changed spacing to none, and updated fonts to use posBodySmallBold and posBodySmallRegular for consistency. --- .../POS/Presentation/Orders/POSDetailsView.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index e7673b30767..3a280898399 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -171,16 +171,12 @@ private extension POSOrderDetailsView { @ViewBuilder func productDetailsView(item: POSOrderItem) -> some View { - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + VStack(alignment: .leading, spacing: POSSpacing.none) { Text(item.name) - .font(.posBodyMediumBold) + .font(.posBodySmallBold) .foregroundStyle(Color.posOnSurface) .fixedSize(horizontal: false, vertical: true) - if !item.attributes.isEmpty { - productAttributesView(item.attributes) - } - Text(Localization.quantityLabel(item.quantity.intValue, item.formattedPrice)) .font(.posBodySmallRegular()) @@ -199,7 +195,7 @@ private extension POSOrderDetailsView { @ViewBuilder func productTotalView(item: POSOrderItem) -> some View { Text(item.formattedTotal) - .font(.posBodyMediumRegular()) + .font(.posBodySmallRegular()) .foregroundStyle(Color.posOnSurface) } } From 190b3fdde3fe007afa3f211ce2d5b23f9c116369 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:09:13 +0300 Subject: [PATCH 11/31] Update totals section with consistent dividers and spacing Changed totals section spacing to small and replaced hardcoded divider with reusable divider component for consistency. --- .../POS/Presentation/Orders/POSDetailsView.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 3a280898399..9d37c6453aa 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -96,22 +96,26 @@ private extension POSOrderDetailsView { @ViewBuilder func totalsSection(_ order: POSOrder) -> some View { - VStack(alignment: .leading, spacing: POSSpacing.medium) { Text(Localization.totalsTitle) .font(.posBodyLargeBold) .foregroundStyle(Color.posOnSurface) - VStack(spacing: POSSpacing.medium) { + VStack(spacing: POSSpacing.small) { productsSubtotalRow(order) discountTotalRow(order) taxTotalRow(order) - Divider() - .background(Color.posSurfaceDim) + divider mainTotalRow(order) + + divider + paidAmountRow(order) + + divider + refundsSection(order) } } From 97805cf51c0dcb0a9f8982f502dea4c05c7cf765 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:09:58 +0300 Subject: [PATCH 12/31] Update totals row fonts, colors and spacing Updated all totals row components to use posBodySmallBold/Regular fonts, proper colors, and consistent spacing. Added dividers between refunds and updated payment method font to posCaptionRegular. --- .../Presentation/Orders/POSDetailsView.swift | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 9d37c6453aa..bf0f2946d8b 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -238,22 +238,24 @@ private extension POSOrderDetailsView { totalsRow( title: Localization.totalLabel, amount: order.formattedTotal, - titleFont: .posBodyMediumBold + titleColor: .posOnSurface, + titleFont: .posBodySmallBold ) } @ViewBuilder func paidAmountRow(_ order: POSOrder) -> some View { - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + VStack(alignment: .leading, spacing: POSSpacing.none) { totalsRow( title: Localization.paidLabel, amount: order.formattedPaymentTotal, - titleFont: .posBodyMediumBold + titleColor: .posOnSurface, + titleFont: .posBodySmallBold ) if order.paymentMethodTitle.isNotEmpty { Text(order.paymentMethodTitle) - .font(.posBodySmallRegular()) + .font(.posCaptionRegular) .foregroundStyle(Color.posOnSurfaceVariantHighest) } } @@ -264,6 +266,7 @@ private extension POSOrderDetailsView { if !order.refunds.isEmpty { ForEach(order.refunds, id: \.refundID) { refund in refundRow(refund: refund) + divider } if let netAmount = order.formattedNetAmount { @@ -278,12 +281,13 @@ private extension POSOrderDetailsView { totalsRow( title: Localization.refundLabel, amount: refund.formattedTotal, - titleFont: .posBodyMediumBold + titleColor: .posOnSurface, + titleFont: .posBodySmallBold ) if let reason = refund.reason, !reason.isEmpty { Text(Localization.reasonLabel(reason)) - .font(.posBodySmallRegular()) + .font(.posCaptionRegular) .foregroundStyle(Color.posOnSurfaceVariantHighest) } } @@ -294,7 +298,8 @@ private extension POSOrderDetailsView { totalsRow( title: Localization.netPaymentLabel, amount: netAmount, - titleFont: .posBodyMediumBold + titleColor: .posOnSurface, + titleFont: .posBodySmallBold ) } @@ -302,15 +307,17 @@ private extension POSOrderDetailsView { func totalsRow( title: String, amount: String, - titleFont: POSFontStyle = .posBodyMediumRegular(), - amountFont: POSFontStyle = .posBodyMediumRegular() + titleColor: Color = .posOnSurfaceVariantHighest, + titleFont: POSFontStyle = .posBodySmallRegular() ) -> some View { HStack { Text(title) .font(titleFont) + .foregroundStyle(titleColor) Spacer() Text(amount) - .font(amountFont) + .font(.posBodySmallRegular()) + .foregroundStyle(Color.posOnSurface) } } } From a6f98d5da5f17a05c53a85242c21d3645de634b5 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:10:23 +0300 Subject: [PATCH 13/31] Remove actions section and add divider helper Removed old actions section code and added reusable divider ViewBuilder with proper styling and spacing. --- .../Presentation/Orders/POSDetailsView.swift | 49 +++---------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index bf0f2946d8b..4bc0124e566 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -322,49 +322,12 @@ private extension POSOrderDetailsView { } } -// MARK: - Actions - private extension POSOrderDetailsView { - enum POSOrderDetailsAction: Identifiable, CaseIterable { - case emailReceipt - - var id: String { title } - - var title: String { - switch self { - case .emailReceipt: - Localization.emailReceiptActionTitle - } - } - - func available(for order: POSOrder) -> Bool { - switch self { - case .emailReceipt: - order.status == .completed - } - } - } - - var actions: [POSOrderDetailsAction] { - POSOrderDetailsAction.allCases.filter { $0.available(for: order) } - } - @ViewBuilder - func actionsSection(_ actions: [POSOrderDetailsAction]) -> some View { - HStack { - ForEach(actions) { action in - Button(action: { - switch action { - case .emailReceipt: - isShowingEmailReceiptView = true - } - }) { - Text(action.title) - } - .buttonStyle(POSOutlinedButtonStyle(size: .extraSmall)) - } - } - .padding(.vertical) + var divider: some View { + Divider() + .overlay(Color.posOutlineVariant.opacity(0.5)) + .padding(.vertical, POSSpacing.small) } } @@ -426,8 +389,8 @@ private enum Localization { ) static let paidLabel = NSLocalizedString( - "pos.orderDetailsView.paidLabel", - value: "Paid", + "pos.orderDetailsView.paidLabel2", + value: "Total paid", comment: "Label for the paid amount" ) From 3eee4efce44635d2dfe9504399ea5cefa275e709 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:10:38 +0300 Subject: [PATCH 14/31] Add preview for order details with refund Added second preview showing order details with refund scenario to test refund display functionality. --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 4bc0124e566..3c0b650118a 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -430,4 +430,12 @@ private enum Localization { ) .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) } + +#Preview("Order Details with Refund") { + POSOrderDetailsView( + order: POSPreviewHelpers.makePreviewOrderWithRefund(), + onBack: {} + ) + .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) +} #endif From d005e6ed57a741224793e478b482c832a615a9cb Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:22:40 +0300 Subject: [PATCH 15/31] Configure spacing between product row title and details --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 3c0b650118a..64ce10a33fb 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -175,7 +175,7 @@ private extension POSOrderDetailsView { @ViewBuilder func productDetailsView(item: POSOrderItem) -> some View { - VStack(alignment: .leading, spacing: POSSpacing.none) { + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { Text(item.name) .font(.posBodySmallBold) .foregroundStyle(Color.posOnSurface) From 3d569f9c38d8bc7aa07be851d2d0632274a0c698 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:26:34 +0300 Subject: [PATCH 16/31] Move bottomContent under whole page header content to put it under the back button --- .../Reusable Views/POSPageHeaderView.swift | 104 ++++++++++-------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift index 8628de52074..9983b8cafd0 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift @@ -50,9 +50,7 @@ struct POSPageHeaderView LeadingContent = { EmptyView() }, @ViewBuilder trailingContent: () -> TrailingContent = { EmptyView() }, @ViewBuilder bottomContent: () -> BottomContent = { EmptyView() } @@ -72,11 +71,17 @@ struct POSPageHeaderView LeadingContent = { EmptyView() }, @ViewBuilder trailingContent: () -> TrailingContent = { EmptyView() }, @ViewBuilder bottomContent: () -> BottomContent = { EmptyView() } @@ -86,66 +91,73 @@ struct POSPageHeaderView Date: Tue, 30 Sep 2025 17:27:01 +0300 Subject: [PATCH 17/31] Spacing and divider adjustments for POSOrderDetailsView --- .../Presentation/Orders/POSDetailsView.swift | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 64ce10a33fb..8903199fe12 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -29,16 +29,17 @@ struct POSOrderDetailsView: View { POSPageHeaderView( title: "#\(order.number)", backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack, buttonIcon: "xmark") : nil, + alignment: .firstTextBaseline, trailingContent: { if order.status == .completed { - VStack { - Button(action: { - isShowingEmailReceiptView = true - }) { - Text(Localization.emailReceiptActionTitle) - } - .buttonStyle(POSFilledButtonStyle(size: .extraSmall)) + Button(action: { + isShowingEmailReceiptView = true + }) { + Text(Localization.emailReceiptActionTitle) + .lineLimit(1) + .minimumScaleFactor(0.5) } + .buttonStyle(POSFilledButtonStyle(size: .extraSmall)) } }, bottomContent: { @@ -79,7 +80,7 @@ private extension POSOrderDetailsView { .font(.posBodyLargeBold) .foregroundStyle(Color.posOnSurface) - VStack(spacing: POSSpacing.none) { + VStack(spacing: POSSpacing.small) { ForEach(Array(order.lineItems.enumerated()), id: \.element.itemID) { index, item in productRow(item: item) @@ -107,16 +108,15 @@ private extension POSOrderDetailsView { taxTotalRow(order) divider - mainTotalRow(order) divider - paidAmountRow(order) - divider - - refundsSection(order) + if !order.refunds.isEmpty { + divider + refundsSection(order) + } } } .padding(POSPadding.medium) @@ -146,7 +146,6 @@ private extension POSOrderDetailsView { Spacer().frame(height: POSSpacing.xSmall) POSOrderBadgeView(order: order) } - .padding(.top, POSSpacing.xSmall) .multilineTextAlignment(.leading) } @@ -263,15 +262,13 @@ private extension POSOrderDetailsView { @ViewBuilder func refundsSection(_ order: POSOrder) -> some View { - if !order.refunds.isEmpty { - ForEach(order.refunds, id: \.refundID) { refund in - refundRow(refund: refund) - divider - } + ForEach(order.refunds, id: \.refundID) { refund in + refundRow(refund: refund) + divider + } - if let netAmount = order.formattedNetAmount { - netPaymentRow(netAmount: netAmount) - } + if let netAmount = order.formattedNetAmount { + netPaymentRow(netAmount: netAmount) } } From c0316f6942601f775db153c3e78e44befb2647c7 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:33:36 +0300 Subject: [PATCH 18/31] Update POSOrderDetailsView background color --- .../Classes/POS/Presentation/Orders/POSDetailsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 8903199fe12..0ca296b24d9 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -59,7 +59,7 @@ struct POSOrderDetailsView: View { .padding(.bottom, POSPadding.medium) } } - .background(Color.posSurfaceBright) + .background(Color.posSurface) .navigationBarHidden(true) .posFullScreenCover(isPresented: $isShowingEmailReceiptView) { POSSendReceiptView(isShowingSendReceiptView: $isShowingEmailReceiptView) { email in From 2b0b0c76a5a3f03b40fc6142126f815d2d680d59 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:13:23 +0300 Subject: [PATCH 19/31] Update POSOrderDetailsLoadingView --- .../Orders/POSOrderDetailsLoadingView.swift | 145 ++++++++++-------- 1 file changed, 81 insertions(+), 64 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift index 144700da5f7..4b0f8700848 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift @@ -2,20 +2,24 @@ import SwiftUI struct POSOrderDetailsLoadingView: View { var body: some View { - VStack(spacing: 0) { + VStack(spacing: POSSpacing.none) { POSPageHeaderView( title: Localization.orderDetailsTitle, backButtonConfiguration: nil, + alignment: .firstTextBaseline, trailingContent: { shimmeringHeaderTrailingContent }, bottomContent: { shimmeringHeaderBottomContent } ) + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) ScrollView { VStack(alignment: .leading, spacing: POSSpacing.medium) { shimmeringProductsSection shimmeringTotalsSection } + .padding(.top, POSPadding.xSmall) .padding(.horizontal, POSPadding.medium) + .padding(.bottom, POSPadding.medium) } } .background(Color.posSurface) @@ -26,31 +30,34 @@ struct POSOrderDetailsLoadingView: View { @ViewBuilder private var shimmeringHeaderTrailingContent: some View { - GeometryReader { geometry in - HStack { - Spacer() - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.3, height: 16) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - } + HStack { + Spacer() + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.shortRowWidth, height: POSPadding.large) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() } - .frame(height: 16) } @ViewBuilder private var shimmeringHeaderBottomContent: some View { - GeometryReader { geometry in - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.5, height: 16) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - } + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.longRowWidth, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + + Spacer().frame(height: POSSpacing.xSmall) + + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.shortRowWidth, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() } - .frame(height: 16) + .multilineTextAlignment(.leading) } @ViewBuilder @@ -61,8 +68,12 @@ struct POSOrderDetailsLoadingView: View { .foregroundStyle(Color.posOnSurface) VStack(spacing: POSSpacing.small) { - ForEach(0..<2, id: \.self) { _ in + ForEach(0..<2, id: \.self) { index in shimmeringProductRow + + if index < 1 { + divider + } } } } @@ -73,39 +84,35 @@ struct POSOrderDetailsLoadingView: View { @ViewBuilder private var shimmeringProductRow: some View { - GeometryReader { geometry in - HStack(alignment: .top, spacing: POSSpacing.medium) { + HStack(alignment: .center, spacing: POSSpacing.medium) { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: POSPadding.xxLarge, height: POSPadding.xxLarge) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + + VStack(alignment: .leading, spacing: POSSpacing.xSmall) { Rectangle() .fill(Color.posOnSurfaceVariantLowest) - .frame(width: 40, height: 40) + .frame(width: Constants.longRowWidth, height: POSPadding.medium) .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) .shimmering() - VStack(alignment: .leading, spacing: POSSpacing.xSmall) { - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.45, height: 20) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.35, height: 16) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - } - - Spacer() - Rectangle() .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.2, height: 20) - .clipShape(RoundedRectangle(cornerRadius: 4)) + .frame(width: Constants.shortRowWidth, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) .shimmering() } - .padding(.vertical, POSPadding.small) + + Spacer() + + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.shortRowWidth, height: POSPadding.medium) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() } - .frame(height: 60) } @ViewBuilder @@ -120,10 +127,10 @@ struct POSOrderDetailsLoadingView: View { shimmeringTotalsRow shimmeringTotalsRow - Divider() - .background(Color.posSurfaceDim) - + divider shimmeringTotalsRow + + divider shimmeringTotalsRow } } @@ -134,25 +141,35 @@ struct POSOrderDetailsLoadingView: View { @ViewBuilder private var shimmeringTotalsRow: some View { - GeometryReader { geometry in - HStack { - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.3, height: 20) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - - Spacer() - - Rectangle() - .fill(Color.posOnSurfaceVariantLowest) - .frame(width: geometry.size.width * 0.25, height: 20) - .clipShape(RoundedRectangle(cornerRadius: 4)) - .shimmering() - } + HStack { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.mediumRowWidth, height: POSPadding.large) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + + Spacer() + + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.shortRowWidth, height: POSPadding.large) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() } - .frame(height: 20) } + + @ViewBuilder + private var divider: some View { + Divider() + .overlay(Color.posOutlineVariant.opacity(0.5)) + .padding(.vertical, POSSpacing.small) + } +} + +private enum Constants { + static let longRowWidth: CGFloat = 120 + static let mediumRowWidth: CGFloat = 100 + static let shortRowWidth: CGFloat = 80 } private enum Localization { From ff2292dd704f7717479d7d318562459443a09a59 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:13:44 +0300 Subject: [PATCH 20/31] Limit dynamicTypeSize growth for POSOrderDetailsView header --- WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift index 0ca296b24d9..e5fbc6f8930 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSDetailsView.swift @@ -46,6 +46,7 @@ struct POSOrderDetailsView: View { headerBottomContent(for: order) } ) + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) ScrollView { VStack(alignment: .leading, spacing: POSSpacing.medium) { From 8a90d0d4d8d950bc02eb8167fa38da1d6ffc2408 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:13:58 +0300 Subject: [PATCH 21/31] Use static height for POSOrderListView ghost row --- .../Classes/POS/Presentation/Orders/POSOrderListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index e3fd7502fe5..15f24e014af 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -251,7 +251,7 @@ private struct POSGhostOrderRowView: View { } .padding(.horizontal, POSPadding.medium * (1 / scale)) .padding(.vertical, POSPadding.medium * (1 / scale)) - .frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, maxHeight: .infinity, alignment: .leading) + .frame(height: minHeight, alignment: .leading) .background(Color.posSurfaceContainerLowest) .posItemCardBorderStyles() .geometryGroup() From 23d535aa3360cb63fe6f8a1c1d1e21340a4947f8 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:51:28 +0300 Subject: [PATCH 22/31] Change selection stile to rounded stroke --- .../POS/Presentation/Orders/POSOrderListView.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index d9adf97d414..2173c42c1a8 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -201,8 +201,14 @@ private struct POSOrderRowView: View { .padding(.horizontal, POSPadding.medium * (1 / scale)) .padding(.vertical, POSPadding.medium * (1 / scale)) .frame(maxWidth: .infinity, minHeight: dynamicTypeSize.isAccessibilitySize ? nil : minHeight, alignment: .leading) - .background(isSelected ? Color.posSurfaceDim : Color.posSurfaceContainerLowest) + .background(Color.posSurfaceContainerLowest) .posItemCardBorderStyles() + .overlay { + if isSelected { + RoundedRectangle(cornerRadius: POSCornerRadiusStyle.medium.value) + .stroke(Color.posOnSurface, lineWidth: 2) + } + } } @ViewBuilder From 982b2191c301947d36c505d40ca98304800be31f Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:06:10 +0300 Subject: [PATCH 23/31] Add more previews for the order list and order details view --- .../Orders/POSOrderDetailsView.swift | 28 ++- .../Orders/POSOrderListView.swift | 6 +- .../Classes/POS/Utils/PreviewHelpers.swift | 226 ++++++++++-------- 3 files changed, 160 insertions(+), 100 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift index 81971d91661..8deb7455643 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift @@ -471,7 +471,7 @@ private enum Localization { } #if DEBUG -#Preview("Order Details") { +#Preview("Order Details - Completed") { POSOrderDetailsView( order: POSPreviewHelpers.makePreviewOrder(), onBack: {} @@ -479,11 +479,35 @@ private enum Localization { .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) } -#Preview("Order Details with Refund") { +#Preview("Order Details - Refunded") { POSOrderDetailsView( order: POSPreviewHelpers.makePreviewOrderWithRefund(), onBack: {} ) .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) } + +#Preview("Order Details - Failed") { + POSOrderDetailsView( + order: POSPreviewHelpers.makePreviewFailedOrder(), + onBack: {} + ) + .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) +} + +#Preview("Order Details - Without Email") { + POSOrderDetailsView( + order: POSPreviewHelpers.makePreviewOrderWithoutEmail(), + onBack: {} + ) + .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) +} + +#Preview("Order Details - With Net Payment") { + POSOrderDetailsView( + order: POSPreviewHelpers.makePreviewOrderWithNetPayment(), + onBack: {} + ) + .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .empty)) +} #endif diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 2173c42c1a8..32692e11ff9 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -384,7 +384,7 @@ private enum Localization { NavigationSplitView(columnVisibility: .constant(.all)) { POSOrderListView(onClose: {}) .navigationSplitViewColumnWidth(450) - .environment(POSPreviewHelpers.makePreviewOrdersModel(state: .loaded([POSPreviewHelpers.makePreviewOrder()], hasMoreItems: false))) + .environment(POSPreviewHelpers.makePreviewOrdersModel(state: POSPreviewHelpers.loadedState())) } detail: { Text("Detail View") } @@ -426,7 +426,7 @@ private enum Localization { POSOrderListView(onClose: {}) .navigationSplitViewColumnWidth(450) .environment(POSPreviewHelpers.makePreviewOrdersModel( - state: .inlineError([POSPreviewHelpers.makePreviewOrder()], + state: .inlineError(POSPreviewHelpers.makePreviewOrders(), error: .errorOnLoadingOrders(), context: .refresh) )) @@ -440,7 +440,7 @@ private enum Localization { POSOrderListView(onClose: {}) .navigationSplitViewColumnWidth(450) .environment(POSPreviewHelpers.makePreviewOrdersModel( - state: .inlineError([POSPreviewHelpers.makePreviewOrder()], + state: .inlineError(POSPreviewHelpers.makePreviewOrders(), error: .errorOnLoadingOrdersNextPage(), context: .pagination) )) diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index a6e250cebb2..9e409c3ff22 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -262,6 +262,20 @@ struct POSPreviewHelpers { receiptSender: POSReceiptSenderPreview()) } + static func makePreviewOrders() -> [POSOrder] { + return [ + makePreviewOrder(), + makePreviewFailedOrder(), + makePreviewOrderWithRefund(), + makePreviewOrderWithoutEmail(), + makePreviewOrderWithNetPayment() + ] + } + + static func loadedState() -> POSOrderListState { + return .loaded(makePreviewOrders(), hasMoreItems: false) + } + static func makePreviewOrder() -> POSOrder { return POSOrder( id: 1, @@ -304,18 +318,18 @@ struct POSPreviewHelpers { static func makePreviewOrderWithRefund() -> POSOrder { return POSOrder( - id: 2, - number: "1002", - dateCreated: Date().addingTimeInterval(-3600), - status: .completed, + id: 3, + number: "1003", + dateCreated: Date().addingTimeInterval(-7200), + status: .refunded, formattedTotal: "$89.50", formattedSubtotal: "$89.96", - customerEmail: "customer.with.refund@example.com", + customerEmail: "very.long.customer.email@withverylongdomainname.com", paymentMethodID: "woocommerce_payments", paymentMethodTitle: "WooCommerce In-Person Payments", lineItems: [ POSOrderItem( - itemID: 3, + itemID: 4, name: "Artisan Chocolate Box", quantity: 3.0, formattedPrice: "$19.99", @@ -324,15 +338,15 @@ struct POSPreviewHelpers { attributes: [] ), POSOrderItem( - itemID: 4, + itemID: 5, name: "Gourmet Cookie Set - Mixed", quantity: 1.0, formattedPrice: "$29.99", formattedTotal: "$29.99", imageSrc: nil, attributes: [ - OrderItemAttribute(metaID: 3, name: "Flavor", value: "Mixed"), - OrderItemAttribute(metaID: 4, name: "Packaging", value: "Gift Box") + OrderItemAttribute(metaID: 4, name: "Flavor", value: "Mixed"), + OrderItemAttribute(metaID: 5, name: "Packaging", value: "Gift Box") ] ) ], @@ -349,98 +363,120 @@ struct POSPreviewHelpers { formattedNetAmount: "$69.51" ) } + + static func makePreviewFailedOrder() -> POSOrder { + return POSOrder( + id: 2, + number: "1002", + dateCreated: Date().addingTimeInterval(-3600), + status: .failed, + formattedTotal: "$129.99", + formattedSubtotal: "$120.00", + customerEmail: nil, + paymentMethodID: "woocommerce_payments", + paymentMethodTitle: "WooCommerce In-Person Payments", + lineItems: [ + POSOrderItem( + itemID: 3, + name: "Wireless Headphones", + quantity: 1.0, + formattedPrice: "$120.00", + formattedTotal: "$120.00", + imageSrc: nil, + attributes: [ + OrderItemAttribute(metaID: 3, name: "Color", value: "Black") + ] + ) + ], + refunds: [], + formattedDiscountTotal: "$0.00", + formattedTotalTax: "$9.99", + formattedPaymentTotal: "$0.00", + formattedNetAmount: nil + ) + } + + static func makePreviewOrderWithoutEmail() -> POSOrder { + return POSOrder( + id: 4, + number: "1004", + dateCreated: Date().addingTimeInterval(-10800), + status: .completed, + formattedTotal: "$24.99", + formattedSubtotal: "$22.99", + customerEmail: nil, + paymentMethodID: "cod", + paymentMethodTitle: "Cash on Delivery", + lineItems: [ + POSOrderItem( + itemID: 6, + name: "Coffee Mug", + quantity: 1.0, + formattedPrice: "$22.99", + formattedTotal: "$22.99", + imageSrc: nil, + attributes: [] + ) + ], + refunds: [], + formattedDiscountTotal: "$0.00", + formattedTotalTax: "$2.00", + formattedPaymentTotal: "$24.99", + formattedNetAmount: nil + ) + } + + static func makePreviewOrderWithNetPayment() -> POSOrder { + return POSOrder( + id: 5, + number: "1005", + dateCreated: Date().addingTimeInterval(-14400), + status: .processing, + formattedTotal: "$156.47", + formattedSubtotal: "$145.00", + customerEmail: "john.doe@example.com", + paymentMethodID: "woocommerce_payments", + paymentMethodTitle: "WooCommerce In-Person Payments", + lineItems: [ + POSOrderItem( + itemID: 7, + name: "Leather Wallet", + quantity: 2.0, + formattedPrice: "$45.00", + formattedTotal: "$90.00", + imageSrc: nil, + attributes: [ + OrderItemAttribute(metaID: 6, name: "Material", value: "Genuine Leather") + ] + ), + POSOrderItem( + itemID: 8, + name: "Sunglasses", + quantity: 1.0, + formattedPrice: "$55.00", + formattedTotal: "$55.00", + imageSrc: nil, + attributes: [ + OrderItemAttribute(metaID: 7, name: "Frame", value: "Metal"), + OrderItemAttribute(metaID: 8, name: "Lens", value: "Polarized") + ] + ) + ], + refunds: [], + formattedDiscountTotal: "-$10.00", + formattedTotalTax: "$11.47", + formattedPaymentTotal: "$156.47", + formattedNetAmount: "$153.50" + ) + } } // MARK: - Preview Orders Controller final class POSConfigurablePreviewOrderListController: POSSearchingOrderListControllerProtocol { let ordersViewState: POSOrderListState - init(state: POSOrderListState? = nil) { - let orders = [ - POSOrder( - id: 1, - number: "1001", - dateCreated: Date(), - status: .completed, - formattedTotal: "$45.75", - formattedSubtotal: "$40.99", - customerEmail: "customer@example.com", - paymentMethodID: "cod", - paymentMethodTitle: "Cash on Delivery", - lineItems: [ - POSOrderItem(itemID: 1, - name: "Premium Coffee Beans", - quantity: 2.0, - formattedPrice: "$12.50", - formattedTotal: "$25.00", - imageSrc: nil, - attributes: []), - POSOrderItem( - itemID: 2, - name: "Organic Tea - Earl Grey", - quantity: 1.0, - formattedPrice: "$15.99", - formattedTotal: "$15.99", - imageSrc: nil, - attributes: [ - OrderItemAttribute(metaID: 1, name: "Size", value: "Large"), - OrderItemAttribute(metaID: 2, name: "Type", value: "Loose Leaf") - ] - ) - ], - refunds: [], - formattedDiscountTotal: "-$5.24", - formattedTotalTax: "$4.75", - formattedPaymentTotal: "$45.75", - formattedNetAmount: nil - ), - POSOrder( - id: 2, - number: "1002", - dateCreated: Date().addingTimeInterval(-3600), - status: .processing, - formattedTotal: "$89.50", - formattedSubtotal: "$89.96", - customerEmail: "very.long.customer.email@withverylongdomainname.com", - paymentMethodID: "woocommerce_payments", - paymentMethodTitle: "WooCommerce Payments", - lineItems: [ - POSOrderItem( - itemID: 3, - name: "Artisan Chocolate Box", - quantity: 3.0, - formattedPrice: "$19.99", - formattedTotal: "$59.97", - imageSrc: nil, - attributes: [] - ), - POSOrderItem( - itemID: 4, - name: "Gourmet Cookie Set - Mixed", - quantity: 1.0, - formattedPrice: "$29.99", - formattedTotal: "$29.99", - imageSrc: nil, - attributes: [ - OrderItemAttribute(metaID: 3, name: "Flavor", value: "Mixed"), - OrderItemAttribute(metaID: 4, name: "Packaging", value: "Gift Box") - ] - ) - ], - refunds: [ - POSOrderRefund( - refundID: 1, - formattedTotal: "-$19.99", - reason: "Customer requested partial refund" - ) - ], - formattedDiscountTotal: "-$15.00", - formattedTotalTax: "$8.95", - formattedPaymentTotal: "$89.50", - formattedNetAmount: "$69.51" - ) - ] - self.ordersViewState = state ?? .loaded(orders, hasMoreItems: false) + init(state: POSOrderListState) { + self.ordersViewState = state } var selectedOrder: POSOrder? { From cb21331db7bc48e05748d76c91c60843c38b197d Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:20:29 +0300 Subject: [PATCH 24/31] Set unfocusedBorderColor for POSOrderListView --- .../Item Search/POSSearchTextFieldStyle.swift | 4 +++- .../Presentation/Orders/POSOrderListView.swift | 1 + .../Classes/POS/Utils/POSEnvironmentKeys.swift | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift b/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift index b9708d6f3c8..1179f03e172 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift @@ -3,6 +3,8 @@ import WooFoundation /// Text field style for search fields that includes a magnifier icon and clear button struct POSSearchTextFieldStyle: TextFieldStyle { + @Environment(\.posSearchTextFieldUnfocusedBorderColor) private var unfocusedBorderColor + private let focused: Bool @Binding private var searchTerm: String @ScaledMetric private var searchFieldHeight: CGFloat = 56.0 @@ -17,7 +19,7 @@ struct POSSearchTextFieldStyle: TextFieldStyle { .textFieldStyle(WooRoundedBorderTextFieldStyle( focused: focused, focusedBorderColor: .posPrimary, - unfocusedBorderColor: .posSurfaceBright, + unfocusedBorderColor: unfocusedBorderColor, backgroundColor: .posSurfaceBright, cornerRadius: POSCornerRadiusStyle.medium.value, insets: EdgeInsets(top: POSPadding.small, leading: POSPadding.medium, bottom: POSPadding.small, trailing: POSPadding.medium), diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 32692e11ff9..42bfd2ca468 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -56,6 +56,7 @@ struct POSOrderListView: View { setSearch(false) } ) + .posSearchTextFieldUnfocusedBorderColor(.posOutlineVariant) .matchedGeometryEffect(id: Constants.searchControlID, in: searchTransition) .transition(.opacity.combined(with: .move(edge: .leading))) .onChange(of: searchTerm) { _, newValue in diff --git a/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift b/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift index 78e9a6cefea..def953c70df 100644 --- a/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift +++ b/WooCommerce/Classes/POS/Utils/POSEnvironmentKeys.swift @@ -52,6 +52,11 @@ struct POSExternalViewKey: EnvironmentKey { static let defaultValue: POSExternalViewProviding = EmptyPOSExternalView() } +/// Environment key for POS search text field unfocused border color +struct POSSearchTextFieldUnfocusedBorderColorKey: EnvironmentKey { + static let defaultValue: Color = .posSurfaceBright +} + extension EnvironmentValues { var posAnalytics: POSAnalyticsProviding { get { self[POSAnalyticsKey.self] } @@ -82,6 +87,19 @@ extension EnvironmentValues { get { self[POSExternalViewKey.self] } set { self[POSExternalViewKey.self] = newValue } } + + var posSearchTextFieldUnfocusedBorderColor: Color { + get { self[POSSearchTextFieldUnfocusedBorderColorKey.self] } + set { self[POSSearchTextFieldUnfocusedBorderColorKey.self] = newValue } + } +} + +// MARK: - View Modifiers + +extension View { + func posSearchTextFieldUnfocusedBorderColor(_ color: Color) -> some View { + environment(\.posSearchTextFieldUnfocusedBorderColor, color) + } } // MARK: - Empty Default Values From 147be523d16928df9bfac3553266d330e25b707b Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:49:03 +0300 Subject: [PATCH 25/31] Improve POSPageHeaderView element alignment in order details --- .../Orders/POSOrderDetailsLoadingView.swift | 1 - .../Orders/POSOrderDetailsView.swift | 34 ++++++++------- .../POSPageHeaderBackButton.swift | 3 +- .../Reusable Views/POSPageHeaderView.swift | 43 +++++++++++-------- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift index 4b0f8700848..e92beef649c 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift @@ -6,7 +6,6 @@ struct POSOrderDetailsLoadingView: View { POSPageHeaderView( title: Localization.orderDetailsTitle, backButtonConfiguration: nil, - alignment: .firstTextBaseline, trailingContent: { shimmeringHeaderTrailingContent }, bottomContent: { shimmeringHeaderBottomContent } ) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift index 8deb7455643..a71e274468d 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift @@ -29,8 +29,7 @@ struct POSOrderDetailsView: View { VStack(spacing: POSSpacing.none) { POSPageHeaderView( title: "#\(order.number)", - backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack, buttonIcon: "xmark") : nil, - alignment: .firstTextBaseline, + backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack) : nil, trailingContent: { if actions.isNotEmpty { actionsSection(actions) @@ -40,6 +39,8 @@ struct POSOrderDetailsView: View { headerBottomContent(for: order) } ) + .posHeaderBackButtonPadding(POSPadding.none) + .fixedSize(horizontal: false, vertical: true) .dynamicTypeSize(...DynamicTypeSize.xxxLarge) ScrollView { @@ -149,6 +150,7 @@ private extension POSOrderDetailsView { Spacer().frame(height: POSSpacing.xSmall) POSOrderBadgeView(order: order) } + .padding(.top, POSSpacing.xSmall) .multilineTextAlignment(.leading) } @@ -350,23 +352,25 @@ private extension POSOrderDetailsView { @ViewBuilder func actionsSection(_ actions: [POSOrderDetailsAction]) -> some View { - HStack { - ForEach(actions) { action in - Button(action: { - switch action { - case .emailReceipt: - analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsEmailReceiptTapped()) - isShowingEmailReceiptView = true + VStack { + HStack { + ForEach(actions) { action in + Button(action: { + switch action { + case .emailReceipt: + analytics.track(event: WooAnalyticsEvent.PointOfSale.orderDetailsEmailReceiptTapped()) + isShowingEmailReceiptView = true + } + }) { + Text(Localization.emailReceiptActionTitle) + .lineLimit(1) + .minimumScaleFactor(0.5) } - }) { - Text(Localization.emailReceiptActionTitle) - .lineLimit(1) - .minimumScaleFactor(0.5) + .buttonStyle(POSFilledButtonStyle(size: .extraSmall)) } - .buttonStyle(POSFilledButtonStyle(size: .extraSmall)) } + Spacer() } - .padding(.vertical) } } diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift index ae81cac56ac..750ba16ce89 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift @@ -3,6 +3,7 @@ import SwiftUI struct POSPageHeaderBackButton: View { private let configuration: POSPageHeaderBackButtonConfiguration @Environment(\.posHeaderBackButtonIcon) private var environmentIcon + @Environment(\.posHeaderBackButtonPadding) private var backButtonPadding init(configuration: POSPageHeaderBackButtonConfiguration) { self.configuration = configuration @@ -18,7 +19,7 @@ struct POSPageHeaderBackButton: View { .font(.posButtonSymbolLarge) .dynamicTypeSize(...POSHeaderLayoutConstants.maximumDynamicTypeSize) .foregroundColor(configuration.state == .disabled ? .posOnSurfaceVariantLowest : .posOnSurface) - .padding(.horizontal, Constants.backButtonHorizontalPadding) + .padding(.horizontal, backButtonPadding) } .disabled(configuration.state == .disabled || configuration.state == .shimmering) .if(configuration.state == .shimmering) { view in diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift index 9983b8cafd0..08130f1ae9d 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift @@ -50,7 +50,9 @@ struct POSPageHeaderView LeadingContent = { EmptyView() }, @ViewBuilder trailingContent: () -> TrailingContent = { EmptyView() }, @ViewBuilder bottomContent: () -> BottomContent = { EmptyView() } @@ -71,17 +72,11 @@ struct POSPageHeaderView LeadingContent = { EmptyView() }, @ViewBuilder trailingContent: () -> TrailingContent = { EmptyView() }, @ViewBuilder bottomContent: () -> BottomContent = { EmptyView() } @@ -91,24 +86,18 @@ struct POSPageHeaderView some View { environment(\.posHeaderBackButtonIcon, systemName) } + + /// Sets the back button horizontal padding for all POSPageHeaderView instances in the view hierarchy. + /// - Parameter padding: The horizontal padding value + /// - Returns: A view with the padding environment value set + func posHeaderBackButtonPadding(_ padding: CGFloat) -> some View { + environment(\.posHeaderBackButtonPadding, padding) + } } // MARK: - Previews From 8e2b54769165be6e6d469367a4cc5ce3284b5964 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:10:17 +0300 Subject: [PATCH 26/31] Update POSOrderDetailsLoadingView to show shimmering instead of order title --- .../Orders/POSOrderDetailsLoadingView.swift | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift index e92beef649c..e64fe6882df 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsLoadingView.swift @@ -4,8 +4,9 @@ struct POSOrderDetailsLoadingView: View { var body: some View { VStack(spacing: POSSpacing.none) { POSPageHeaderView( - title: Localization.orderDetailsTitle, + title: "", backButtonConfiguration: nil, + leadingContent: { shimmeringHeaderLeadingContent }, trailingContent: { shimmeringHeaderTrailingContent }, bottomContent: { shimmeringHeaderBottomContent } ) @@ -27,6 +28,18 @@ struct POSOrderDetailsLoadingView: View { // MARK: - Shimmer Components + @ViewBuilder + private var shimmeringHeaderLeadingContent: some View { + HStack { + Rectangle() + .fill(Color.posOnSurfaceVariantLowest) + .frame(width: Constants.longRowWidth, height: POSPadding.xxLarge) + .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) + .shimmering() + Spacer() + } + } + @ViewBuilder private var shimmeringHeaderTrailingContent: some View { HStack { @@ -56,6 +69,7 @@ struct POSOrderDetailsLoadingView: View { .clipShape(RoundedRectangle(cornerRadius: POSCornerRadiusStyle.small.value)) .shimmering() } + .padding(.top, POSPadding.medium) .multilineTextAlignment(.leading) } @@ -172,12 +186,6 @@ private enum Constants { } private enum Localization { - static let orderDetailsTitle = NSLocalizedString( - "pos.orderDetailsLoadingView.title", - value: "Order", - comment: "Title for the order details screen when no specific order is selected" - ) - static let productsTitle = NSLocalizedString( "pos.orderDetailsLoadingView.productsTitle", value: "Products", From 4db0e8bf361f6685e7e63898389dab85b4d781f4 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:19:12 +0300 Subject: [PATCH 27/31] Animate order details update --- .../POS/Presentation/Orders/POSOrdersView.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrdersView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrdersView.swift index e446ee5d47b..2ffb1edb147 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrdersView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrdersView.swift @@ -114,13 +114,18 @@ private struct CustomNavigationSplitView Date: Wed, 1 Oct 2025 16:27:14 +0300 Subject: [PATCH 28/31] Show product attributes view --- .../Classes/POS/Presentation/Orders/POSOrderDetailsView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift index a71e274468d..98bc55c8998 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift @@ -185,6 +185,10 @@ private extension POSOrderDetailsView { .foregroundStyle(Color.posOnSurface) .fixedSize(horizontal: false, vertical: true) + if !item.attributes.isEmpty { + productAttributesView(item.attributes) + } + Text(Localization.quantityLabel(item.quantity.intValue, item.formattedPrice)) .font(.posBodySmallRegular()) From 0e94d89c64d85eb464142caffa9be935652e5e29 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:33:57 +0300 Subject: [PATCH 29/31] Localize orderTitle --- .../POS/Presentation/Orders/POSOrderDetailsView.swift | 11 +---------- .../POS/Presentation/Orders/POSOrderListView.swift | 11 ++++++++++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift index 98bc55c8998..0244ce864d7 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderDetailsView.swift @@ -28,7 +28,7 @@ struct POSOrderDetailsView: View { var body: some View { VStack(spacing: POSSpacing.none) { POSPageHeaderView( - title: "#\(order.number)", + title: POSOrderListView.Localization.orderTitle(order.number), backButtonConfiguration: shouldShowBackButton ? .init(state: .enabled, action: onBack) : nil, trailingContent: { if actions.isNotEmpty { @@ -390,15 +390,6 @@ private extension POSOrderDetailsView { // MARK: - Localization private enum Localization { - static func orderTitle(_ orderNumber: String) -> String { - let format = NSLocalizedString( - "pos.orderDetailsView.orderTitle", - value: "Order #%1$@", - comment: "Order title with order number. %1$@ is the order number." - ) - return String(format: format, orderNumber) - } - static let productsTitle = NSLocalizedString( "pos.orderDetailsView.productsTitle", value: "Products", diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 42bfd2ca468..73988ebdd5b 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -215,7 +215,7 @@ private struct POSOrderRowView: View { @ViewBuilder private var orderHeaderRow: some View { HStack(alignment: .center) { - Text("#\(order.number)") + Text(POSOrderListView.Localization.orderTitle(order.number)) .font(.posBodySmallBold) .foregroundStyle(Color.posOnSurface) .fixedSize(horizontal: false, vertical: true) @@ -369,6 +369,15 @@ extension POSOrderListView { "pos.orderListView.ordersTitle", value: "Orders", comment: "Title at the header for the Orders view.") + + static func orderTitle(_ orderNumber: String) -> String { + let format = NSLocalizedString( + "pos.orderListView.orderTitle", + value: "#%1$@", + comment: "%1$@ is the order number. # symbol is shown as a prefix to a number." + ) + return String(format: format, orderNumber) + } } } From 6d28c2738fd7d70ddc9d77f76b0c50f005f71650 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:49:49 +0300 Subject: [PATCH 30/31] Address periphery warnings --- .../Yosemite/Model/Copiable/Models+Copiable.generated.swift | 3 --- .../Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift | 3 --- .../Yosemite/PointOfSale/OrderList/POSOrderMapper.swift | 1 - .../Presentation/Item Search/POSSearchTextFieldStyle.swift | 1 + .../Reusable Views/POSPageHeaderBackButton.swift | 2 -- .../POS/Presentation/Reusable Views/POSPageHeaderView.swift | 1 + WooCommerce/Classes/POS/Utils/PreviewHelpers.swift | 5 ----- .../WooCommerceTests/POS/Mocks/MockPOSOrderListService.swift | 5 ----- .../WooCommerceTests/POS/Models/POSOrderListModelTests.swift | 1 - .../WooCommerceTests/POS/Models/POSOrderListStateTests.swift | 4 ++-- 10 files changed, 4 insertions(+), 22 deletions(-) diff --git a/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift b/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift index 387e101dbae..a8b5fd70c75 100644 --- a/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift +++ b/Modules/Sources/Yosemite/Model/Copiable/Models+Copiable.generated.swift @@ -66,7 +66,6 @@ extension Yosemite.POSOrder { formattedTotal: CopiableProp = .copy, formattedSubtotal: CopiableProp = .copy, customerEmail: NullableCopiableProp = .copy, - paymentMethodID: CopiableProp = .copy, paymentMethodTitle: CopiableProp = .copy, lineItems: CopiableProp<[POSOrderItem]> = .copy, refunds: CopiableProp<[POSOrderRefund]> = .copy, @@ -82,7 +81,6 @@ extension Yosemite.POSOrder { let formattedTotal = formattedTotal ?? self.formattedTotal let formattedSubtotal = formattedSubtotal ?? self.formattedSubtotal let customerEmail = customerEmail ?? self.customerEmail - let paymentMethodID = paymentMethodID ?? self.paymentMethodID let paymentMethodTitle = paymentMethodTitle ?? self.paymentMethodTitle let lineItems = lineItems ?? self.lineItems let refunds = refunds ?? self.refunds @@ -99,7 +97,6 @@ extension Yosemite.POSOrder { formattedTotal: formattedTotal, formattedSubtotal: formattedSubtotal, customerEmail: customerEmail, - paymentMethodID: paymentMethodID, paymentMethodTitle: paymentMethodTitle, lineItems: lineItems, refunds: refunds, diff --git a/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift b/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift index 7f6f18bf456..17ea25680be 100644 --- a/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift +++ b/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift @@ -15,7 +15,6 @@ public struct POSOrder: Equatable, Hashable, GeneratedCopiable { public let formattedTotal: String public let formattedSubtotal: String public let customerEmail: String? - public let paymentMethodID: String public let paymentMethodTitle: String public let lineItems: [POSOrderItem] public let refunds: [POSOrderRefund] @@ -31,7 +30,6 @@ public struct POSOrder: Equatable, Hashable, GeneratedCopiable { formattedTotal: String, formattedSubtotal: String, customerEmail: String? = nil, - paymentMethodID: String, paymentMethodTitle: String, lineItems: [POSOrderItem] = [], refunds: [POSOrderRefund] = [], @@ -46,7 +44,6 @@ public struct POSOrder: Equatable, Hashable, GeneratedCopiable { self.formattedTotal = formattedTotal self.formattedSubtotal = formattedSubtotal self.customerEmail = customerEmail - self.paymentMethodID = paymentMethodID self.paymentMethodTitle = paymentMethodTitle self.lineItems = lineItems self.refunds = refunds diff --git a/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderMapper.swift b/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderMapper.swift index e55ae0565e4..d724093ff74 100644 --- a/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderMapper.swift +++ b/Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderMapper.swift @@ -41,7 +41,6 @@ struct POSOrderMapper { formattedTotal: currencyFormatter.formatAmount(order.total, with: order.currency) ?? "", formattedSubtotal: order.subtotalValue(currencyFormatter: currencyFormatter), customerEmail: customerEmail, - paymentMethodID: order.paymentMethodID, paymentMethodTitle: order.paymentMethodTitle, lineItems: posLineItems, refunds: posRefunds, diff --git a/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift b/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift index 1179f03e172..0e02e43cce5 100644 --- a/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift +++ b/WooCommerce/Classes/POS/Presentation/Item Search/POSSearchTextFieldStyle.swift @@ -3,6 +3,7 @@ import WooFoundation /// Text field style for search fields that includes a magnifier icon and clear button struct POSSearchTextFieldStyle: TextFieldStyle { + /// periphery: ignore - used in the body @Environment(\.posSearchTextFieldUnfocusedBorderColor) private var unfocusedBorderColor private let focused: Bool diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift index 750ba16ce89..6b6f95d5d3e 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderBackButton.swift @@ -31,7 +31,5 @@ struct POSPageHeaderBackButton: View { private extension POSPageHeaderBackButton { enum Constants { static let defaultBackButtonIcon = "chevron.backward" - /// Icon container is 48x48, chevron icon width is 24px. Therefore, adding a horizontal padding (48-24)/2 = 12. - static let backButtonHorizontalPadding: CGFloat = 12 } } diff --git a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift index 08130f1ae9d..abf487622cc 100644 --- a/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift +++ b/WooCommerce/Classes/POS/Presentation/Reusable Views/POSPageHeaderView.swift @@ -181,6 +181,7 @@ struct POSHeaderBackButtonIconKey: EnvironmentKey { } struct POSHeaderBackButtonPaddingKey: EnvironmentKey { + /// Icon container is 48x48, chevron icon width is 24px. Therefore, adding a horizontal padding (48-24)/2 = 12. static let defaultValue: CGFloat = POSPadding.large / 2 } diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index 9e409c3ff22..b576fffac9b 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -285,7 +285,6 @@ struct POSPreviewHelpers { formattedTotal: "$45.75", formattedSubtotal: "$41.99", customerEmail: "customer@example.com", - paymentMethodID: "cod", paymentMethodTitle: "Cash on Delivery", lineItems: [ POSOrderItem(itemID: 1, @@ -325,7 +324,6 @@ struct POSPreviewHelpers { formattedTotal: "$89.50", formattedSubtotal: "$89.96", customerEmail: "very.long.customer.email@withverylongdomainname.com", - paymentMethodID: "woocommerce_payments", paymentMethodTitle: "WooCommerce In-Person Payments", lineItems: [ POSOrderItem( @@ -373,7 +371,6 @@ struct POSPreviewHelpers { formattedTotal: "$129.99", formattedSubtotal: "$120.00", customerEmail: nil, - paymentMethodID: "woocommerce_payments", paymentMethodTitle: "WooCommerce In-Person Payments", lineItems: [ POSOrderItem( @@ -405,7 +402,6 @@ struct POSPreviewHelpers { formattedTotal: "$24.99", formattedSubtotal: "$22.99", customerEmail: nil, - paymentMethodID: "cod", paymentMethodTitle: "Cash on Delivery", lineItems: [ POSOrderItem( @@ -435,7 +431,6 @@ struct POSPreviewHelpers { formattedTotal: "$156.47", formattedSubtotal: "$145.00", customerEmail: "john.doe@example.com", - paymentMethodID: "woocommerce_payments", paymentMethodTitle: "WooCommerce In-Person Payments", lineItems: [ POSOrderItem( diff --git a/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSOrderListService.swift b/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSOrderListService.swift index 12c317228ed..9993ca87e9f 100644 --- a/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSOrderListService.swift +++ b/WooCommerce/WooCommerceTests/POS/Mocks/MockPOSOrderListService.swift @@ -149,7 +149,6 @@ extension MockPOSOrderListService { formattedTotal: "$25.99", formattedSubtotal: "$25.99", customerEmail: "customer1@example.com", - paymentMethodID: "cod", paymentMethodTitle: "Cash", lineItems: [ POSOrderItem( @@ -186,7 +185,6 @@ extension MockPOSOrderListService { formattedTotal: "$15.50", formattedSubtotal: "$15.50", customerEmail: "customer2@example.com", - paymentMethodID: "cod", paymentMethodTitle: "Card", lineItems: [ POSOrderItem( @@ -220,7 +218,6 @@ extension MockPOSOrderListService { formattedTotal: "$42.75", formattedSubtotal: "$42.75", customerEmail: "customer3@example.com", - paymentMethodID: "cod", paymentMethodTitle: "Cash", lineItems: [ POSOrderItem( @@ -257,7 +254,6 @@ extension MockPOSOrderListService { formattedTotal: "$12.00", formattedSubtotal: "$12.00", customerEmail: "customer4@example.com", - paymentMethodID: "cod", paymentMethodTitle: "Card", lineItems: [ POSOrderItem( @@ -293,7 +289,6 @@ extension MockPOSOrderListService { formattedTotal: "$18.50", formattedSubtotal: "$18.50", customerEmail: "search@example.com", - paymentMethodID: "cod", paymentMethodTitle: "Cash", lineItems: [ POSOrderItem( diff --git a/WooCommerce/WooCommerceTests/POS/Models/POSOrderListModelTests.swift b/WooCommerce/WooCommerceTests/POS/Models/POSOrderListModelTests.swift index b83b024be48..c945ca8bdfc 100644 --- a/WooCommerce/WooCommerceTests/POS/Models/POSOrderListModelTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Models/POSOrderListModelTests.swift @@ -74,7 +74,6 @@ final class POSOrderListModelTests { formattedTotal: "$10.00", formattedSubtotal: "$10.00", customerEmail: email, - paymentMethodID: "test", paymentMethodTitle: "Test Payment", lineItems: [], refunds: [], diff --git a/WooCommerce/WooCommerceTests/POS/Models/POSOrderListStateTests.swift b/WooCommerce/WooCommerceTests/POS/Models/POSOrderListStateTests.swift index fca6ad4d129..bf7b1359e27 100644 --- a/WooCommerce/WooCommerceTests/POS/Models/POSOrderListStateTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Models/POSOrderListStateTests.swift @@ -124,12 +124,12 @@ private extension POSOrderListStateTests { [ POSOrder(id: 1, number: "1001", dateCreated: Date(), status: .completed, formattedTotal: "$10.00", formattedSubtotal: "$10.00", customerEmail: "test1@example.com", - paymentMethodID: "stripe", paymentMethodTitle: "Credit Card", lineItems: [], + paymentMethodTitle: "Credit Card", lineItems: [], refunds: [], formattedDiscountTotal: nil, formattedTotalTax: "$0.00", formattedPaymentTotal: "$10.00", formattedNetAmount: nil), POSOrder(id: 2, number: "1002", dateCreated: Date(), status: .completed, formattedTotal: "$20.00", formattedSubtotal: "$20.00", customerEmail: "test2@example.com", - paymentMethodID: "cash", paymentMethodTitle: "Cash", lineItems: [], + paymentMethodTitle: "Cash", lineItems: [], refunds: [], formattedDiscountTotal: nil, formattedTotalTax: "$0.00", formattedPaymentTotal: "$20.00", formattedNetAmount: nil) ] From 5bf18fdbc8a9e16e6654058bbdaa7d2544532847 Mon Sep 17 00:00:00 2001 From: Povilas Staskus <4062343+staskus@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:25:42 +0300 Subject: [PATCH 31/31] Scroll to top when search term changes --- .../Orders/POSOrderListView.swift | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift index 73988ebdd5b..894dda855ee 100644 --- a/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift +++ b/WooCommerce/Classes/POS/Presentation/Orders/POSOrderListView.swift @@ -118,17 +118,19 @@ struct POSOrderListView: View { @ViewBuilder private var listView: some View { - InfiniteScrollView( - triggerDeterminer: infiniteScrollTriggerDeterminer, - loadMore: { - guard case .loaded(_, let hasMoreItems) = ordersViewState, hasMoreItems else { return } - await orderListModel.ordersController.loadNextOrders() - }, - content: { - LazyVStack(spacing: POSSpacing.small) { - headerRows - - let orders = ordersViewState.orders + ScrollViewReader { proxy in + InfiniteScrollView( + triggerDeterminer: infiniteScrollTriggerDeterminer, + loadMore: { + guard case .loaded(_, let hasMoreItems) = ordersViewState, hasMoreItems else { return } + await orderListModel.ordersController.loadNextOrders() + }, + content: { + LazyVStack(spacing: POSSpacing.small) { + headerRows + .id(Constants.scrollTopID) + + let orders = ordersViewState.orders ForEach(Array(orders.enumerated()), id: \.element.id) { index, order in Button(action: { analytics.track(event: WooAnalyticsEvent.PointOfSale.ordersListRowTapped( @@ -152,8 +154,14 @@ struct POSOrderListView: View { .padding(.top, POSPadding.xSmall) .padding(.bottom, POSPadding.medium) } - ) - .scrollDismissesKeyboard(.immediately) + ) + .scrollDismissesKeyboard(.immediately) + .onChange(of: searchTerm) { _, _ in + withAnimation { + proxy.scrollTo(Constants.scrollTopID, anchor: .top) + } + } + } } @ViewBuilder @@ -361,6 +369,7 @@ private enum Constants { static let maximumOrderCardHeight: CGFloat = Constants.orderCardMinHeight * 2 static let animationDuration: CGFloat = 0.2 static let searchControlID = "searchControl" + static let scrollTopID = "orderListViewTopID" } extension POSOrderListView {