Skip to content

Commit b609327

Browse files
committed
Merge remote-tracking branch 'origin/develop' into issue/nuking-orderstatus-viewmodel
2 parents 5c67a10 + ca81b2d commit b609327

File tree

13 files changed

+190
-22
lines changed

13 files changed

+190
-22
lines changed

Networking/Networking/Model/Note.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,18 @@ public struct Note {
3838
///
3939
public let kind: Kind
4040

41+
/// Notification.Subtype expressed as a Swift Native enum.
42+
///
43+
public let subkind: Subkind?
44+
4145
/// Notification Type.
4246
///
4347
public let type: String?
4448

49+
/// Notification Subtype.
50+
///
51+
public let subtype: String?
52+
4553
/// Associated Resource's URL.
4654
///
4755
public let url: String?
@@ -96,6 +104,7 @@ public struct Note {
96104
noticon: String?,
97105
timestamp: String,
98106
type: String,
107+
subtype: String?,
99108
url: String?,
100109
title: String?,
101110
subject: Data,
@@ -111,7 +120,9 @@ public struct Note {
111120
self.timestamp = timestamp
112121
self.timestampAsDate = DateFormatter.Defaults.iso8601.date(from: timestamp) ?? Date()
113122
self.type = type
123+
self.subtype = subtype
114124
self.kind = Kind(rawValue: type) ?? .unknown
125+
self.subkind = subtype.flatMap { Subkind(rawValue: $0) }
115126
self.url = url
116127
self.title = title
117128

@@ -148,6 +159,7 @@ extension Note: Decodable {
148159
let noticon = container.failsafeDecodeIfPresent(String.self, forKey: .noticon)
149160
let timestamp = container.failsafeDecodeIfPresent(stringForKey: .timestamp) ?? String()
150161
let type = container.failsafeDecodeIfPresent(String.self, forKey: .type) ?? String()
162+
let subtype = container.failsafeDecodeIfPresent(String.self, forKey: .subtype)
151163
let url = container.failsafeDecodeIfPresent(String.self, forKey: .url)
152164
let title = container.failsafeDecodeIfPresent(String.self, forKey: .title)
153165

@@ -170,6 +182,7 @@ extension Note: Decodable {
170182
noticon: noticon,
171183
timestamp: timestamp,
172184
type: type,
185+
subtype: subtype,
173186
url: url,
174187
title: title,
175188
subject: subjectAsData,
@@ -194,6 +207,7 @@ extension Note {
194207
case noticon
195208
case timestamp
196209
case type
210+
case subtype
197211
case url
198212
case title
199213
case subject
@@ -216,4 +230,10 @@ extension Note {
216230
case user
217231
case unknown
218232
}
233+
234+
/// Known Notification Subkind(s)
235+
///
236+
public enum Subkind: String {
237+
case storeReview = "store_review"
238+
}
219239
}

Networking/NetworkingTests/Mapper/NoteListMapperTests.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,15 @@ class NoteListMapperTests: XCTestCase {
250250
let commentNote = sampleNotes.first(where: { $0.kind == .comment })
251251
XCTAssertEqual(commentNote?.body[1].kind, .comment)
252252
}
253+
254+
255+
/// Verifies that the Notification's subtype is properly parsed.
256+
///
257+
func testStoreReviewSubtypeIsProperlyParsed() {
258+
let storeReview = sampleNotes.first(where: { $0.noteId == 100009 })
259+
XCTAssertEqual(storeReview?.subtype, "store_review")
260+
XCTAssertEqual(storeReview?.subkind, .storeReview)
261+
}
253262
}
254263

255264

Networking/NetworkingTests/Responses/notifications-load-all.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,7 @@
12281228
"id": 100009,
12291229
"note_hash": 987654,
12301230
"type": "comment",
1231+
"subtype": "store_review",
12311232
"read": 1,
12321233
"noticon": "\uf300",
12331234
"timestamp": "2018-10-19T12:56:47+00:00",

Storage/Storage/Model/MIGRATIONS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
This file documents changes in the WCiOS Storage data model. Please explain any changes to the data model as well as any custom migrations.
44

55
## Model 6
6+
- @jleandroperez 2018-11-12
7+
- New `Note.subtype` property (optional type)
8+
69
- @thuycopeland 2018-11-8
710
- Added new attribute: `isJetpackInstalled`, to site entity
811
- Added new attribute: `plan`, to site entity

Storage/Storage/Model/Note+CoreDataProperties.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extension Note {
1515
@NSManaged public var noticon: String?
1616
@NSManaged public var timestamp: String?
1717
@NSManaged public var type: String?
18+
@NSManaged public var subtype: String?
1819
@NSManaged public var url: String?
1920
@NSManaged public var title: String?
2021
@NSManaged public var subject: Data?

Storage/Storage/Model/WooCommerce.xcdatamodeld/Model 6.xcdatamodel/contents

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18A391" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
2+
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="18B75" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
33
<entity name="Account" representedClassName="Account" syncable="YES">
44
<attribute name="displayName" optional="YES" attributeType="String" syncable="YES"/>
55
<attribute name="email" optional="YES" attributeType="String" syncable="YES"/>
@@ -17,6 +17,7 @@
1717
<attribute name="noticon" optional="YES" attributeType="String" syncable="YES"/>
1818
<attribute name="read" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
1919
<attribute name="subject" optional="YES" attributeType="Binary" syncable="YES"/>
20+
<attribute name="subtype" optional="YES" attributeType="String" syncable="YES"/>
2021
<attribute name="timestamp" attributeType="String" syncable="YES"/>
2122
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
2223
<attribute name="type" optional="YES" attributeType="String" syncable="YES"/>
@@ -175,7 +176,7 @@
175176
</entity>
176177
<elements>
177178
<element name="Account" positionX="-200.9765625" positionY="63.5625" width="128" height="120"/>
178-
<element name="Note" positionX="-162" positionY="180" width="128" height="240"/>
179+
<element name="Note" positionX="-162" positionY="180" width="128" height="255"/>
179180
<element name="Order" positionX="-20" positionY="27" width="128" height="690"/>
180181
<element name="OrderCoupon" positionX="-206.01953125" positionY="379.74609375" width="128" height="120"/>
181182
<element name="OrderItem" positionX="-364.890625" positionY="379.453125" width="128" height="225"/>

WooCommerce/Classes/ViewRelated/Notifications/Cells/NoteTableViewCell.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,6 @@ class NoteTableViewCell: UITableViewCell {
7272
noticonLabel.font = UIFont.noticon(forStyle: .title1)
7373
}
7474

75-
override func prepareForReuse() {
76-
super.prepareForReuse()
77-
78-
noticon = nil
79-
attributedSubject = nil
80-
attributedSnippet = nil
81-
}
82-
8375
override func setSelected(_ selected: Bool, animated: Bool) {
8476
// Note: this is required, since the cell unhighlight mechanism will reset the new background color
8577
super.setSelected(selected, animated: animated)

WooCommerce/Classes/ViewRelated/Notifications/Cells/NoteTableViewCell.xib

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@
1818
<rect key="frame" x="0.0" y="0.0" width="375" height="87.5"/>
1919
<autoresizingMask key="autoresizingMask"/>
2020
<subviews>
21-
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="🐮" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MDO-G5-bNl">
22-
<rect key="frame" x="14" y="11" width="25" height="25"/>
21+
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MDO-G5-bNl">
22+
<rect key="frame" x="14" y="11" width="25" height="20.5"/>
2323
<constraints>
2424
<constraint firstAttribute="width" constant="25" id="Eqv-DJ-O5N"/>
25-
<constraint firstAttribute="height" priority="750" constant="25" id="w2h-Rr-ayg"/>
2625
</constraints>
2726
<fontDescription key="fontDescription" type="system" pointSize="17"/>
2827
<nil key="textColor"/>

WooCommerce/Classes/ViewRelated/Notifications/NotificationsViewController.swift

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import UIKit
22
import Gridicons
33
import Yosemite
4+
import WordPressUI
5+
import SafariServices
46

57

68
// MARK: - NotificationsViewController
@@ -20,11 +22,10 @@ class NotificationsViewController: UIViewController {
2022
return ResultsController<StorageNote>(storageManager: storageManager, sectionNameKeyPath: "normalizedAgeAsString", matching: filter, sortedBy: [descriptor])
2123
}()
2224

23-
/// Store Notifications CoreData Filter. IMPORTANT!! This is CLEARLY a quick hack (we can't filter based on the title!)
24-
/// TODO: Remove ASAP as soon as the (pending) backend PR is merged
25+
/// Store Notifications CoreData Filter.
2526
///
2627
private var filter: NSPredicate {
27-
return NSPredicate(format: "type == %@ OR title == 'Product Review'", Note.Kind.storeOrder.rawValue)
28+
return NSPredicate(format: "type == %@ OR subtype == %@", Note.Kind.storeOrder.rawValue, Note.Subkind.storeReview.rawValue)
2829
}
2930

3031
/// Pull To Refresh Support.
@@ -47,6 +48,24 @@ class NotificationsViewController: UIViewController {
4748
///
4849
private let formatter = StringFormatter()
4950

51+
/// UI Active State
52+
///
53+
private var state: State = .results {
54+
didSet {
55+
guard oldValue != state else {
56+
return
57+
}
58+
59+
didLeave(state: oldValue)
60+
didEnter(state: state)
61+
}
62+
}
63+
64+
/// Indicates if there are no results onscreen.
65+
///
66+
private var isEmpty: Bool {
67+
return resultsController.isEmpty
68+
}
5069

5170

5271
// MARK: - View Lifecycle
@@ -145,9 +164,11 @@ private extension NotificationsViewController {
145164
DDLogError("⛔️ Error synchronizing notifications: \(error)")
146165
}
147166

167+
self.transitionToResultsUpdatedState()
148168
onCompletion?()
149169
}
150170

171+
transitionToSyncingState()
151172
StoresManager.shared.dispatch(action)
152173
}
153174
}
@@ -248,3 +269,119 @@ private extension NotificationsViewController {
248269
return snippet
249270
}
250271
}
272+
273+
274+
// MARK: - Placeholders
275+
//
276+
private extension NotificationsViewController {
277+
278+
/// Renders Placeholder Notes: For safety reasons, we'll also halt ResultsController <> UITableView glue.
279+
///
280+
func displayPlaceholderNotes() {
281+
let options = GhostOptions(reuseIdentifier: NoteTableViewCell.reuseIdentifier, rowsPerSection: Settings.placeholderRowsPerSection)
282+
tableView.displayGhostContent(options: options)
283+
284+
resultsController.stopForwardingEvents()
285+
}
286+
287+
/// Removes Placeholder Notes (and restores the ResultsController <> UITableView link).
288+
///
289+
func removePlaceholderNotes() {
290+
tableView.removeGhostContent()
291+
resultsController.startForwardingEvents(to: self.tableView)
292+
}
293+
294+
/// Displays the Empty State Overlay.
295+
///
296+
func displayEmptyNotesOverlay() {
297+
let overlayView: OverlayMessageView = OverlayMessageView.instantiateFromNib()
298+
overlayView.messageImage = .waitingForCustomersImage
299+
overlayView.messageText = NSLocalizedString("No Notifications Yet!", comment: "Empty Notifications List Message")
300+
overlayView.actionText = NSLocalizedString("Share your Store", comment: "Action: Opens the Store in a browser")
301+
overlayView.onAction = { [weak self] in
302+
self?.displayDefaultSite()
303+
}
304+
305+
overlayView.attach(to: view)
306+
}
307+
308+
/// Removes all of the the OverlayMessageView instances in the view hierarchy.
309+
///
310+
func removeAllOverlays() {
311+
for subview in view.subviews where subview is OverlayMessageView {
312+
subview.removeFromSuperview()
313+
}
314+
}
315+
316+
/// Displays the Default Site in a WebView.
317+
///
318+
func displayDefaultSite() {
319+
guard let urlAsString = StoresManager.shared.sessionManager.defaultSite?.url, let siteURL = URL(string: urlAsString) else {
320+
return
321+
}
322+
323+
let safariViewController = SFSafariViewController(url: siteURL)
324+
safariViewController.modalPresentationStyle = .pageSheet
325+
present(safariViewController, animated: true, completion: nil)
326+
}
327+
}
328+
329+
330+
// MARK: - Finite State Machine Management
331+
//
332+
private extension NotificationsViewController {
333+
334+
/// Runs whenever the FSM enters a State.
335+
///
336+
func didEnter(state: State) {
337+
switch state {
338+
case .empty:
339+
displayEmptyNotesOverlay()
340+
case .results:
341+
break
342+
case .syncing:
343+
displayPlaceholderNotes()
344+
}
345+
}
346+
347+
/// Runs whenever the FSM leaves a State.
348+
///
349+
func didLeave(state: State) {
350+
switch state {
351+
case .empty:
352+
removeAllOverlays()
353+
case .results:
354+
break
355+
case .syncing:
356+
removePlaceholderNotes()
357+
}
358+
}
359+
360+
/// Should be called before Sync'ing Starts: Transitions to .results / .syncing
361+
///
362+
func transitionToSyncingState() {
363+
state = isEmpty ? .syncing : .results
364+
}
365+
366+
/// Should be called after Sync'ing wraps up: Transitions to .empty / .results
367+
///
368+
func transitionToResultsUpdatedState() {
369+
state = isEmpty ? .empty : .results
370+
}
371+
}
372+
373+
374+
// MARK: - Nested Types
375+
//
376+
private extension NotificationsViewController {
377+
378+
enum Settings {
379+
static let placeholderRowsPerSection = [3]
380+
}
381+
382+
enum State {
383+
case empty
384+
case results
385+
case syncing
386+
}
387+
}

WooCommerce/Classes/ViewRelated/Orders/OrdersViewController.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ extension OrdersViewController {
479479
}
480480

481481

482-
// MARK: - FSM Management
482+
// MARK: - Finite State Machine Management
483483
//
484484
private extension OrdersViewController {
485485

@@ -536,7 +536,6 @@ private extension OrdersViewController {
536536

537537
state = .emptyUnfiltered
538538
}
539-
540539
}
541540

542541

0 commit comments

Comments
 (0)