Skip to content

Commit 1aef4ef

Browse files
Decouple OrderListCellViewModel dependencies between app and watch (#15920)
2 parents b9f8568 + 2153590 commit 1aef4ef

File tree

12 files changed

+248
-184
lines changed

12 files changed

+248
-184
lines changed

Modules/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ enum XcodeSupport {
342342
.xcodeTarget(
343343
XcodeTargetNames.notificationExtension,
344344
dependencies: [
345-
"Networking",
345+
"NetworkingCore",
346346
"WooFoundation",
347347
.product(name: "KeychainAccess", package: "KeychainAccess"),
348348
]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import Foundation
2+
import Contacts
3+
import NetworkingCore
4+
5+
extension Address {
6+
/// Returns the First + LastName combined according to language rules and Locale.
7+
///
8+
var fullName: String {
9+
var components = PersonNameComponents()
10+
components.givenName = firstName
11+
components.familyName = lastName
12+
13+
return PersonNameComponentsFormatter.localizedString(from: components, style: .medium, options: [])
14+
}
15+
16+
/// Returns the Postal Address, formatted and ready for display.
17+
///
18+
var formattedPostalAddress: String? {
19+
return postalAddress.formatted(as: .mailingAddress)
20+
}
21+
}
22+
23+
// MARK: - Private Methods
24+
//
25+
private extension Address {
26+
/// Returns a CNPostalAddress with the receiver's properties
27+
///
28+
var postalAddress: CNPostalAddress {
29+
let refinedAddress = refineAddressState()
30+
let address = CNMutablePostalAddress()
31+
32+
address.street = refinedAddress.combinedAddress
33+
address.city = refinedAddress.city
34+
address.state = refinedAddress.state
35+
address.postalCode = refinedAddress.postcode
36+
address.country = refinedAddress.country
37+
address.isoCountryCode = refinedAddress.country
38+
39+
return address
40+
}
41+
42+
func refineAddressState() -> Address {
43+
#if !os(watchOS)
44+
// https://github.com/woocommerce/woocommerce-ios/issues/5851
45+
// The backend gives us the state code in the state field.
46+
// For these countries we should show the state name instead, as the code is not identifiable (e.g. JP01 -> Hokkaido)
47+
guard ["JP", "TR"].contains(country),
48+
let stateName = LocallyStoredStateNameRetriever().retrieveLocallyStoredStateName(of: self) else {
49+
return self
50+
}
51+
52+
return copy(state: stateName)
53+
#else
54+
// Watch app does not have a local storage of countries.
55+
// In this case we don't apply any special treatment
56+
return self
57+
#endif
58+
}
59+
60+
/// Returns the two Address Lines combined (if there are, effectively, two lines).
61+
/// Per US Post Office standardized rules for address lines. Ref. https://pe.usps.com/text/pub28/28c2_001.htm
62+
///
63+
var combinedAddress: String {
64+
guard let address2 = address2, address2.isEmpty == false else {
65+
return address1
66+
}
67+
68+
return address1 + "\n" + address2
69+
}
70+
}

WooCommerce/Classes/Model/Address+Woo.swift

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
11
import Foundation
22
import Contacts
3-
4-
#if canImport(Yosemite)
53
import Yosemite
6-
#elseif canImport(NetworkingCore)
7-
import NetworkingCore
8-
#endif
9-
104

115
// Yosemite.Address Helper Methods
126
//
137
extension Address {
14-
15-
/// Returns the First + LastName combined according to language rules and Locale.
16-
///
17-
var fullName: String {
18-
var components = PersonNameComponents()
19-
components.givenName = firstName
20-
components.familyName = lastName
21-
22-
return PersonNameComponentsFormatter.localizedString(from: components, style: .medium, options: [])
23-
}
24-
258
/// Returns the `fullName` and `company` (on a new line). If either the `fullname` or `company` is empty,
269
/// then a single line is returned containing the other value.
2710
///
@@ -38,13 +21,6 @@ extension Address {
3821
return output.joined(separator: "\n")
3922
}
4023

41-
42-
/// Returns the Postal Address, formatted and ready for display.
43-
///
44-
var formattedPostalAddress: String? {
45-
return postalAddress.formatted(as: .mailingAddress)
46-
}
47-
4824
/// Returns the `fullName`, `company`, and `address`. Basically this var combines the
4925
/// `fullNameWithCompany` & `formattedPostalAddress` vars.
5026
///
@@ -124,56 +100,3 @@ extension Address {
124100
}
125101
#endif
126102
}
127-
128-
129-
// MARK: - Private Methods
130-
//
131-
private extension Address {
132-
133-
/// Returns the two Address Lines combined (if there are, effectively, two lines).
134-
/// Per US Post Office standardized rules for address lines. Ref. https://pe.usps.com/text/pub28/28c2_001.htm
135-
///
136-
var combinedAddress: String {
137-
guard let address2 = address2, address2.isEmpty == false else {
138-
return address1
139-
}
140-
141-
return address1 + "\n" + address2
142-
}
143-
144-
145-
/// Returns a CNPostalAddress with the receiver's properties
146-
///
147-
var postalAddress: CNPostalAddress {
148-
let refinedAddress = refineAddressState()
149-
let address = CNMutablePostalAddress()
150-
151-
address.street = refinedAddress.combinedAddress
152-
address.city = refinedAddress.city
153-
address.state = refinedAddress.state
154-
address.postalCode = refinedAddress.postcode
155-
address.country = refinedAddress.country
156-
address.isoCountryCode = refinedAddress.country
157-
158-
return address
159-
}
160-
161-
162-
func refineAddressState() -> Address {
163-
#if !os(watchOS)
164-
// https://github.com/woocommerce/woocommerce-ios/issues/5851
165-
// The backend gives us the state code in the state field.
166-
// For these countries we should show the state name instead, as the code is not identifiable (e.g. JP01 -> Hokkaido)
167-
guard ["JP", "TR"].contains(country),
168-
let stateName = LocallyStoredStateNameRetriever().retrieveLocallyStoredStateName(of: self) else {
169-
return self
170-
}
171-
172-
return copy(state: stateName)
173-
#else
174-
// Watch app does not have a local storage of countries.
175-
// In this case we don't apply any special treatment
176-
return self
177-
#endif
178-
}
179-
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Foundation
2+
import Yosemite
3+
4+
extension MarkOrderAsReadUseCase {
5+
/// Async method that marks the order note as read if it is the notification for the last order.
6+
/// We do it in a way that first we syncronize notification to get the remote `Note`
7+
/// and then we compare local `orderID` with the one from remote `Note`.
8+
/// If they match we mark it as read.
9+
/// Returns syncronized note if marking was successful and error if some error happened
10+
@MainActor
11+
static func markOrderNoteAsReadIfNeeded(stores: StoresManager, noteID: Int64, orderID: Int) async -> Result<Note, Error> {
12+
let syncronizedNoteResult: Result<Note, Error> = await withCheckedContinuation { continuation in
13+
let action = NotificationAction.synchronizeNotification(noteID: noteID) { syncronizedNote, error in
14+
guard let syncronizedNote = syncronizedNote else {
15+
continuation.resume(returning: .failure(MarkOrderAsReadUseCase.Error.unavailableNote))
16+
return
17+
}
18+
continuation.resume(returning: .success(syncronizedNote))
19+
}
20+
stores.dispatch(action)
21+
}
22+
23+
switch syncronizedNoteResult {
24+
case .success(let syncronizedNote):
25+
guard let syncronizedNoteOrderID = syncronizedNote.meta.identifier(forKey: .order),
26+
syncronizedNoteOrderID == orderID,
27+
syncronizedNote.read == false else {
28+
return .failure(MarkOrderAsReadUseCase.Error.noNeedToMarkAsRead)
29+
}
30+
31+
let updateNoteStatusResult: Result<Note, Error> = await withCheckedContinuation { continuation in
32+
let syncAction = NotificationAction.updateReadStatus(noteID: noteID, read: true) { error in
33+
if let error {
34+
continuation.resume(returning: .failure(MarkOrderAsReadUseCase.Error.failure(error)))
35+
} else {
36+
continuation.resume(returning: .success(syncronizedNote))
37+
}
38+
}
39+
stores.dispatch(syncAction)
40+
}
41+
42+
switch updateNoteStatusResult {
43+
case .success(let note):
44+
return .success(note)
45+
case .failure(let error):
46+
return .failure(error)
47+
}
48+
case .failure(let error):
49+
return .failure(error)
50+
}
51+
}
52+
}

WooCommerce/Classes/Model/MarkOrderAsReadUseCase.swift

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import Foundation
2-
3-
#if canImport(Networking)
4-
import Networking
5-
#elseif canImport(NetworkingCore)
62
import NetworkingCore
7-
#endif
8-
9-
#if canImport(Yosemite) && !NOTIFICATION_EXTENSION
10-
import Yosemite
11-
#endif
123

134
struct MarkOrderAsReadUseCase {
145
/// Possible error states.
@@ -19,56 +10,6 @@ struct MarkOrderAsReadUseCase {
1910
case noNeedToMarkAsRead
2011
}
2112

22-
#if canImport(Yosemite) && !NOTIFICATION_EXTENSION
23-
/// Async method that marks the order note as read if it is the notification for the last order.
24-
/// We do it in a way that first we syncronize notification to get the remote `Note`
25-
/// and then we compare local `orderID` with the one from remote `Note`.
26-
/// If they match we mark it as read.
27-
/// Returns syncronized note if marking was successful and error if some error happened
28-
@MainActor
29-
static func markOrderNoteAsReadIfNeeded(stores: StoresManager, noteID: Int64, orderID: Int) async -> Result<Note, Error> {
30-
let syncronizedNoteResult: Result<Note, Error> = await withCheckedContinuation { continuation in
31-
let action = NotificationAction.synchronizeNotification(noteID: noteID) { syncronizedNote, error in
32-
guard let syncronizedNote = syncronizedNote else {
33-
continuation.resume(returning: .failure(MarkOrderAsReadUseCase.Error.unavailableNote))
34-
return
35-
}
36-
continuation.resume(returning: .success(syncronizedNote))
37-
}
38-
stores.dispatch(action)
39-
}
40-
41-
switch syncronizedNoteResult {
42-
case .success(let syncronizedNote):
43-
guard let syncronizedNoteOrderID = syncronizedNote.meta.identifier(forKey: .order),
44-
syncronizedNoteOrderID == orderID,
45-
syncronizedNote.read == false else {
46-
return .failure(MarkOrderAsReadUseCase.Error.noNeedToMarkAsRead)
47-
}
48-
49-
let updateNoteStatusResult: Result<Note, Error> = await withCheckedContinuation { continuation in
50-
let syncAction = NotificationAction.updateReadStatus(noteID: noteID, read: true) { error in
51-
if let error {
52-
continuation.resume(returning: .failure(MarkOrderAsReadUseCase.Error.failure(error)))
53-
} else {
54-
continuation.resume(returning: .success(syncronizedNote))
55-
}
56-
}
57-
stores.dispatch(syncAction)
58-
}
59-
60-
switch updateNoteStatusResult {
61-
case .success(let note):
62-
return .success(note)
63-
case .failure(let error):
64-
return .failure(error)
65-
}
66-
case .failure(let error):
67-
return .failure(error)
68-
}
69-
}
70-
#endif
71-
7213
/// Async method that marks the order note as read if it is the notification for the last order.
7314
/// We do it in a way that first we syncronize notification to get the remote `Note`
7415
/// and then we compare local `orderID` with the one from remote `Note`.

WooCommerce/Classes/ServiceLocator/PushNotification.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import Foundation
2-
3-
#if canImport(Networking)
4-
import struct Networking.Note
5-
#elseif canImport(NetworkingCore)
62
import struct NetworkingCore.Note
7-
#endif
3+
84
#if DEBUG
95
import UserNotifications
106
#endif
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
3+
/// Shared localization for `OrderListCellViewModel` between `WooCommerce` and `Woo Watch App` targets
4+
enum OrderListCellViewModelLocalization {
5+
static func title(orderNumber: String, customerName: String) -> String {
6+
let format = NSLocalizedString(
7+
"orderlistcellviewmodel.cell.title",
8+
value: "#%@ %@",
9+
comment: "In Order List,"
10+
+ " the pattern to show the order number. For example, “#123456”."
11+
+ " The %@ placeholder is the order number.")
12+
return String.localizedStringWithFormat(format, orderNumber, customerName)
13+
}
14+
static let guestName = NSLocalizedString(
15+
"orderlistcellviewmodel.customerName.guestName",
16+
value: "Guest",
17+
comment: "In Order List, the name of the billed person when there are no first and last name.")
18+
}

0 commit comments

Comments
 (0)