1+ import Combine
12import SwiftUI
23
4+ final class CustomFieldsListHostingController : UIHostingController < CustomFieldsListView > {
5+ private let viewModel : CustomFieldsListViewModel
6+ private var subscriptions : Set < AnyCancellable > = [ ]
7+
8+ init ( isEditable: Bool , viewModel: CustomFieldsListViewModel ) {
9+ self . viewModel = viewModel
10+ super. init ( rootView: CustomFieldsListView ( isEditable: isEditable,
11+ viewModel: viewModel)
12+ )
13+ }
14+
15+ override func viewDidLoad( ) {
16+ super. viewDidLoad ( )
17+
18+ configureNavigation ( )
19+ observeStateChange ( )
20+ }
21+
22+ /// Create a `UIBarButtonItem` to be used as the add custom field button on the top-right.
23+ ///
24+ private lazy var addCustomFieldButtonItem : UIBarButtonItem = {
25+ let button = UIBarButtonItem ( image: . plusImage,
26+ style: . plain,
27+ target: self ,
28+ action: #selector( openAddCustomFieldScreen) )
29+ button. accessibilityTraits = . button
30+ button. accessibilityLabel = Localization . accessibilityLabelAddCustomField
31+ button. accessibilityHint = Localization . accessibilityHintAddCustomField
32+ button. accessibilityIdentifier = " add-custom-field-button "
33+
34+ return button
35+ } ( )
36+
37+ /// Create a `UIBarButtonItem` to be used as the save custom field button on the top-right.
38+ ///
39+ private lazy var saveCustomFieldButtonItem =
40+ UIBarButtonItem ( title: Localization . save,
41+ style: . plain,
42+ target: self ,
43+ action: #selector( saveCustomField) )
44+
45+ required dynamic init ? ( coder aDecoder: NSCoder ) {
46+ fatalError ( " init(coder:) has not been implemented " )
47+ }
48+ }
49+
50+ private extension CustomFieldsListHostingController {
51+ func configureNavigation( ) {
52+ title = Localization . title
53+ navigationItem. rightBarButtonItems = [ saveCustomFieldButtonItem, addCustomFieldButtonItem]
54+ }
55+
56+ @objc func openAddCustomFieldScreen( ) {
57+ viewModel. isAddingNewField = true
58+ }
59+
60+ @objc func saveCustomField( ) {
61+ // todo: call viewmodel's save function
62+ navigationController? . popViewController ( animated: true )
63+ }
64+
65+ func observeStateChange( ) {
66+ viewModel. $hasChanges
67+ . sink { [ weak self] hasChanges in
68+ self ? . saveCustomFieldButtonItem. isEnabled = hasChanges
69+ }
70+ . store ( in: & subscriptions)
71+ }
72+ }
73+
374struct CustomFieldsListView : View {
475 @Environment ( \. presentationMode) var presentationMode
576 @ObservedObject private var viewModel : CustomFieldsListViewModel
677
778 let isEditable : Bool
8- let onBackButtonTapped : ( ) -> Void
979
1080 init ( isEditable: Bool ,
11- viewModel: CustomFieldsListViewModel ,
12- onBackButtonTapped: @escaping ( ) -> Void ) {
81+ viewModel: CustomFieldsListViewModel ) {
1382 self . isEditable = isEditable
1483 self . viewModel = viewModel
15- self . onBackButtonTapped = onBackButtonTapped
1684 }
1785
1886 var body : some View {
19- NavigationStack {
20- List ( viewModel. combinedList) { customField in
21- if isEditable {
22- NavigationLink ( destination: CustomFieldEditorView ( key: customField. key, value: customField. value, onSave: { updatedKey, updatedValue in
23- viewModel. saveField ( key: updatedKey, value: updatedValue, fieldId: customField. fieldId)
24- } ) ) {
25- CustomFieldRow ( isEditable: true ,
26- title: customField. key,
27- content: customField. value. removedHTMLTags,
28- contentURL: nil )
29- }
30- } else {
31- CustomFieldRow ( isEditable: false ,
32- title: customField. key,
33- content: customField. value. removedHTMLTags,
34- contentURL: nil )
35- }
36- }
37- . listStyle ( . plain)
38- . navigationTitle ( Localization . title)
39- . navigationBarTitleDisplayMode ( . inline)
40- . toolbar {
41- ToolbarItem ( placement: . cancellationAction) {
42- Button ( action: {
43- onBackButtonTapped ( )
44- presentationMode. wrappedValue. dismiss ( )
45- } , label: {
46- Image ( systemName: " chevron.backward " )
47- . headlineLinkStyle ( )
48- } )
49- }
50-
51- ToolbarItem ( placement: . navigationBarTrailing) {
52- if isEditable {
53- HStack {
54- Button {
55- // todo-13493: add save handling
56- } label: {
57- Text ( " Save " ) // todo-13493: set String to be translatable
58- }
59- . disabled ( !viewModel. hasChanges)
60- Button ( action: {
61- // todo-13493: add addition handling
62- } , label: {
63- Image ( systemName: " plus " )
64- . renderingMode ( . template)
65- } )
66- }
67- }
68- }
87+ List ( viewModel. combinedList) { customField in
88+ Button ( action: { viewModel. selectedCustomField = customField } ) {
89+ CustomFieldRow ( isEditable: isEditable,
90+ title: customField. key,
91+ content: customField. value. removedHTMLTags,
92+ contentURL: nil )
6993 }
70- . wooNavigationBarStyle ( )
94+ }
95+ . listStyle ( . plain)
96+ . sheet ( item: $viewModel. selectedCustomField) { customField in
97+ buildCustomFieldEditorView ( customField: customField)
98+ }
99+ . sheet ( isPresented: $viewModel. isAddingNewField) {
100+ buildCustomFieldEditorView ( customField: nil )
71101 }
72102 }
73103}
@@ -128,12 +158,52 @@ private struct CustomFieldRow: View {
128158 }
129159}
130160
161+ // MARK: - Helpers
162+ //
163+ private extension CustomFieldsListView {
164+ /// Builds the Custom Field Editor View.
165+ /// - When `customField` is provided, it configures the editor for editing an existing field
166+ /// - When `customField` is nil, it configures the editor for creating a new field
167+ func buildCustomFieldEditorView( customField: CustomFieldsListViewModel . CustomFieldUI ? ) -> some View {
168+ NavigationView {
169+ CustomFieldEditorView (
170+ key: customField? . key ?? " " ,
171+ value: customField? . value ?? " " ,
172+ onSave: { updatedKey, updatedValue in
173+ viewModel. saveField (
174+ key: updatedKey,
175+ value: updatedValue,
176+ fieldId: customField? . fieldId
177+ )
178+ }
179+ )
180+ }
181+ }
182+ }
131183
132184// MARK: - Constants
133185//
134- extension CustomFieldsListView {
186+ private extension CustomFieldsListHostingController {
135187 enum Localization {
136- static let title = NSLocalizedString ( " Custom Fields " , comment: " Title for the order custom fields list " )
188+ static let title = NSLocalizedString (
189+ " customFieldsListHostingController.title " ,
190+ value: " Custom Fields " ,
191+ comment: " Title for the order custom fields list " )
192+
193+ static let accessibilityLabelAddCustomField = NSLocalizedString (
194+ " customFieldsListHostingController.accessibilityLabelAddCustomField " ,
195+ value: " Add custom field " ,
196+ comment: " Accessibility label for the Add Custom Field button " )
197+
198+ static let accessibilityHintAddCustomField = NSLocalizedString (
199+ " customFieldsListHostingController.accessibilityHintAddCustomField " ,
200+ value: " Add a new custom field to the list " ,
201+ comment: " VoiceOver accessibility hint, informing the user the button can be used to add custom field. " )
202+
203+ static let save = NSLocalizedString (
204+ " customFieldsListHostingController.save " ,
205+ value: " Save " ,
206+ comment: " Button to save the changes on Custom Fields list " )
137207 }
138208}
139209
@@ -155,9 +225,7 @@ struct OrderCustomFieldsDetails_Previews: PreviewProvider {
155225 customFields: [
156226 CustomFieldViewModel ( id: 0 , title: " First Title " , content: " First Content " ) ,
157227 CustomFieldViewModel ( id: 1 , title: " Second Title " , content: " Second Content " , contentURL: URL ( string: " https://woocommerce.com/ " ) )
158- ] ) ,
159- onBackButtonTapped: { }
160- )
228+ ] ) )
161229 }
162230}
163231
0 commit comments