Skip to content

Commit 63304a5

Browse files
committed
Merge branch 'trunk' into issue/6128-basic-local-implementation
2 parents 39762f1 + 0a44507 commit 63304a5

31 files changed

+927
-80
lines changed

Networking/Networking/Model/ShippingLine.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import Codegen
66
public struct ShippingLine: Codable, Equatable, GeneratedFakeable {
77
public let shippingID: Int64
88
public let methodTitle: String
9-
public let methodID: String
9+
10+
/// Shipping Method ID
11+
///
12+
/// Sending a null value to the REST API removes the Shipping Line.
13+
///
14+
public let methodID: String?
15+
1016
public let total: String
1117
public let totalTax: String
1218
public let taxes: [ShippingLineTax]
@@ -15,7 +21,7 @@ public struct ShippingLine: Codable, Equatable, GeneratedFakeable {
1521
///
1622
public init(shippingID: Int64,
1723
methodTitle: String,
18-
methodID: String,
24+
methodID: String?,
1925
total: String,
2026
totalTax: String,
2127
taxes: [ShippingLineTax]) {

Networking/Networking/Remote/OrdersRemote.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,13 @@ public extension OrdersRemote {
274274
private static let commonOrderFieldValues = [
275275
"id", "parent_id", "number", "status", "currency", "customer_id", "customer_note", "date_created_gmt", "date_modified_gmt", "date_paid_gmt",
276276
"discount_total", "discount_tax", "shipping_total", "shipping_tax", "total", "total_tax", "payment_method", "payment_method_title",
277-
"billing", "coupon_lines", "shipping_lines", "refunds", "fee_lines", "order_key", "tax_lines"
277+
"billing", "coupon_lines", "shipping_lines", "refunds", "fee_lines", "order_key", "tax_lines", "meta_data"
278278
]
279+
// Use with caution. Any fields in here will be overwritten with empty values by
280+
// `Order+ReadOnlyConvertible.swift: Order.update(with:)` when the list of orders is fetched.
281+
// See p91TBi-7yL-p2 for discussion.
279282
private static let singleOrderExtraFieldValues = [
280-
"line_items", "shipping", "meta_data"
283+
"line_items", "shipping"
281284
]
282285
}
283286

Networking/NetworkingTests/Remote/OrdersRemoteTests.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,27 @@ final class OrdersRemoteTests: XCTestCase {
211211
wait(for: [expectation], timeout: Constants.expectationTimeout)
212212
}
213213

214+
func test_update_order_properly_encodes_shipping_lines_for_removal_from_order() throws {
215+
// Given
216+
let remote = OrdersRemote(network: network)
217+
let shipping = ShippingLine(shippingID: 333, methodTitle: "Shipping", methodID: nil, total: "1.23", totalTax: "", taxes: [])
218+
let order = Order.fake().copy(shippingLines: [shipping])
219+
220+
// When
221+
remote.updateOrder(from: 123, order: order, fields: [.shippingLines]) { result in }
222+
223+
// Then
224+
let request = try XCTUnwrap(network.requestsForResponseData.last as? JetpackRequest)
225+
let received = try XCTUnwrap(request.parameters["shipping_lines"] as? [[String: AnyHashable]]).first
226+
let expected: [String: AnyHashable] = [
227+
"id": shipping.shippingID,
228+
"method_title": shipping.methodTitle,
229+
"method_id": NSNull(),
230+
"total": shipping.total
231+
]
232+
assertEqual(received, expected)
233+
}
234+
214235

215236
// MARK: - Load Order Notes Tests
216237

@@ -394,7 +415,7 @@ final class OrdersRemoteTests: XCTestCase {
394415
let expected: [String: AnyHashable] = [
395416
"id": shipping.shippingID,
396417
"method_title": shipping.methodTitle,
397-
"method_id": shipping.methodID,
418+
"method_id": shipping.methodID ?? "",
398419
"total": shipping.total
399420
]
400421
assertEqual(received, expected)

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
-----
55
- [*] Orders: In the experimental Order Creation feature, product variations added to a new order now show a list of their attributes. [https://github.com/woocommerce/woocommerce-ios/pull/6131]
66
- [*] Enlarged the tap area for the action button on the notice view. [https://github.com/woocommerce/woocommerce-ios/pull/6146]
7+
- [*] Reviews: Fixed crash on iPad when tapping the More button. [https://github.com/woocommerce/woocommerce-ios/pull/6187]
78

89
8.5
910
-----

WooCommerce/Classes/Analytics/WooAnalyticsStat.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,12 @@ public enum WooAnalyticsStat: String {
565565
case hubMenuSwitchStoreTapped = "hub_menu_switch_store_tapped"
566566
case hubMenuOptionTapped = "hub_menu_option_tapped"
567567
case hubMenuSettingsTapped = "hub_menu_settings_tapped"
568+
569+
// MARK: Coupons
570+
case couponsLoaded = "coupons_loaded"
571+
case couponsLoadedFailed = "coupons_loaded_failed"
572+
case couponsListSearchTapped = "coupons_list_search_tapped"
573+
case couponDetails = "coupon_details"
568574
}
569575

570576
public extension WooAnalyticsStat {

WooCommerce/Classes/ViewRelated/Coupons/CouponDetails/CouponDetails.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ struct CouponDetails: View {
3434
self.noticePresenter = DefaultNoticePresenter()
3535
viewModel.syncCoupon()
3636
viewModel.loadCouponReport()
37+
38+
ServiceLocator.analytics.track(.couponDetails, withProperties: ["action": "loaded"])
3739
}
3840

3941
private var detailRows: [DetailRow] {
@@ -60,9 +62,11 @@ struct CouponDetails: View {
6062
UIPasteboard.general.string = viewModel.couponCode
6163
let notice = Notice(title: Localization.couponCopied, feedbackType: .success)
6264
noticePresenter.enqueue(notice: notice)
65+
ServiceLocator.analytics.track(.couponDetails, withProperties: ["action": "copied_code"])
6366
}),
6467
.default(Text(Localization.shareCoupon), action: {
6568
showingShareSheet = true
69+
ServiceLocator.analytics.track(.couponDetails, withProperties: ["action": "shared_code"])
6670
}),
6771
.cancel()
6872
]

WooCommerce/Classes/ViewRelated/Coupons/CouponListViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ private extension CouponListViewController {
167167
/// Shows `SearchViewController`.
168168
///
169169
@objc private func displaySearchCoupons() {
170-
// TODO: add analytics
170+
ServiceLocator.analytics.track(.couponsListSearchTapped)
171171
let searchViewController = SearchViewController<TitleAndSubtitleAndStatusTableViewCell, CouponSearchUICommand>(
172172
storeID: siteID,
173173
command: CouponSearchUICommand(),

WooCommerce/Classes/ViewRelated/Coupons/CouponListViewModel.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,23 @@ extension CouponListViewModel: SyncingCoordinatorDelegate {
151151
pageNumber: pageNumber,
152152
pageSize: pageSize) { [weak self] result in
153153
guard let self = self else { return }
154-
self.handleCouponSyncResult(result: result)
154+
self.handleCouponSyncResult(result: result, pageNumber: pageNumber)
155155
onCompletion?(result.isSuccess)
156156
}
157157

158158
storesManager.dispatch(action)
159159
}
160160

161-
func handleCouponSyncResult(result: Result<Bool, Error>) {
161+
func handleCouponSyncResult(result: Result<Bool, Error>, pageNumber: Int) {
162162
switch result {
163163
case .success:
164164
DDLogInfo("Synchronized coupons")
165+
ServiceLocator.analytics.track(.couponsLoaded,
166+
withProperties: ["is_loading_more": pageNumber != SyncingCoordinator.Defaults.pageFirstIndex])
165167

166168
case .failure(let error):
167169
DDLogError("⛔️ Error synchronizing coupons: \(error)")
170+
ServiceLocator.analytics.track(.couponsLoadedFailed, withError: error)
168171
}
169172

170173
self.transitionToResultsUpdatedState(hasData: couponViewModels.isNotEmpty)

WooCommerce/Classes/ViewRelated/Inbox/Inbox.swift

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,9 @@ struct Inbox: View {
1818
InfiniteScrollList(isLoading: viewModel.shouldShowBottomActivityIndicator,
1919
loadAction: viewModel.onLoadNextPageAction) {
2020
ForEach(viewModel.noteRowViewModels) { rowViewModel in
21-
if #available(iOS 15.0, *) {
22-
// In order to show full-width separator, the default list separator is hidden and a `Divider` is shown inside the row.
23-
InboxNoteRow(viewModel: rowViewModel)
24-
.listRowSeparator(.hidden)
25-
} else {
26-
InboxNoteRow(viewModel: rowViewModel)
27-
}
21+
InboxNoteRow(viewModel: rowViewModel)
2822
}
23+
.background(Constants.listForeground)
2924
}
3025
case .empty:
3126
// TODO: 5954 - update empty state
@@ -34,17 +29,19 @@ struct Inbox: View {
3429
image: .emptyProductsTabImage)
3530
.frame(maxHeight: .infinity)
3631
case .syncingFirstPage:
37-
List {
38-
ForEach(viewModel.placeholderRowViewModels) { rowViewModel in
39-
InboxNoteRow(viewModel: rowViewModel)
40-
.redacted(reason: .placeholder)
41-
.shimmering()
32+
ScrollView {
33+
LazyVStack(spacing: 0) {
34+
ForEach(viewModel.placeholderRowViewModels) { rowViewModel in
35+
InboxNoteRow(viewModel: rowViewModel)
36+
.redacted(reason: .placeholder)
37+
.shimmering()
38+
}
39+
.background(Constants.listForeground)
4240
}
4341
}
44-
.listStyle(PlainListStyle())
4542
}
4643
}
47-
.background(Color(.listBackground).ignoresSafeArea())
44+
.background(Constants.listBackground.ignoresSafeArea())
4845
.navigationTitle(Localization.title)
4946
.onAppear {
5047
viewModel.onLoadTrigger.send()
@@ -53,6 +50,12 @@ struct Inbox: View {
5350
}
5451

5552
private extension Inbox {
53+
54+
enum Constants {
55+
static let listForeground: Color = Color(.listForeground)
56+
static let listBackground: Color = Color(.listBackground)
57+
}
58+
5659
enum Localization {
5760
static let title = NSLocalizedString("Inbox", comment: "Title for the screen that shows inbox notes.")
5861
static let emptyStateTitle = NSLocalizedString("Congrats, you’ve read everything!",

WooCommerce/Classes/ViewRelated/Inbox/InboxNoteRow.swift

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,71 @@ import Yosemite
55
struct InboxNoteRow: View {
66
let viewModel: InboxNoteRowViewModel
77

8+
// Tracks the scale of the view due to accessibility changes.
9+
@ScaledMetric private var scale: CGFloat = 1
10+
811
var body: some View {
912
VStack(spacing: 0) {
10-
VStack(alignment: .leading,
11-
spacing: Constants.verticalSpacing) {
13+
VStack(alignment: .leading, spacing: Constants.spacingBetweenTopHStackAndContentVStack) {
1214
// HStack with type icon and relative date.
13-
// TODO: 5954 - type icon and relative date
15+
HStack {
16+
Circle()
17+
.frame(width: scale * Constants.typeIconDimension, height: scale * Constants.typeIconDimension, alignment: .center)
18+
.foregroundColor(Color(Constants.typeIconCircleColor))
19+
.overlay(
20+
viewModel.typeIcon
21+
.resizable()
22+
.scaledToFit()
23+
.padding(scale * Constants.typeIconPadding)
24+
)
25+
Text(viewModel.date)
26+
.font(.subheadline)
27+
.foregroundColor(Color(Constants.dateTextColor))
28+
Spacer()
29+
}
1430

15-
// Title.
16-
Text(viewModel.title)
17-
.bodyStyle()
18-
.fixedSize(horizontal: false, vertical: true)
31+
VStack(alignment: .leading, spacing: Constants.verticalSpacing) {
32+
// Title.
33+
Text(viewModel.title)
34+
.bodyStyle()
35+
.fixedSize(horizontal: false, vertical: true)
1936

20-
// Content.
21-
AttributedText(viewModel.attributedContent)
22-
.attributedTextLinkColor(Color(.accent))
37+
// Content.
38+
AttributedText(viewModel.attributedContent)
39+
.attributedTextLinkColor(Color(.accent))
2340

24-
// HStack with actions and dismiss action.
25-
HStack(spacing: Constants.spacingBetweenActions) {
26-
ForEach(viewModel.actions) { action in
27-
if let url = action.url {
28-
Button(action.title) {
29-
// TODO: 5955 - handle action
30-
print("Handling action with URL: \(url)")
41+
// HStack with actions and dismiss action.
42+
HStack(spacing: Constants.spacingBetweenActions) {
43+
ForEach(viewModel.actions) { action in
44+
if let url = action.url {
45+
Button(action.title) {
46+
// TODO: 5955 - handle action
47+
print("Handling action with URL: \(url)")
48+
}
49+
.foregroundColor(Color(.accent))
50+
.font(.body)
51+
.buttonStyle(PlainButtonStyle())
52+
} else {
53+
Text(action.title)
3154
}
32-
.foregroundColor(Color(.accent))
33-
.font(.body)
34-
.buttonStyle(PlainButtonStyle())
35-
} else {
36-
Text(action.title)
3755
}
38-
}
39-
Button(Localization.dismiss) {
40-
// TODO: 5955 - handle dismiss action
41-
print("Handling dismiss action")
42-
}
43-
.foregroundColor(Color(.withColorStudio(.gray, shade: .shade30)))
44-
.font(.body)
45-
.buttonStyle(PlainButtonStyle())
56+
Button(Localization.dismiss) {
57+
// TODO: 5955 - handle dismiss action
58+
print("Handling dismiss action")
59+
}
60+
.foregroundColor(Color(.withColorStudio(.gray, shade: .shade30)))
61+
.font(.body)
62+
.buttonStyle(PlainButtonStyle())
4663

47-
Spacer()
64+
Spacer()
65+
}
4866
}
4967
}
5068
.padding(Constants.defaultPadding)
5169

52-
if #available(iOS 15.0, *) {
53-
// In order to show full-width separator, the default list separator is hidden and a `Divider` is shown inside the row.
54-
Divider()
55-
.frame(height: Constants.dividerHeight)
56-
}
70+
Divider()
71+
.frame(height: Constants.dividerHeight)
5772
}
58-
.listRowInsets(.zero)
5973
}
6074
}
6175

@@ -66,18 +80,25 @@ private extension InboxNoteRow {
6680

6781
enum Constants {
6882
static let spacingBetweenActions: CGFloat = 16
83+
static let spacingBetweenTopHStackAndContentVStack: CGFloat = 8
6984
static let verticalSpacing: CGFloat = 14
7085
static let defaultPadding: CGFloat = 16
7186
static let dividerHeight: CGFloat = 1
87+
static let dateTextColor: UIColor = .withColorStudio(.gray, shade: .shade30)
88+
static let typeIconDimension: CGFloat = 29
89+
static let typeIconPadding: CGFloat = 5
90+
static let typeIconCircleColor: UIColor = .init(light: .withColorStudio(.gray, shade: .shade0), dark: .withColorStudio(.gray, shade: .shade70))
7291
}
7392
}
7493

7594
struct InboxNoteRow_Previews: PreviewProvider {
7695
static var previews: some View {
96+
// Monday, February 14, 2022 1:04:42 PM
97+
let today = Date(timeIntervalSince1970: 1644843882)
7798
let note = InboxNote(siteID: 2,
7899
id: 6,
79100
name: "",
80-
type: "",
101+
type: "marketing",
81102
status: "",
82103
actions: [.init(id: 2, name: "", label: "Let your customers know about Apple Pay", status: "", url: "https://wordpress.org"),
83104
.init(id: 6, name: "", label: "No URL", status: "", url: "")],
@@ -91,13 +112,33 @@ struct InboxNoteRow_Previews: PreviewProvider {
91112
isRemoved: false,
92113
isRead: false,
93114
dateCreated: .init())
94-
let viewModel = InboxNoteRowViewModel(note: note)
115+
let shortNote = InboxNote(siteID: 2,
116+
id: 6,
117+
name: "",
118+
type: "",
119+
status: "",
120+
actions: [.init(id: 2, name: "", label: "Learn Apple Pay", status: "", url: "https://wordpress.org"),
121+
.init(id: 6, name: "", label: "No URL", status: "", url: "")],
122+
title: "Boost sales this holiday season with Apple Pay!",
123+
content: "Increase your conversion rate.",
124+
isRemoved: false,
125+
isRead: false,
126+
dateCreated: today)
95127
Group {
96-
InboxNoteRow(viewModel: viewModel)
128+
List {
129+
InboxNoteRow(viewModel: .init(note: note.copy(type: "marketing", dateCreated: today), today: today))
130+
InboxNoteRow(viewModel: .init(note: shortNote.copy(type: "error").copy(dateCreated: today.addingTimeInterval(-6*60)), today: today))
131+
InboxNoteRow(viewModel: .init(note: shortNote.copy(type: "warning").copy(dateCreated: today.addingTimeInterval(-6*3600)), today: today))
132+
InboxNoteRow(viewModel: .init(note: shortNote.copy(type: "update").copy(dateCreated: today.addingTimeInterval(-6*86400)), today: today))
133+
InboxNoteRow(viewModel: .init(note: shortNote.copy(type: "info").copy(dateCreated: today.addingTimeInterval(-14*86400)), today: today))
134+
InboxNoteRow(viewModel: .init(note: shortNote.copy(type: "survey").copy(dateCreated: today.addingTimeInterval(-1.5*86400)), today: today))
135+
}
97136
.preferredColorScheme(.dark)
98-
InboxNoteRow(viewModel: viewModel)
137+
.environment(\.sizeCategory, .extraSmall)
138+
.previewLayout(.sizeThatFits)
139+
InboxNoteRow(viewModel: .init(note: note.copy(dateCreated: today.addingTimeInterval(-86400*2)), today: today))
99140
.preferredColorScheme(.light)
100-
InboxNoteRow(viewModel: viewModel)
141+
InboxNoteRow(viewModel: .init(note: note.copy(dateCreated: today.addingTimeInterval(-6*60)), today: today))
101142
.preferredColorScheme(.light)
102143
.environment(\.sizeCategory, .extraExtraExtraLarge)
103144
}

0 commit comments

Comments
 (0)