Skip to content

Commit e29670e

Browse files
committed
Pass the selected country via a binding
1 parent 16f6426 commit e29670e

File tree

5 files changed

+79
-25
lines changed

5 files changed

+79
-25
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import Foundation
22
import Yosemite
33

4+
import struct SwiftUI.Binding
5+
46
/// Command to be used to select a country when editing addresses.
57
///
68
final class CountrySelectorCommand: ObservableListSelectorCommand {
@@ -17,24 +19,24 @@ final class CountrySelectorCommand: ObservableListSelectorCommand {
1719

1820
/// Current selected country
1921
///
20-
private(set) var selected: Country?
22+
@Binding private(set) var selected: Country?
2123

2224
/// Navigation bar title
2325
///
2426
let navigationBarTitle: String? = ""
2527

26-
init(countries: [Country], selected: Country? = nil) {
28+
init(countries: [Country], selected: Binding<Country?>) {
2729
self.countries = countries
2830
self.data = countries
29-
self.selected = selected
31+
self._selected = selected
3032
}
3133

3234
func handleSelectedChange(selected: Country, viewController: ViewController) {
3335
self.selected = selected
3436
}
3537

3638
func isSelected(model: Country) -> Bool {
37-
model == selected
39+
model.code == selected?.code // I'm only comparing country codes because states can be unsorted
3840
}
3941

4042
func configureCell(cell: BasicTableViewCell, model: Country) {

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,18 @@ final class CountrySelectorViewModel: FilterListSelectorViewModelable, Observabl
2727
///
2828
let filterPlaceholder = Localization.placeholder
2929

30-
/// Current `SiteID`, needed to sync countries from a remote source.
30+
/// Subscriptions store
3131
///
32-
private let siteID: Int64
32+
private var subscriptions = Set<AnyCancellable>()
3333

34-
init(siteID: Int64, countries: [Country]) {
35-
self.siteID = siteID
36-
self.command = CountrySelectorCommand(countries: countries)
34+
init(countries: [Country], selected: CurrentValueSubject<Country?, Never>) {
35+
self.command = CountrySelectorCommand(countries: countries, selected: selected.value)
36+
37+
/// Bind selected country to the provided selected subject
38+
///
39+
command.$selected.sink { country in
40+
selected.send(country)
41+
}.store(in: &subscriptions)
3742
}
3843
}
3944

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ struct EditAddressForm: View {
112112
}
113113

114114
Group {
115-
TitleAndValueRow(title: Localization.countryField, value: Localization.placeholderSelectOption, selectable: true) {
115+
TitleAndValueRow(title: Localization.countryField, value: viewModel.fields.country, selectable: true) {
116116
showCountrySelector = true
117117
}
118118
Divider()

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

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ final class EditAddressFormViewModel: ObservableObject {
3737
self.originalAddress = address ?? .empty
3838
self.storageManager = storageManager
3939
self.stores = stores
40-
updateFieldsWithOriginalAddress()
41-
bindNavigationTrailingItemPublisher()
4240

4341
// Listen only to the first emitted event.
44-
onLoadTrigger.first().sink {
42+
onLoadTrigger.first().sink { [weak self] in
43+
guard let self = self else { return }
44+
self.bindNavigationTrailingItemPublisher()
4545
self.bindSyncTrigger()
4646
self.fetchStoredCountriesAndTriggerSyncIfNeeded()
47+
self.updateFieldsWithOriginalAddress()
4748
}.store(in: &subscriptions)
4849
}
4950

@@ -55,6 +56,10 @@ final class EditAddressFormViewModel: ObservableObject {
5556
///
5657
@Published var fields = FormFields()
5758

59+
/// Tracks the current selected country
60+
///
61+
private let selectedCountry = CurrentValueSubject<Yosemite.Country?, Never>(nil)
62+
5863
/// Trigger to perform any one time setups.
5964
///
6065
let onLoadTrigger: PassthroughSubject<Void, Never> = PassthroughSubject()
@@ -75,7 +80,7 @@ final class EditAddressFormViewModel: ObservableObject {
7580
/// Creates a view model to be used when selecting a country
7681
///
7782
func createCountryViewModel() -> CountrySelectorViewModel {
78-
CountrySelectorViewModel(siteID: siteID, countries: countriesResultsController.fetchedObjects)
83+
CountrySelectorViewModel(countries: countriesResultsController.fetchedObjects, selected: selectedCountry)
7984
}
8085

8186
/// Update the address remotely and invoke a completion block when finished
@@ -118,7 +123,7 @@ extension EditAddressFormViewModel {
118123
var country: String = ""
119124
var state: String = ""
120125

121-
mutating func update(from address: Address) {
126+
mutating func update(from address: Address, selectedCountry: Yosemite.Country?) {
122127
firstName = address.firstName
123128
lastName = address.lastName
124129
email = address.email ?? ""
@@ -129,11 +134,11 @@ extension EditAddressFormViewModel {
129134
address2 = address.address2 ?? ""
130135
city = address.city
131136
postcode = address.postcode
132-
country = address.country
137+
country = selectedCountry?.name ?? address.country
133138
state = address.state
134139
}
135140

136-
func toAddress() -> Address {
141+
func toAddress(selectedCountry: Yosemite.Country?) -> Address {
137142
Address(firstName: firstName,
138143
lastName: lastName,
139144
company: company.isEmpty ? nil : company,
@@ -142,7 +147,7 @@ extension EditAddressFormViewModel {
142147
city: city,
143148
state: state,
144149
postcode: postcode,
145-
country: country,
150+
country: selectedCountry?.code ?? country,
146151
phone: phone.isEmpty ? nil : phone,
147152
email: email.isEmpty ? nil : email)
148153
}
@@ -151,18 +156,25 @@ extension EditAddressFormViewModel {
151156

152157
private extension EditAddressFormViewModel {
153158
func updateFieldsWithOriginalAddress() {
154-
fields.update(from: originalAddress)
159+
updateSelectedCountryFromOriginalAddress()
160+
fields.update(from: originalAddress, selectedCountry: selectedCountry.value)
161+
}
162+
163+
/// Updates the `selectedCountry` subject by matching the address country code in our stored countries.
164+
///
165+
func updateSelectedCountryFromOriginalAddress() {
166+
selectedCountry.value = countriesResultsController.fetchedObjects.first { $0.code == originalAddress.country }
155167
}
156168

157169
/// Calculates what navigation trailing item should be shown depending on our internal state.
158170
///
159171
func bindNavigationTrailingItemPublisher() {
160172
Publishers.CombineLatest($fields, performingNetworkRequest)
161-
.map { [originalAddress] fields, performingNetworkRequest -> NavigationItem in
173+
.map { [originalAddress, selectedCountry] fields, performingNetworkRequest -> NavigationItem in
162174
guard !performingNetworkRequest else {
163175
return .loading
164176
}
165-
return .done(enabled: originalAddress != fields.toAddress())
177+
return .done(enabled: originalAddress != fields.toAddress(selectedCountry: selectedCountry.value))
166178
}
167179
.assign(to: &$navigationTrailingItem)
168180
}
@@ -173,6 +185,11 @@ private extension EditAddressFormViewModel {
173185
// Initial fetch
174186
try? countriesResultsController.performFetch()
175187

188+
// Updates the selected country when the data store changes.
189+
countriesResultsController.onDidChangeContent = { [weak self] in
190+
self?.updateSelectedCountryFromOriginalAddress()
191+
}
192+
176193
// Trigger a sync request if there are no countries.
177194
guard !countriesResultsController.isEmpty else {
178195
return syncCountriesTrigger.send()

WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/Addresses/CountrySelector/CountrySelectorViewModelTests.swift

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import XCTest
22
import Yosemite
33
import TestKit
4+
import Combine
45
@testable import WooCommerce
56

67
final class CountrySelectorViewModelTests: XCTestCase {
78

8-
let sampleSiteID: Int64 = 123
9+
let selectedSubject = CurrentValueSubject<Country?, Never>(nil)
10+
var subscriptions = Set<AnyCancellable>()
911

1012
override func setUp () {
1113
super.setUp()
14+
15+
selectedSubject.value = nil
16+
subscriptions.removeAll()
1217
}
1318

1419
func test_filter_countries_return_expected_results() {
1520
// Given
16-
let viewModel = CountrySelectorViewModel(siteID: sampleSiteID, countries: Self.sampleCountries)
21+
let viewModel = CountrySelectorViewModel(countries: Self.sampleCountries, selected: selectedSubject)
1722

1823
// When
1924
viewModel.searchTerm = "Co"
@@ -38,7 +43,7 @@ final class CountrySelectorViewModelTests: XCTestCase {
3843

3944
func test_filter_countries_with_uppercase_letters_return_expected_results() {
4045
// Given
41-
let viewModel = CountrySelectorViewModel(siteID: sampleSiteID, countries: Self.sampleCountries)
46+
let viewModel = CountrySelectorViewModel(countries: Self.sampleCountries, selected: selectedSubject)
4247

4348
// When
4449
viewModel.searchTerm = "CO"
@@ -63,7 +68,7 @@ final class CountrySelectorViewModelTests: XCTestCase {
6368

6469
func test_cleaning_search_terms_return_all_countries() {
6570
// Given
66-
let viewModel = CountrySelectorViewModel(siteID: sampleSiteID, countries: Self.sampleCountries)
71+
let viewModel = CountrySelectorViewModel(countries: Self.sampleCountries, selected: selectedSubject)
6772
let totalNumberOfCountries = viewModel.command.data.count
6873

6974
// When
@@ -74,6 +79,31 @@ final class CountrySelectorViewModelTests: XCTestCase {
7479
// Then
7580
XCTAssertEqual(viewModel.command.data.count, totalNumberOfCountries)
7681
}
82+
83+
func test_providing_a_selected_country_is_reflected_on_command() {
84+
// Given
85+
let country = Self.sampleCountries[0]
86+
selectedSubject.value = country
87+
88+
// When
89+
let viewModel = CountrySelectorViewModel(countries: Self.sampleCountries, selected: selectedSubject)
90+
91+
// Then
92+
XCTAssertEqual(viewModel.command.selected, country)
93+
}
94+
95+
func test_selecting_country_via_command_updates_subject() {
96+
// Given
97+
let country = Self.sampleCountries[0]
98+
let viewModel = CountrySelectorViewModel(countries: Self.sampleCountries, selected: selectedSubject)
99+
100+
// When
101+
let viewController = ListSelectorViewController(command: viewModel.command, onDismiss: { _ in }) // Needed because of legacy UIKit ways
102+
viewModel.command.handleSelectedChange(selected: country, viewController: viewController)
103+
104+
// Then
105+
XCTAssertEqual(selectedSubject.value, country)
106+
}
77107
}
78108

79109
// MARK: Helpers

0 commit comments

Comments
 (0)