Skip to content

Commit 16f6426

Browse files
authored
Merge pull request #4969 from woocommerce/issue/4778-action-on-edit-address-form
2 parents 26cd798 + b5d5e00 commit 16f6426

File tree

3 files changed

+171
-82
lines changed

3 files changed

+171
-82
lines changed

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/EditAddressForm.swift

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,28 @@ struct EditAddressForm: View {
4242
VStack(spacing: 0) {
4343
TitleAndTextFieldRow(title: Localization.firstNameField,
4444
placeholder: "",
45-
text: $viewModel.firstName,
45+
text: $viewModel.fields.firstName,
4646
symbol: nil,
4747
keyboardType: .default)
4848
Divider()
4949
.padding(.leading, Constants.dividerPadding)
5050
TitleAndTextFieldRow(title: Localization.lastNameField,
5151
placeholder: "",
52-
text: $viewModel.lastName,
52+
text: $viewModel.fields.lastName,
5353
symbol: nil,
5454
keyboardType: .default)
5555
Divider()
5656
.padding(.leading, Constants.dividerPadding)
5757
TitleAndTextFieldRow(title: Localization.emailField,
5858
placeholder: "",
59-
text: $viewModel.email,
59+
text: $viewModel.fields.email,
6060
symbol: nil,
6161
keyboardType: .emailAddress)
6262
Divider()
6363
.padding(.leading, Constants.dividerPadding)
6464
TitleAndTextFieldRow(title: Localization.phoneField,
6565
placeholder: "",
66-
text: $viewModel.phone,
66+
text: $viewModel.fields.phone,
6767
symbol: nil,
6868
keyboardType: .phonePad)
6969
}
@@ -76,35 +76,35 @@ struct EditAddressForm: View {
7676
Group {
7777
TitleAndTextFieldRow(title: Localization.companyField,
7878
placeholder: Localization.placeholderOptional,
79-
text: $viewModel.company,
79+
text: $viewModel.fields.company,
8080
symbol: nil,
8181
keyboardType: .default)
8282
Divider()
8383
.padding(.leading, Constants.dividerPadding)
8484
TitleAndTextFieldRow(title: Localization.address1Field,
8585
placeholder: "",
86-
text: $viewModel.address1,
86+
text: $viewModel.fields.address1,
8787
symbol: nil,
8888
keyboardType: .default)
8989
Divider()
9090
.padding(.leading, Constants.dividerPadding)
9191
TitleAndTextFieldRow(title: Localization.address2Field,
9292
placeholder: "Optional",
93-
text: $viewModel.address2,
93+
text: $viewModel.fields.address2,
9494
symbol: nil,
9595
keyboardType: .default)
9696
Divider()
9797
.padding(.leading, Constants.dividerPadding)
9898
TitleAndTextFieldRow(title: Localization.cityField,
9999
placeholder: "",
100-
text: $viewModel.city,
100+
text: $viewModel.fields.city,
101101
symbol: nil,
102102
keyboardType: .default)
103103
Divider()
104104
.padding(.leading, Constants.dividerPadding)
105105
TitleAndTextFieldRow(title: Localization.postcodeField,
106106
placeholder: "",
107-
text: $viewModel.postcode,
107+
text: $viewModel.fields.postcode,
108108
symbol: nil,
109109
keyboardType: .default)
110110
Divider()
@@ -130,10 +130,7 @@ struct EditAddressForm: View {
130130
}
131131
.navigationTitle(Localization.shippingTitle)
132132
.navigationBarTitleDisplayMode(.inline)
133-
.navigationBarItems(trailing: Button(Localization.done) {
134-
// TODO: save changes
135-
}
136-
.disabled(!viewModel.isDoneButtonEnabled))
133+
.navigationBarItems(trailing: navigationBarTrailingItem())
137134
.redacted(reason: viewModel.showPlaceholders ? .placeholder : [])
138135
.shimmering(active: viewModel.showPlaceholders)
139136
.onAppear {
@@ -151,6 +148,22 @@ struct EditAddressForm: View {
151148
EmptyView()
152149
}
153150
}
151+
152+
/// Decides if the navigation trailing item should be a done button or a loading indicator.
153+
///
154+
@ViewBuilder private func navigationBarTrailingItem() -> some View {
155+
switch viewModel.navigationTrailingItem {
156+
case .done(let enabled):
157+
Button(Localization.done) {
158+
viewModel.updateRemoteAddress(onFinish: { success in
159+
// TODO: dismiss on success
160+
})
161+
}
162+
.disabled(!enabled)
163+
case .loading:
164+
ProgressView()
165+
}
166+
}
154167
}
155168

156169
// MARK: Constants

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/EditAddressFormViewModel.swift

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

91152
private 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

Comments
 (0)