@@ -38,6 +38,7 @@ final class EditAddressFormViewModel: ObservableObject {
3838 self . storageManager = storageManager
3939 self . stores = stores
4040 updateFieldsWithOriginalAddress ( )
41+ bindNavigationTrailingItemPublisher ( )
4142
4243 // Listen only to the first emitted event.
4344 onLoadTrigger. first ( ) . sink {
@@ -50,75 +51,122 @@ final class EditAddressFormViewModel: ObservableObject {
5051 ///
5152 private let originalAddress : Address
5253
54+ /// Address form fields
55+ ///
56+ @Published var fields = FormFields ( )
57+
5358 /// Trigger to perform any one time setups.
5459 ///
5560 let onLoadTrigger : PassthroughSubject < Void , Never > = PassthroughSubject ( )
5661
57- // MARK: User Fields
58-
59- @Published var firstName : String = " "
60- @Published var lastName : String = " "
61- @Published var email : String = " "
62- @Published var phone : String = " "
63-
64- // MARK: Address Fields
65-
66- @Published var company : String = " "
67- @Published var address1 : String = " "
68- @Published var address2 : String = " "
69- @Published var city : String = " "
70- @Published var postcode : String = " "
62+ /// Tracks if a network request is being performed.
63+ ///
64+ private let performingNetworkRequest : CurrentValueSubject < Bool , Never > = . init( false )
7165
72- // MARK: Navigation and utility
66+ /// Active navigation bar trailing item.
67+ /// Defaults to a disabled done button.
68+ ///
69+ @Published private( set) var navigationTrailingItem : NavigationItem = . done( enabled: false )
7370
7471 /// Define if the view should show placeholders instead of the real elements.
7572 ///
7673 @Published private( set) var showPlaceholders : Bool = false
7774
78- /// Return `true` if the done button should be enabled.
79- ///
80- var isDoneButtonEnabled : Bool {
81- return originalAddress != addressFromFields
82- }
83-
8475 /// Creates a view model to be used when selecting a country
8576 ///
8677 func createCountryViewModel( ) -> CountrySelectorViewModel {
8778 CountrySelectorViewModel ( siteID: siteID, countries: countriesResultsController. fetchedObjects)
8879 }
80+
81+ /// Update the address remotely and invoke a completion block when finished
82+ ///
83+ func updateRemoteAddress( onFinish: @escaping ( Bool ) -> Void ) {
84+ // TODO: perform network request
85+ // TODO: add success/failure notice
86+ performingNetworkRequest. send ( true )
87+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 3 ) { [ weak self] in
88+ self ? . performingNetworkRequest. send ( false )
89+ onFinish ( true )
90+ }
91+ }
92+ }
93+
94+ extension EditAddressFormViewModel {
95+ /// Representation of possible navigation bar trailing buttons
96+ ///
97+ enum NavigationItem : Equatable {
98+ case done( enabled: Bool )
99+ case loading
100+ }
101+
102+ /// Type to hold values from all the form fields
103+ ///
104+ struct FormFields {
105+ // MARK: User Fields
106+ var firstName : String = " "
107+ var lastName : String = " "
108+ var email : String = " "
109+ var phone : String = " "
110+
111+ // MARK: Address Fields
112+
113+ var company : String = " "
114+ var address1 : String = " "
115+ var address2 : String = " "
116+ var city : String = " "
117+ var postcode : String = " "
118+ var country : String = " "
119+ var state : String = " "
120+
121+ mutating func update( from address: Address ) {
122+ firstName = address. firstName
123+ lastName = address. lastName
124+ email = address. email ?? " "
125+ phone = address. phone ?? " "
126+
127+ company = address. company ?? " "
128+ address1 = address. address1
129+ address2 = address. address2 ?? " "
130+ city = address. city
131+ postcode = address. postcode
132+ country = address. country
133+ state = address. state
134+ }
135+
136+ func toAddress( ) -> Address {
137+ Address ( firstName: firstName,
138+ lastName: lastName,
139+ company: company. isEmpty ? nil : company,
140+ address1: address1,
141+ address2: address2. isEmpty ? nil : address2,
142+ city: city,
143+ state: state,
144+ postcode: postcode,
145+ country: country,
146+ phone: phone. isEmpty ? nil : phone,
147+ email: email. isEmpty ? nil : email)
148+ }
149+ }
89150}
90151
91152private extension EditAddressFormViewModel {
92153 func updateFieldsWithOriginalAddress( ) {
93- firstName = originalAddress. firstName
94- lastName = originalAddress. lastName
95- email = originalAddress. email ?? " "
96- phone = originalAddress. phone ?? " "
97-
98- company = originalAddress. company ?? " "
99- address1 = originalAddress. address1
100- address2 = originalAddress. address2 ?? " "
101- city = originalAddress. city
102- postcode = originalAddress. postcode
103-
104- // TODO: Add country and state init
154+ fields. update ( from: originalAddress)
105155 }
106156
107- var addressFromFields : Address {
108- Address ( firstName: firstName,
109- lastName: lastName,
110- company: company. isEmpty ? nil : company,
111- address1: address1,
112- address2: company. isEmpty ? nil : company,
113- city: city,
114- state: originalAddress. state, // TODO: replace with local value
115- postcode: postcode,
116- country: originalAddress. country, // TODO: replace with local value
117- phone: phone. isEmpty ? nil : phone,
118- email: email. isEmpty ? nil : email)
157+ /// Calculates what navigation trailing item should be shown depending on our internal state.
158+ ///
159+ func bindNavigationTrailingItemPublisher( ) {
160+ Publishers . CombineLatest ( $fields, performingNetworkRequest)
161+ . map { [ originalAddress] fields, performingNetworkRequest -> NavigationItem in
162+ guard !performingNetworkRequest else {
163+ return . loading
164+ }
165+ return . done( enabled: originalAddress != fields. toAddress ( ) )
166+ }
167+ . assign ( to: & $navigationTrailingItem)
119168 }
120169
121-
122170 /// Fetches countries from storage, If there are no stored countries, trigger a sync request.
123171 ///
124172 func fetchStoredCountriesAndTriggerSyncIfNeeded( ) {
0 commit comments