Skip to content

Commit a23223e

Browse files
authored
Merge pull request #4944 from woocommerce/issue/4778-edit-address-form-viewmodel
2 parents e603e38 + 28e94a1 commit a23223e

File tree

6 files changed

+254
-17
lines changed

6 files changed

+254
-17
lines changed

Networking/Networking/Model/Address.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Codegen
33

44
/// Represents an Address Entity.
55
///
6-
public struct Address: Decodable, Equatable, GeneratedFakeable {
6+
public struct Address: Decodable, GeneratedFakeable {
77
public let firstName: String
88
public let lastName: String
99
public let company: String?
@@ -69,8 +69,46 @@ public struct Address: Decodable, Equatable, GeneratedFakeable {
6969
phone: phone,
7070
email: email)
7171
}
72+
73+
public static var empty: Address {
74+
self.init(firstName: "",
75+
lastName: "",
76+
company: nil,
77+
address1: "",
78+
address2: nil,
79+
city: "",
80+
state: "",
81+
postcode: "",
82+
country: "",
83+
phone: nil,
84+
email: nil)
85+
}
7286
}
7387

88+
extension Address: Equatable {
89+
// custom implementation to handle empty strings and `nil` comparison
90+
public static func ==(lhs: Address, rhs: Address) -> Bool {
91+
return lhs.firstName == rhs.firstName &&
92+
lhs.lastName == rhs.lastName &&
93+
(lhs.company == rhs.company ||
94+
lhs.company == nil && rhs.company?.isEmpty == true ||
95+
lhs.company?.isEmpty == true && rhs.company == nil) &&
96+
lhs.address1 == rhs.address1 &&
97+
(lhs.address2 == rhs.address2 ||
98+
lhs.address2 == nil && rhs.address2?.isEmpty == true ||
99+
lhs.address2?.isEmpty == true && rhs.address2 == nil) &&
100+
lhs.city == rhs.city &&
101+
lhs.state == rhs.state &&
102+
lhs.postcode == rhs.postcode &&
103+
lhs.country == rhs.country &&
104+
(lhs.phone == rhs.phone ||
105+
lhs.phone == nil && rhs.phone?.isEmpty == true ||
106+
lhs.phone?.isEmpty == true && rhs.phone == nil) &&
107+
(lhs.email == rhs.email ||
108+
lhs.email == nil && rhs.email?.isEmpty == true ||
109+
lhs.email?.isEmpty == true && rhs.email == nil)
110+
}
111+
}
74112

75113
/// Defines all of the Address's CodingKeys.
76114
///

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

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import UIKit
77
///
88
final class EditAddressHostingController: UIHostingController<EditAddressForm> {
99

10-
init() {
11-
super.init(rootView: EditAddressForm())
10+
init(viewModel: EditAddressFormViewModel) {
11+
super.init(rootView: EditAddressForm(viewModel: viewModel))
1212
}
1313

1414
required dynamic init?(coder aDecoder: NSCoder) {
@@ -20,9 +20,15 @@ final class EditAddressHostingController: UIHostingController<EditAddressForm> {
2020
///
2121
struct EditAddressForm: View {
2222

23+
@ObservedObject private var viewModel: EditAddressFormViewModel
24+
2325
/// Set it to `true` to present the country selector.
2426
///
25-
@State var showCountrySelector = false
27+
@State var showCountrySelector: Bool = false
28+
29+
init(viewModel: EditAddressFormViewModel) {
30+
self.viewModel = viewModel
31+
}
2632

2733
/// Set it to `true` to present the state selector.
2834
///
@@ -36,28 +42,28 @@ struct EditAddressForm: View {
3642
VStack(spacing: 0) {
3743
TitleAndTextFieldRow(title: Localization.firstNameField,
3844
placeholder: "",
39-
text: .constant(""),
45+
text: $viewModel.firstName,
4046
symbol: nil,
4147
keyboardType: .default)
4248
Divider()
4349
.padding(.leading, Constants.dividerPadding)
4450
TitleAndTextFieldRow(title: Localization.lastNameField,
4551
placeholder: "",
46-
text: .constant(""),
52+
text: $viewModel.lastName,
4753
symbol: nil,
4854
keyboardType: .default)
4955
Divider()
5056
.padding(.leading, Constants.dividerPadding)
5157
TitleAndTextFieldRow(title: Localization.emailField,
5258
placeholder: "",
53-
text: .constant(""),
59+
text: $viewModel.email,
5460
symbol: nil,
5561
keyboardType: .emailAddress)
5662
Divider()
5763
.padding(.leading, Constants.dividerPadding)
5864
TitleAndTextFieldRow(title: Localization.phoneField,
5965
placeholder: "",
60-
text: .constant(""),
66+
text: $viewModel.phone,
6167
symbol: nil,
6268
keyboardType: .phonePad)
6369
}
@@ -70,35 +76,35 @@ struct EditAddressForm: View {
7076
Group {
7177
TitleAndTextFieldRow(title: Localization.companyField,
7278
placeholder: Localization.placeholderOptional,
73-
text: .constant(""),
79+
text: $viewModel.company,
7480
symbol: nil,
7581
keyboardType: .default)
7682
Divider()
7783
.padding(.leading, Constants.dividerPadding)
7884
TitleAndTextFieldRow(title: Localization.address1Field,
7985
placeholder: "",
80-
text: .constant(""),
86+
text: $viewModel.address1,
8187
symbol: nil,
8288
keyboardType: .default)
8389
Divider()
8490
.padding(.leading, Constants.dividerPadding)
8591
TitleAndTextFieldRow(title: Localization.address2Field,
8692
placeholder: "Optional",
87-
text: .constant(""),
93+
text: $viewModel.address2,
8894
symbol: nil,
8995
keyboardType: .default)
9096
Divider()
9197
.padding(.leading, Constants.dividerPadding)
9298
TitleAndTextFieldRow(title: Localization.cityField,
9399
placeholder: "",
94-
text: .constant(""),
100+
text: $viewModel.city,
95101
symbol: nil,
96102
keyboardType: .default)
97103
Divider()
98104
.padding(.leading, Constants.dividerPadding)
99105
TitleAndTextFieldRow(title: Localization.postcodeField,
100106
placeholder: "",
101-
text: .constant(""),
107+
text: $viewModel.postcode,
102108
symbol: nil,
103109
keyboardType: .default)
104110
Divider()
@@ -127,8 +133,7 @@ struct EditAddressForm: View {
127133
.navigationBarItems(trailing: Button(Localization.done) {
128134
// TODO: save changes
129135
}
130-
.disabled(true) // TODO: enable if there are pending changes
131-
)
136+
.disabled(!viewModel.isDoneButtonEnabled))
132137

133138
// Go to edit country
134139
// TODO: Move `CountrySelectorViewModel` creation to the VM when it exists.
@@ -178,10 +183,25 @@ private extension EditAddressForm {
178183

179184
#if DEBUG
180185

186+
import struct Yosemite.Address
187+
181188
struct EditAddressForm_Previews: PreviewProvider {
189+
static let sampleAddress = Address(firstName: "Johnny",
190+
lastName: "Appleseed",
191+
company: nil,
192+
address1: "234 70th Street",
193+
address2: nil,
194+
city: "Niagara Falls",
195+
state: "NY",
196+
postcode: "14304",
197+
country: "US",
198+
phone: "333-333-3333",
199+
200+
static let sampleViewModel = EditAddressFormViewModel(address: sampleAddress)
201+
182202
static var previews: some View {
183203
NavigationView {
184-
EditAddressForm()
204+
EditAddressForm(viewModel: sampleViewModel)
185205
}
186206
}
187207
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Yosemite
2+
3+
final class EditAddressFormViewModel: ObservableObject {
4+
5+
init(address: Address?) {
6+
self.originalAddress = address ?? .empty
7+
updateFieldsWithOriginalAddress()
8+
}
9+
10+
/// Original `Address` model.
11+
///
12+
private let originalAddress: Address
13+
14+
// MARK: User Fields
15+
16+
@Published var firstName: String = ""
17+
@Published var lastName: String = ""
18+
@Published var email: String = ""
19+
@Published var phone: String = ""
20+
21+
// MARK: Address Fields
22+
23+
@Published var company: String = ""
24+
@Published var address1: String = ""
25+
@Published var address2: String = ""
26+
@Published var city: String = ""
27+
@Published var postcode: String = ""
28+
29+
// MARK: Navigation and utility
30+
31+
/// Return `true` if the done button should be enabled.
32+
///
33+
var isDoneButtonEnabled: Bool {
34+
return originalAddress != addressFromFields
35+
}
36+
}
37+
38+
private extension EditAddressFormViewModel {
39+
func updateFieldsWithOriginalAddress() {
40+
firstName = originalAddress.firstName
41+
lastName = originalAddress.lastName
42+
email = originalAddress.email ?? ""
43+
phone = originalAddress.phone ?? ""
44+
45+
company = originalAddress.company ?? ""
46+
address1 = originalAddress.address1
47+
address2 = originalAddress.address2 ?? ""
48+
city = originalAddress.city
49+
postcode = originalAddress.postcode
50+
51+
// TODO: Add country and state init
52+
}
53+
54+
var addressFromFields: Address {
55+
Address(firstName: firstName,
56+
lastName: lastName,
57+
company: company.isEmpty ? nil : company,
58+
address1: address1,
59+
address2: company.isEmpty ? nil : company,
60+
city: city,
61+
state: originalAddress.state, // TODO: replace with local value
62+
postcode: postcode,
63+
country: originalAddress.country, // TODO: replace with local value
64+
phone: phone.isEmpty ? nil : phone,
65+
email: email.isEmpty ? nil : email)
66+
}
67+
}

WooCommerce/Classes/ViewRelated/Orders/Order Details/OrderDetailsViewController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,8 @@ private extension OrderDetailsViewController {
661661
}
662662

663663
func editShippingAddressTapped() {
664-
let editAddressViewController = EditAddressHostingController()
664+
let viewModel = EditAddressFormViewModel(address: viewModel.order.shippingAddress)
665+
let editAddressViewController = EditAddressHostingController(viewModel: viewModel)
665666
show(editAddressViewController, sender: self)
666667
}
667668

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,8 @@
819819
AEDDDA1A25CAB2170077F9B2 /* AttributePickerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = AEDDDA1925CAB2170077F9B2 /* AttributePickerViewController.xib */; };
820820
AEE1D4F525D14F88006A490B /* AttributeOptionListSelectorCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE1D4F425D14F88006A490B /* AttributeOptionListSelectorCommand.swift */; };
821821
AEE1D4FF25D1580E006A490B /* AttributeOptionListSelectorCommandTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE1D4FE25D1580E006A490B /* AttributeOptionListSelectorCommandTests.swift */; };
822+
AEE2610F26E664CE00B142A0 /* EditAddressFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE2610E26E664CE00B142A0 /* EditAddressFormViewModel.swift */; };
823+
AEE2611126E6785400B142A0 /* EditAddressFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE2611026E6785400B142A0 /* EditAddressFormViewModelTests.swift */; };
822824
B50911302049E27A007D25DC /* DashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509112D2049E27A007D25DC /* DashboardViewController.swift */; };
823825
B509FED121C041DF000076A9 /* Locale+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509FED021C041DF000076A9 /* Locale+Woo.swift */; };
824826
B509FED321C05121000076A9 /* SupportManagerAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509FED221C05121000076A9 /* SupportManagerAdapter.swift */; };
@@ -2223,6 +2225,8 @@
22232225
AEDDDA1925CAB2170077F9B2 /* AttributePickerViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AttributePickerViewController.xib; sourceTree = "<group>"; };
22242226
AEE1D4F425D14F88006A490B /* AttributeOptionListSelectorCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeOptionListSelectorCommand.swift; sourceTree = "<group>"; };
22252227
AEE1D4FE25D1580E006A490B /* AttributeOptionListSelectorCommandTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeOptionListSelectorCommandTests.swift; sourceTree = "<group>"; };
2228+
AEE2610E26E664CE00B142A0 /* EditAddressFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAddressFormViewModel.swift; sourceTree = "<group>"; };
2229+
AEE2611026E6785400B142A0 /* EditAddressFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAddressFormViewModelTests.swift; sourceTree = "<group>"; };
22262230
B509112D2049E27A007D25DC /* DashboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardViewController.swift; sourceTree = "<group>"; };
22272231
B509FED021C041DF000076A9 /* Locale+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+Woo.swift"; sourceTree = "<group>"; };
22282232
B509FED221C05121000076A9 /* SupportManagerAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportManagerAdapter.swift; sourceTree = "<group>"; };
@@ -3885,6 +3889,7 @@
38853889
isa = PBXGroup;
38863890
children = (
38873891
26C6E8E226E2D85300C7BB0F /* CountrySelector */,
3892+
AEE2611026E6785400B142A0 /* EditAddressFormViewModelTests.swift */,
38883893
);
38893894
path = Addresses;
38903895
sourceTree = "<group>";
@@ -4816,6 +4821,7 @@
48164821
isa = PBXGroup;
48174822
children = (
48184823
AECD57D126DFDF7500A3B580 /* EditAddressForm.swift */,
4824+
AEE2610E26E664CE00B142A0 /* EditAddressFormViewModel.swift */,
48194825
2662D90926E16B3600E25611 /* FilterListSelector.swift */,
48204826
2662D90726E15D6E00E25611 /* CountrySelectorCommand.swift */,
48214827
26C6E8DF26E2B7BD00C7BB0F /* CountrySelectorViewModel.swift */,
@@ -7068,6 +7074,7 @@
70687074
B59D49CD219B587E006BF0AD /* UILabel+OrderStatus.swift in Sources */,
70697075
265BCA0C2430E741004E53EE /* ProductCategoryTableViewCell.swift in Sources */,
70707076
451A9973260E39270059D135 /* ShippingLabelPackageNumberRow.swift in Sources */,
7077+
AEE2610F26E664CE00B142A0 /* EditAddressFormViewModel.swift in Sources */,
70717078
025C00BA25514A7100FAC222 /* BarcodeScannerFrameScaler.swift in Sources */,
70727079
02913E9723A774E600707A0C /* DecimalInputFormatter.swift in Sources */,
70737080
E15F163126C5117300D3059B /* InPersonPaymentsNoConnectionView.swift in Sources */,
@@ -7869,6 +7876,7 @@
78697876
02279594237A60FD00787C63 /* AztecHeaderFormatBarCommandTests.swift in Sources */,
78707877
0259EF77246BF4EC00B84FDF /* ProductDetailsFactoryTests.swift in Sources */,
78717878
4574745F24EA9ADE00CF49BC /* ProductTypeBottomSheetListSelectorCommandTests.swift in Sources */,
7879+
AEE2611126E6785400B142A0 /* EditAddressFormViewModelTests.swift in Sources */,
78727880
0212683724C049F000F8A892 /* ProductListMultiSelectorDataSourceTests.swift in Sources */,
78737881
5768315126694ADC00FDFB6C /* AuthenticationManagerTests.swift in Sources */,
78747882
D82DFB4C225F303200EFE2CB /* EmptyListMessageWithActionTests.swift in Sources */,

0 commit comments

Comments
 (0)