@@ -29,7 +29,7 @@ final class NewOrderHostingController: UIHostingController<NewOrder> {
2929 . sink { [ weak self] notice in
3030 switch notice {
3131 case . error:
32- self ? . noticePresenter. enqueue ( notice: . init( title: Localization . errorMessage, feedbackType: . error) )
32+ self ? . noticePresenter. enqueue ( notice: . init( title: NewOrder . Localization. errorMessage, feedbackType: . error) )
3333 }
3434
3535 // Nullify the presentation intent.
@@ -45,11 +45,17 @@ struct NewOrder: View {
4545 @ObservedObject var viewModel : NewOrderViewModel
4646
4747 var body : some View {
48- ScrollView {
49- EmptyView ( )
50- }
51- . background ( Color ( . listBackground) )
48+ GeometryReader { geometry in
49+ ScrollView {
50+ VStack ( spacing: Layout . noSpacing) {
51+ Spacer ( minLength: Layout . sectionSpacing)
52+
53+ ProductsSection ( geometry: geometry)
54+ }
55+ }
56+ . background ( Color ( . listBackground) )
5257 . ignoresSafeArea ( . container, edges: [ . horizontal, . bottom] )
58+ }
5359 . navigationTitle ( Localization . title)
5460 . navigationBarTitleDisplayMode ( . inline)
5561 . toolbar {
@@ -70,11 +76,147 @@ struct NewOrder: View {
7076 }
7177}
7278
79+ // MARK: Order Sections
80+ /// Represents the Products section
81+ ///
82+ private struct ProductsSection : View {
83+ let geometry : GeometryProxy
84+
85+ var body : some View {
86+ Group {
87+ Divider ( )
88+
89+ VStack ( alignment: . leading, spacing: NewOrder . Layout. verticalSpacing) {
90+ Text ( NewOrder . Localization. products)
91+ . headlineStyle ( )
92+
93+ // TODO: Add a product row for each product added to the order
94+ ProductRow ( )
95+
96+ Button ( NewOrder . Localization. addProduct) {
97+ // TODO: Open Add Product modal view
98+ }
99+ . buttonStyle ( PlusButtonStyle ( ) )
100+ }
101+ . padding ( . horizontal, insets: geometry. safeAreaInsets)
102+ . padding ( )
103+ . background ( Color ( . listForeground) )
104+
105+ Divider ( )
106+ }
107+ }
108+
109+ /// Represent a single product row in the Product section
110+ ///
111+ struct ProductRow : View {
112+
113+ // Tracks the scale of the view due to accessibility changes
114+ @ScaledMetric private var scale : CGFloat = 1
115+
116+ var body : some View {
117+ AdaptiveStack ( horizontalAlignment: . leading) {
118+ HStack ( alignment: . top) {
119+ // Product image
120+ // TODO: Display actual product image when available
121+ Image ( uiImage: . productPlaceholderImage)
122+ . resizable ( )
123+ . aspectRatio ( contentMode: . fill)
124+ . frame ( width: NewOrder . Layout. productImageSize * scale, height: NewOrder . Layout. productImageSize * scale)
125+ . foregroundColor ( Color ( UIColor . listSmallIcon) )
126+ . accessibilityHidden ( true )
127+
128+ // Product details
129+ VStack ( alignment: . leading) {
130+ Text ( " Love Ficus " ) // Fake data - product name
131+ Text ( " 7 in stock • $20.00 " ) // Fake data - stock / price
132+ . subheadlineStyle ( )
133+ Text ( " SKU: 123456 " ) // Fake data - SKU
134+ . subheadlineStyle ( )
135+ }
136+ . accessibilityElement ( children: . combine)
137+ }
138+
139+ Spacer ( )
140+
141+ ProductStepper ( )
142+ }
143+
144+ Divider ( )
145+ }
146+ }
147+
148+ /// Represents a custom stepper.
149+ /// Used to change the product quantity in the order.
150+ ///
151+ struct ProductStepper : View {
152+
153+ // Tracks the scale of the view due to accessibility changes
154+ @ScaledMetric private var scale : CGFloat = 1
155+
156+ var body : some View {
157+ HStack ( spacing: NewOrder . Layout. stepperSpacing * scale) {
158+ Button {
159+ // TODO: Decrement the product quantity
160+ } label: {
161+ Image ( uiImage: . minusSmallImage)
162+ . resizable ( )
163+ . aspectRatio ( contentMode: . fit)
164+ . frame ( height: NewOrder . Layout. stepperButtonSize * scale)
165+ }
166+
167+ Text ( " 1 " ) // Fake data - quantity
168+
169+ Button {
170+ // TODO: Increment the product quantity
171+ } label: {
172+ Image ( uiImage: . plusSmallImage)
173+ . resizable ( )
174+ . aspectRatio ( contentMode: . fit)
175+ . frame ( height: NewOrder . Layout. stepperButtonSize * scale)
176+ }
177+ }
178+ . padding ( NewOrder . Layout. stepperSpacing/ 2 * scale)
179+ . overlay (
180+ RoundedRectangle ( cornerRadius: NewOrder . Layout. stepperBorderRadius)
181+ . stroke ( Color ( UIColor . separator) , lineWidth: NewOrder . Layout. stepperBorderWidth)
182+ )
183+ . accessibilityElement ( children: . ignore)
184+ . accessibility ( label: Text ( " Quantity " ) )
185+ . accessibility ( value: Text ( " 1 " ) ) // Fake static data - quantity
186+ . accessibilityAdjustableAction { direction in
187+ switch direction {
188+ case . decrement:
189+ break // TODO: Decrement the product quantity
190+ case . increment:
191+ break // TODO: Increment the product quantity
192+ @unknown default :
193+ break
194+ }
195+ }
196+ }
197+ }
198+ }
199+
73200// MARK: Constants
74- private enum Localization {
75- static let title = NSLocalizedString ( " New Order " , comment: " Title for the order creation screen " )
76- static let createButton = NSLocalizedString ( " Create " , comment: " Button to create an order on the New Order screen " )
77- static let errorMessage = NSLocalizedString ( " Unable to create new order " , comment: " Notice displayed when order creation fails " )
201+ private extension NewOrder {
202+ enum Layout {
203+ static let sectionSpacing : CGFloat = 16.0
204+ static let verticalSpacing : CGFloat = 22.0
205+ static let noSpacing : CGFloat = 0.0
206+ static let productImageSize : CGFloat = 44.0
207+ static let stepperBorderWidth : CGFloat = 1.0
208+ static let stepperBorderRadius : CGFloat = 4.0
209+ static let stepperButtonSize : CGFloat = 22.0
210+ static let stepperSpacing : CGFloat = 22.0
211+ }
212+
213+ enum Localization {
214+ static let title = NSLocalizedString ( " New Order " , comment: " Title for the order creation screen " )
215+ static let createButton = NSLocalizedString ( " Create " , comment: " Button to create an order on the New Order screen " )
216+ static let errorMessage = NSLocalizedString ( " Unable to create new order " , comment: " Notice displayed when order creation fails " )
217+ static let products = NSLocalizedString ( " Products " , comment: " Title text of the section that shows the Products when creating a new order " )
218+ static let addProduct = NSLocalizedString ( " Add product " , comment: " Title text of the button that adds a product when creating a new order " )
219+ }
78220}
79221
80222struct NewOrder_Previews : PreviewProvider {
@@ -84,5 +226,23 @@ struct NewOrder_Previews: PreviewProvider {
84226 NavigationView {
85227 NewOrder ( viewModel: viewModel)
86228 }
229+
230+ NavigationView {
231+ NewOrder ( viewModel: viewModel)
232+ }
233+ . environment ( \. sizeCategory, . accessibilityExtraExtraLarge)
234+ . previewDisplayName ( " Accessibility " )
235+
236+ NavigationView {
237+ NewOrder ( viewModel: viewModel)
238+ }
239+ . environment ( \. colorScheme, . dark)
240+ . previewDisplayName ( " Dark " )
241+
242+ NavigationView {
243+ NewOrder ( viewModel: viewModel)
244+ }
245+ . environment ( \. layoutDirection, . rightToLeft)
246+ . previewDisplayName ( " Right to left " )
87247 }
88248}
0 commit comments