@@ -83,6 +83,7 @@ private extension POSOrderDetailsView {
8383 Text ( Localization . productsTitle)
8484 . font ( . posBodyLargeBold)
8585 . foregroundStyle ( Color . posOnSurface)
86+ . accessibilityAddTraits ( . isHeader)
8687
8788 VStack ( spacing: POSSpacing . small) {
8889 ForEach ( Array ( order. lineItems. enumerated ( ) ) , id: \. element. itemID) { index, item in
@@ -105,6 +106,7 @@ private extension POSOrderDetailsView {
105106 Text ( Localization . totalsTitle)
106107 . font ( . posBodyLargeBold)
107108 . foregroundStyle ( Color . posOnSurface)
109+ . accessibilityAddTraits ( . isHeader)
108110
109111 VStack ( spacing: POSSpacing . small) {
110112 productsSubtotalRow ( order)
@@ -152,6 +154,20 @@ private extension POSOrderDetailsView {
152154 }
153155 . padding ( . top, POSSpacing . xSmall)
154156 . multilineTextAlignment ( . leading)
157+ . accessibilityElement ( children: . combine)
158+ . accessibilityLabel ( headerBottomContentAccessibilityLabel ( for: order) )
159+ }
160+
161+ private func headerBottomContentAccessibilityLabel( for order: POSOrder ) -> String {
162+ let date = dateFormatter. string ( from: order. dateCreated)
163+ let email = order. customerEmail
164+ let status = order. status. localizedName
165+
166+ return Localization . headerBottomContentAccessibilityLabel (
167+ date: date,
168+ email: email,
169+ status: status
170+ )
155171 }
156172
157173}
@@ -167,6 +183,19 @@ private extension POSOrderDetailsView {
167183 Spacer ( )
168184 productTotalView ( item: item)
169185 }
186+ . accessibilityElement ( children: . combine)
187+ . accessibilityLabel ( productRowAccessibilityLabel ( for: item) )
188+ }
189+
190+ private func productRowAccessibilityLabel( for item: POSOrderItem ) -> String {
191+ let attributesText = item. attributes. isEmpty ? nil : item. attributes. map { " \( $0. name) : \( $0. value) " } . joined ( separator: " , " )
192+ return Localization . productRowAccessibilityLabel (
193+ name: item. name,
194+ attributes: attributesText,
195+ quantity: String ( item. quantity. intValue) ,
196+ unitPrice: item. formattedPrice,
197+ total: item. formattedTotal
198+ )
170199 }
171200
172201 @ViewBuilder
@@ -219,7 +248,8 @@ private extension POSOrderDetailsView {
219248 func productsSubtotalRow( _ order: POSOrder ) -> some View {
220249 totalsRow (
221250 title: Localization . productsLabel,
222- amount: order. formattedSubtotal
251+ amount: order. formattedSubtotal,
252+ accessibilityLabel: Localization . subtotalAccessibilityLabel ( order. formattedSubtotal)
223253 )
224254 }
225255
@@ -228,7 +258,8 @@ private extension POSOrderDetailsView {
228258 if let formattedDiscountTotal = order. formattedDiscountTotal {
229259 totalsRow (
230260 title: Localization . discountTotalLabel,
231- amount: formattedDiscountTotal
261+ amount: formattedDiscountTotal,
262+ accessibilityLabel: Localization . discountAccessibilityLabel ( formattedDiscountTotal)
232263 )
233264 }
234265 }
@@ -237,7 +268,8 @@ private extension POSOrderDetailsView {
237268 func taxTotalRow( _ order: POSOrder ) -> some View {
238269 totalsRow (
239270 title: Localization . taxesLabel,
240- amount: order. formattedTotalTax
271+ amount: order. formattedTotalTax,
272+ accessibilityLabel: Localization . taxAccessibilityLabel ( order. formattedTotalTax)
241273 )
242274 }
243275
@@ -247,7 +279,8 @@ private extension POSOrderDetailsView {
247279 title: Localization . totalLabel,
248280 amount: order. formattedTotal,
249281 titleColor: . posOnSurface,
250- titleFont: . posBodySmallBold
282+ titleFont: . posBodySmallBold,
283+ accessibilityLabel: Localization . totalAccessibilityLabel ( order. formattedTotal)
251284 )
252285 }
253286
@@ -267,6 +300,13 @@ private extension POSOrderDetailsView {
267300 . foregroundStyle ( Color . posOnSurfaceVariantHighest)
268301 }
269302 }
303+ . accessibilityElement ( children: . combine)
304+ . accessibilityLabel (
305+ Localization . paidAccessibilityLabel (
306+ amount: order. formattedPaymentTotal,
307+ method: order. paymentMethodTitle
308+ )
309+ )
270310 }
271311
272312 @ViewBuilder
@@ -297,6 +337,8 @@ private extension POSOrderDetailsView {
297337 . foregroundStyle ( Color . posOnSurfaceVariantHighest)
298338 }
299339 }
340+ . accessibilityElement ( children: . combine)
341+ . accessibilityLabel ( Localization . refundAccessibilityLabel ( amount: refund. formattedTotal, reason: refund. reason) )
300342 }
301343
302344 @ViewBuilder
@@ -305,7 +347,8 @@ private extension POSOrderDetailsView {
305347 title: Localization . netPaymentLabel,
306348 amount: netAmount,
307349 titleColor: . posOnSurface,
308- titleFont: . posBodySmallBold
350+ titleFont: . posBodySmallBold,
351+ accessibilityLabel: Localization . netPaymentAccessibilityLabel ( netAmount)
309352 )
310353 }
311354
@@ -314,7 +357,8 @@ private extension POSOrderDetailsView {
314357 title: String ,
315358 amount: String ,
316359 titleColor: Color = . posOnSurfaceVariantHighest,
317- titleFont: POSFontStyle = . posBodySmallRegular( )
360+ titleFont: POSFontStyle = . posBodySmallRegular( ) ,
361+ accessibilityLabel: String ? = nil
318362 ) -> some View {
319363 HStack {
320364 Text ( title)
@@ -325,6 +369,8 @@ private extension POSOrderDetailsView {
325369 . font ( . posBodySmallRegular( ) )
326370 . foregroundStyle ( Color . posOnSurface)
327371 }
372+ . accessibilityElement ( children: . combine)
373+ . accessibilityLabel ( accessibilityLabel ?? " \( title) \( amount) " )
328374 }
329375}
330376
@@ -371,11 +417,19 @@ private extension POSOrderDetailsView {
371417 . minimumScaleFactor ( 0.5 )
372418 }
373419 . buttonStyle ( POSFilledButtonStyle ( size: . extraSmall) )
420+ . accessibilityHint ( accessibilityHint ( for: action) )
374421 }
375422 }
376423 Spacer ( )
377424 }
378425 }
426+
427+ private func accessibilityHint( for action: POSOrderDetailsAction ) -> String {
428+ switch action {
429+ case . emailReceipt:
430+ return Localization . emailReceiptAccessibilityHint
431+ }
432+ }
379433}
380434
381435private extension POSOrderDetailsView {
@@ -467,6 +521,131 @@ private enum Localization {
467521 value: " Email receipt " ,
468522 comment: " Label for email receipt action on order details view "
469523 )
524+
525+ static let emailReceiptAccessibilityHint = NSLocalizedString (
526+ " pos.orderDetailsView.emailReceiptAction.accessibilityHint " ,
527+ value: " Tap to send order receipt via email " ,
528+ comment: " Accessibility hint for email receipt button on order details view "
529+ )
530+
531+ static func headerBottomContentAccessibilityLabel( date: String , email: String ? , status: String ) -> String {
532+ let baseFormat = NSLocalizedString (
533+ " pos.orderDetailsView.headerBottomContent.accessibilityLabel " ,
534+ value: " Order date: %1$@, Status: %2$@ " ,
535+ comment: " Accessibility label for order header bottom content. %1$@ is order date and time, %2$@ is order status. "
536+ )
537+ var label = String ( format: baseFormat, date, status)
538+
539+ if let email = email, email. isNotEmpty {
540+ let emailFormat = NSLocalizedString (
541+ " pos.orderDetailsView.headerBottomContent.accessibilityLabel.email " ,
542+ value: " Customer email: %1$@ " ,
543+ comment: " Email portion of order header accessibility label. %1$@ is customer email address. "
544+ )
545+ label += " , " + String( format: emailFormat, email)
546+ }
547+
548+ return label
549+ }
550+
551+ static func productRowAccessibilityLabel( name: String , attributes: String ? , quantity: String , unitPrice: String , total: String ) -> String {
552+ var label = name
553+ if let attributes = attributes {
554+ label += " , \( attributes) "
555+ }
556+ let format = NSLocalizedString (
557+ " pos.orderDetailsView.productRow.accessibilityLabel " ,
558+ value: " Quantity: %1$@ at %2$@ each, Total %3$@ " ,
559+ comment: " Accessibility label for product row. %1$@ is quantity, %2$@ is unit price, %3$@ is total price. "
560+ )
561+ label += " , " + String( format: format, quantity, unitPrice, total)
562+ return label
563+ }
564+
565+ static func subtotalAccessibilityLabel( _ amount: String ) -> String {
566+ let format = NSLocalizedString (
567+ " pos.orderDetailsView.subtotal.accessibilityLabel " ,
568+ value: " Products subtotal: %1$@ " ,
569+ comment: " Accessibility label for products subtotal. %1$@ is the subtotal amount. "
570+ )
571+ return String ( format: format, amount)
572+ }
573+
574+ static func discountAccessibilityLabel( _ amount: String ) -> String {
575+ let format = NSLocalizedString (
576+ " pos.orderDetailsView.discount.accessibilityLabel " ,
577+ value: " Discount total: %1$@ " ,
578+ comment: " Accessibility label for discount total. %1$@ is the discount amount. "
579+ )
580+ return String ( format: format, amount)
581+ }
582+
583+ static func taxAccessibilityLabel( _ amount: String ) -> String {
584+ let format = NSLocalizedString (
585+ " pos.orderDetailsView.tax.accessibilityLabel " ,
586+ value: " Taxes: %1$@ " ,
587+ comment: " Accessibility label for taxes. %1$@ is the tax amount. "
588+ )
589+ return String ( format: format, amount)
590+ }
591+
592+ static func totalAccessibilityLabel( _ amount: String ) -> String {
593+ let format = NSLocalizedString (
594+ " pos.orderDetailsView.total.accessibilityLabel " ,
595+ value: " Order total: %1$@ " ,
596+ comment: " Accessibility label for order total. %1$@ is the total amount. "
597+ )
598+ return String ( format: format, amount)
599+ }
600+
601+ static func paidAccessibilityLabel( amount: String , method: String ) -> String {
602+ let baseFormat = NSLocalizedString (
603+ " pos.orderDetailsView.paid.accessibilityLabel " ,
604+ value: " Total paid: %1$@ " ,
605+ comment: " Accessibility label for total paid. %1$@ is the paid amount. "
606+ )
607+ var label = String ( format: baseFormat, amount)
608+
609+ if method. isNotEmpty {
610+ let methodFormat = NSLocalizedString (
611+ " pos.orderDetailsView.paid.accessibilityLabel.method " ,
612+ value: " Payment method: %1$@ " ,
613+ comment: " Payment method portion of paid accessibility label. %1$@ is the payment method. "
614+ )
615+ label += " , " + String( format: methodFormat, method)
616+ }
617+
618+ return label
619+ }
620+
621+ static func refundAccessibilityLabel( amount: String , reason: String ? ) -> String {
622+ let baseFormat = NSLocalizedString (
623+ " pos.orderDetailsView.refund.accessibilityLabel " ,
624+ value: " Refunded: %1$@ " ,
625+ comment: " Accessibility label for refunded amount. %1$@ is the refund amount. "
626+ )
627+ var label = String ( format: baseFormat, amount)
628+
629+ if let reason = reason, !reason. isEmpty {
630+ let reasonFormat = NSLocalizedString (
631+ " pos.orderDetailsView.refund.accessibilityLabel.reason " ,
632+ value: " Reason: %1$@ " ,
633+ comment: " Reason portion of refund accessibility label. %1$@ is the refund reason. "
634+ )
635+ label += " , " + String( format: reasonFormat, reason)
636+ }
637+
638+ return label
639+ }
640+
641+ static func netPaymentAccessibilityLabel( _ amount: String ) -> String {
642+ let format = NSLocalizedString (
643+ " pos.orderDetailsView.netPayment.accessibilityLabel " ,
644+ value: " Net payment: %1$@ " ,
645+ comment: " Accessibility label for net payment. %1$@ is the net payment amount after refunds. "
646+ )
647+ return String ( format: format, amount)
648+ }
470649}
471650
472651#if DEBUG
0 commit comments