Skip to content

Commit 9e9d7d9

Browse files
Merge pull request #426 from woocommerce/issue/19-notification-details-pull-to-refresh
Notification Details: Pull to Refresh
2 parents ca81b2d + dc33c7b commit 9e9d7d9

File tree

5 files changed

+98
-10
lines changed

5 files changed

+98
-10
lines changed

WooCommerce/Classes/ViewRelated/Notifications/NotificationDetailsViewController.swift

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ class NotificationDetailsViewController: UIViewController {
1717
return EntityListener(storageManager: AppDelegate.shared.storageManager, readOnlyEntity: note)
1818
}()
1919

20+
/// Pull To Refresh Support.
21+
///
22+
private lazy var refreshControl: UIRefreshControl = {
23+
let refreshControl = UIRefreshControl()
24+
refreshControl.addTarget(self, action: #selector(pullToRefresh(sender:)), for: .valueChanged)
25+
return refreshControl
26+
}()
27+
2028
/// Note to be displayed!
2129
///
2230
private var note: Note! {
2331
didSet {
24-
buildDetailsRows()
32+
reloadInterface()
2533
}
2634
}
2735

@@ -57,7 +65,7 @@ class NotificationDetailsViewController: UIViewController {
5765
configureEntityListener()
5866

5967
registerTableViewCells()
60-
buildDetailsRows()
68+
reloadInterface()
6169
}
6270
}
6371

@@ -69,8 +77,6 @@ private extension NotificationDetailsViewController {
6977
/// Setup: Navigation
7078
///
7179
func configureNavigationItem() {
72-
title = note.title
73-
7480
// Don't show the Notifications title in the next-view's back button
7581
navigationItem.backBarButtonItem = UIBarButtonItem(title: String(), style: .plain, target: nil, action: nil)
7682
}
@@ -87,6 +93,7 @@ private extension NotificationDetailsViewController {
8793
// Hide "Empty Rows"
8894
tableView.tableFooterView = UIView()
8995
tableView.backgroundColor = StyleManager.tableViewBackgroundColor
96+
tableView.refreshControl = refreshControl
9097
}
9198

9299
/// Setup: EntityListener
@@ -114,13 +121,44 @@ private extension NotificationDetailsViewController {
114121
}
115122

116123

124+
// MARK: - Sync
125+
//
126+
private extension NotificationDetailsViewController {
127+
128+
/// Refresh Control's Callback.
129+
///
130+
@IBAction func pullToRefresh(sender: UIRefreshControl) {
131+
WooAnalytics.shared.track(.notificationsListPulledToRefresh)
132+
133+
synchronizeNotification(noteId: note.noteId) {
134+
sender.endRefreshing()
135+
}
136+
}
137+
138+
/// Synchronizes the Notifications associated to the active WordPress.com account.
139+
///
140+
func synchronizeNotification(noteId: Int64, onCompletion: @escaping () -> Void) {
141+
let action = NotificationAction.synchronizeNotification(noteId: noteId) { error in
142+
if let error = error {
143+
DDLogError("⛔️ Error synchronizing notification [\(noteId)]: \(error)")
144+
}
145+
146+
onCompletion()
147+
}
148+
149+
StoresManager.shared.dispatch(action)
150+
}
151+
}
152+
153+
117154
// MARK: - Private Methods
118155
//
119156
private extension NotificationDetailsViewController {
120157

121-
/// Reloads all of the Notification Detail Rows!
158+
/// Reloads all of the Details Interface
122159
///
123-
func buildDetailsRows() {
160+
func reloadInterface() {
161+
title = note.title
124162
rows = NoteDetailsRow.details(from: note)
125163
tableView.reloadData()
126164
}

Yosemite/Yosemite/Actions/NotificationAction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Networking
77
//
88
public enum NotificationAction: Action {
99
case synchronizeNotifications(onCompletion: (Error?) -> Void)
10+
case synchronizeNotification(noteId: Int64, onCompletion: (Error?) -> Void)
1011
case updateLastSeen(timestamp: String, onCompletion: (Error?) -> Void)
1112
case updateReadStatus(noteID: Int64, read: Bool, onCompletion: (Error?) -> Void)
1213
}

Yosemite/Yosemite/Model/ReadOnly/Note+ReadOnlyType.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ extension Yosemite.Note: ReadOnlyType {
1313
return false
1414
}
1515

16-
return storageNote.noteID == noteId &&
17-
storageNote.noteHash == hash
16+
return storageNote.noteID == noteId
1817
}
1918
}

Yosemite/Yosemite/Stores/NotificationStore.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class NotificationStore: Store {
3232
switch action {
3333
case .synchronizeNotifications(let onCompletion):
3434
synchronizeNotifications(onCompletion: onCompletion)
35+
case .synchronizeNotification(let noteId, let onCompletion):
36+
synchronizeNotification(with: noteId, onCompletion: onCompletion)
3537
case .updateLastSeen(let timestamp, let onCompletion):
3638
updateLastSeen(timestamp: timestamp, onCompletion: onCompletion)
3739
case .updateReadStatus(let noteID, let read, let onCompletion):
@@ -81,7 +83,28 @@ private extension NotificationStore {
8183
}
8284
}
8385

84-
86+
87+
/// Synchronizes the Notification matching the specified ID, and updates the local entity.
88+
///
89+
/// - Parameters:
90+
/// - noteId: Notification ID of the note to be downloaded.
91+
/// - onCompletion: Closure to be executed on completion.
92+
///
93+
func synchronizeNotification(with noteId: Int64, onCompletion: @escaping (Error?) -> Void) {
94+
let remote = NotificationsRemote(network: network)
95+
96+
remote.loadNotes(noteIds: [noteId]) { notes, error in
97+
guard let notes = notes else {
98+
onCompletion(error)
99+
return
100+
}
101+
102+
self.updateLocalNotes(with: notes) {
103+
onCompletion(nil)
104+
}
105+
}
106+
}
107+
85108

86109
/// Updates the last seen notification
87110
///
@@ -92,6 +115,7 @@ private extension NotificationStore {
92115
}
93116
}
94117

118+
95119
/// Updates the read status for the given notification ID
96120
///
97121
func updateReadStatus(noteID: Int64, read: Bool, onCompletion: @escaping (Error?) -> Void) {

Yosemite/YosemiteTests/Stores/NotificationStoreTests.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ class NotificationStoreTests: XCTestCase {
111111
/// Initial Sync
112112
///
113113
let initialSyncAction = NotificationAction.synchronizeNotifications() { (error) in
114-
115114
XCTAssertEqual(self.viewStorage.countObjects(ofType: Storage.Note.self), 40)
116115
notificationStore.onAction(nestedSyncAction)
117116
}
@@ -120,6 +119,33 @@ class NotificationStoreTests: XCTestCase {
120119
wait(for: [expectation], timeout: Constants.expectationTimeout)
121120
}
122121

122+
/// Verifies that `NotificationAction.synchronizeNotification` will effectively request a single notification,
123+
/// which will be stored in CoreData.
124+
///
125+
func testSynchronizeSingleNotificationEffectivelyUpdatesRequestedNote() {
126+
let expectation = self.expectation(description: "Sync notification")
127+
let notificationStore = NotificationStore(dispatcher: dispatcher, storageManager: storageManager, network: network)
128+
let notificationId = Int64(100001)
129+
130+
network.simulateResponse(requestUrlSuffix: "notifications", filename: "notifications-load-all")
131+
XCTAssertEqual(viewStorage.countObjects(ofType: Storage.Note.self), 0)
132+
133+
let syncAction = NotificationAction.synchronizeNotification(noteId: notificationId) { error in
134+
let note = self.viewStorage.loadNotification(noteID: notificationId)
135+
XCTAssertNil(error)
136+
XCTAssertNotNil(note)
137+
138+
let request = self.network.requestsForResponseData[0] as! DotcomRequest
139+
XCTAssertEqual(request.parameters?["ids"], String(notificationId))
140+
141+
expectation.fulfill()
142+
}
143+
144+
notificationStore.onAction(syncAction)
145+
wait(for: [expectation], timeout: Constants.expectationTimeout)
146+
}
147+
148+
123149
// MARK: - NotificationAction.updateLastSeen
124150

125151
/// Verifies that NotificationAction.updateLastSeen handles a success response from the backend properly

0 commit comments

Comments
 (0)