Skip to content

Commit ace04ba

Browse files
committed
Merge branch 'develop' into issue/4745-service-packages-endpoint
2 parents 6e6a83a + 7305fa4 commit ace04ba

24 files changed

+744
-225
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
///
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Foundation
2+
3+
/// A structure to hold the basic details for a selected package on Package Details screen of Shipping Label purchase flow.
4+
///
5+
struct ShippingLabelPackageAttributes: Hashable, Equatable {
6+
7+
/// ID of the selected package.
8+
let packageID: String
9+
10+
/// Total weight of the package in string value.
11+
let totalWeight: String
12+
13+
/// List of product or variation IDs for items in the package.
14+
let productIDs: [Int64]
15+
}

WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalScanningForReader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ private extension CardPresentModalScanningForReader {
4747
)
4848

4949
static let instruction = NSLocalizedString(
50-
"Press the power button of your reader until you see a flashing blue light",
50+
"To turn on your card reader, briefly press its power button. When powered on, the power LED will blink blue.",
5151
comment: "Label within the modal dialog that appears when searching for a card reader"
5252
)
5353

WooCommerce/Classes/ViewRelated/ListSelector/ListSelector.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import SwiftUI
22

3+
/// Protocol required to re-render the view when the command updates any of it's content.
4+
///
5+
protocol ObservableListSelectorCommand: ListSelectorCommand, ObservableObject {}
6+
37
/// `SwiftUI` wrapper for `ListSelectorViewController`
48
///
5-
struct ListSelector<Command: ListSelectorCommand>: UIViewControllerRepresentable {
9+
struct ListSelector<Command: ObservableListSelectorCommand>: UIViewControllerRepresentable {
610

711
/// Command that defines cell style and provide data.
812
///
9-
let command: Command
13+
@ObservedObject var command: Command
1014

1115
/// Table view style.
1216
///

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

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ import Yosemite
33

44
/// Command to be used to select a country when editing addresses.
55
///
6-
final class CountrySelectorCommand: ListSelectorCommand {
6+
final class CountrySelectorCommand: ObservableListSelectorCommand {
77
typealias Model = Country
88
typealias Cell = BasicTableViewCell
99

1010
/// Original array of countries.
1111
///
12-
private let countries: [Country]
12+
private var countries: [Country]
1313

1414
/// Data to display
1515
///
16-
private(set) var data: [Country]
16+
@Published private(set) var data: [Country]
1717

1818
/// Current selected country
1919
///
@@ -23,7 +23,7 @@ final class CountrySelectorCommand: ListSelectorCommand {
2323
///
2424
let navigationBarTitle: String? = ""
2525

26-
init(countries: [Country] = temporaryCountries, selected: Country? = nil) {
26+
init(countries: [Country], selected: Country? = nil) {
2727
self.countries = countries
2828
self.data = countries
2929
self.selected = selected
@@ -41,6 +41,13 @@ final class CountrySelectorCommand: ListSelectorCommand {
4141
cell.textLabel?.text = model.name
4242
}
4343

44+
/// Resets countries data.
45+
///
46+
func resetCountries(_ countries: [Country]) {
47+
self.countries = countries
48+
self.data = countries
49+
}
50+
4451
/// Filter available countries that contains a given search term.
4552
///
4653
func filterCountries(term: String) {
@@ -51,16 +58,3 @@ final class CountrySelectorCommand: ListSelectorCommand {
5158
data = countries.filter { $0.name.localizedCaseInsensitiveContains(term) }
5259
}
5360
}
54-
55-
// MARK: Temporary Methods
56-
extension CountrySelectorCommand {
57-
58-
// Supported countries will come from the view model later.
59-
//
60-
private static let temporaryCountries: [Country] = {
61-
return Locale.isoRegionCodes.map { regionCode in
62-
let name = Locale.current.localizedString(forRegionCode: regionCode) ?? ""
63-
return Country(code: regionCode, name: name, states: [])
64-
}
65-
}()
66-
}
Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,87 @@
11
import Combine
2+
import SwiftUI
3+
import Yosemite
4+
import protocol Storage.StorageManagerType
25

36
/// View Model for the `CountrySelector` view.
47
///
5-
final class CountrySelectorViewModel: ObservableObject {
8+
final class CountrySelectorViewModel: FilterListSelectorViewModelable, ObservableObject {
69

710
/// Current search term entered by the user.
811
/// Each update will trigger an update to the `command` that contains the country data.
9-
@Published var searchTerm = "" {
12+
var searchTerm: String = "" {
1013
didSet {
1114
command.filterCountries(term: searchTerm)
1215
}
1316
}
1417

1518
/// Command that powers the `ListSelector` view.
1619
///
17-
let command = CountrySelectorCommand()
20+
let command = CountrySelectorCommand(countries: [])
21+
22+
/// Navigation title
23+
///
24+
let navigationTitle = Localization.title
25+
26+
/// Filter text field placeholder
27+
///
28+
let filterPlaceholder = Localization.placeholder
29+
30+
/// ResultsController for stored countries.
31+
///
32+
private lazy var countriesResultsController: ResultsController<StorageCountry> = {
33+
let countriesDescriptor = NSSortDescriptor(key: "name", ascending: true)
34+
return ResultsController<StorageCountry>(storageManager: storageManager, sortedBy: [countriesDescriptor])
35+
}()
36+
37+
/// Storage to fetch countries
38+
///
39+
private let storageManager: StorageManagerType
40+
41+
/// Stores to sync countries
42+
///
43+
private let stores: StoresManager
44+
45+
/// Current `SiteID`, needed to sync countries from a remote source.
46+
///
47+
private let siteID: Int64
48+
49+
init(siteID: Int64, storageManager: StorageManagerType = ServiceLocator.storageManager, stores: StoresManager = ServiceLocator.stores) {
50+
self.siteID = siteID
51+
self.storageManager = storageManager
52+
self.stores = stores
53+
fetchAndBindCountries()
54+
}
55+
}
56+
57+
// MARK: Helpers
58+
private extension CountrySelectorViewModel {
59+
/// Fetches & Binds countries from storage, If there are no stored countries, sync them from a remote source.
60+
///
61+
func fetchAndBindCountries() {
62+
// Bind stored countries & command
63+
countriesResultsController.onDidChangeContent = { [weak self] in
64+
guard let self = self else { return }
65+
self.command.resetCountries(self.countriesResultsController.fetchedObjects)
66+
}
67+
68+
// Initial fetch
69+
try? countriesResultsController.performFetch()
70+
71+
// Reset countries with fetched data or sync countries if needed.
72+
if !countriesResultsController.isEmpty {
73+
command.resetCountries(countriesResultsController.fetchedObjects)
74+
} else {
75+
let action = DataAction.synchronizeCountries(siteID: siteID, onCompletion: { _ in })
76+
stores.dispatch(action)
77+
}
78+
}
79+
}
80+
81+
// MARK: Constants
82+
private extension CountrySelectorViewModel {
83+
enum Localization {
84+
static let title = NSLocalizedString("Country", comment: "Title to select country from the edit address screen")
85+
static let placeholder = NSLocalizedString("Filter Countries", comment: "Placeholder on the search field to search for a specific country")
86+
}
1887
}

0 commit comments

Comments
 (0)