Skip to content

Commit 0d4ac9a

Browse files
authored
Merge pull request #5827 from woocommerce/issue/5767-pull-to-refresh-coupons
Coupons: Add pull-to-refresh to coupon list
2 parents 4466258 + ef9828f commit 0d4ac9a

File tree

3 files changed

+75
-5
lines changed

3 files changed

+75
-5
lines changed

WooCommerce/Classes/ViewRelated/Coupons/CouponListViewController.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ final class CouponListViewController: UIViewController {
1010
///
1111
private var emptyStateViewController: UIViewController?
1212

13+
/// Pull To Refresh Support.
14+
///
15+
private lazy var refreshControl: UIRefreshControl = {
16+
let refreshControl = UIRefreshControl()
17+
refreshControl.addTarget(self, action: #selector(refreshCouponList), for: .valueChanged)
18+
return refreshControl
19+
}()
20+
1321
private var subscriptions: Set<AnyCancellable> = []
1422

1523
init(siteID: Int64) {
@@ -33,15 +41,16 @@ final class CouponListViewController: UIViewController {
3341
.removeDuplicates()
3442
.sink { [weak self] state in
3543
guard let self = self else { return }
36-
self.removeNoResultsOverlay()
37-
self.removePlaceholderCoupons()
44+
self.resetViews()
3845
switch state {
3946
case .empty:
4047
self.displayNoResultsOverlay()
4148
case .loading:
4249
self.displayPlaceholderCoupons()
4350
case .coupons:
4451
self.tableView.reloadData()
52+
case .refreshing:
53+
self.refreshControl.beginRefreshing()
4554
case .initialized:
4655
break
4756
}
@@ -53,6 +62,25 @@ final class CouponListViewController: UIViewController {
5362
}
5463
}
5564

65+
// MARK: - Actions
66+
private extension CouponListViewController {
67+
/// Triggers a refresh for the coupon list
68+
///
69+
@objc func refreshCouponList() {
70+
viewModel.refreshCoupons()
71+
}
72+
73+
/// Removes overlays and loading indicators if present.
74+
///
75+
func resetViews() {
76+
removeNoResultsOverlay()
77+
removePlaceholderCoupons()
78+
if refreshControl.isRefreshing {
79+
refreshControl.endRefreshing()
80+
}
81+
}
82+
}
83+
5684

5785
// MARK: - View Configuration
5886
//
@@ -66,6 +94,7 @@ private extension CouponListViewController {
6694
tableView.dataSource = self
6795
tableView.estimatedRowHeight = Constants.estimatedRowHeight
6896
tableView.rowHeight = UITableView.automaticDimension
97+
tableView.addSubview(refreshControl)
6998
}
7099

71100
func registerTableViewCells() {

WooCommerce/Classes/ViewRelated/Coupons/CouponManagementListViewModel.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum CouponListState {
1515
case loading // View should show ghost cells
1616
case empty // View should display the empty state
1717
case coupons // View should display the contents of `couponViewModels`
18+
case refreshing // View should display the refresh control
1819
}
1920

2021
final class CouponListViewModel {
@@ -116,6 +117,12 @@ final class CouponListViewModel {
116117
func coupon(at indexPath: IndexPath) -> Coupon? {
117118
return resultsController.safeObject(at: indexPath)
118119
}
120+
121+
/// Triggers a refresh of loaded coupons
122+
///
123+
func refreshCoupons() {
124+
syncingCoordinator.resynchronize(reason: nil, onCompletion: nil)
125+
}
119126
}
120127

121128

@@ -133,7 +140,7 @@ extension CouponListViewModel: SyncingCoordinatorDelegate {
133140
pageSize: Int,
134141
reason: String?,
135142
onCompletion: ((Bool) -> Void)?) {
136-
transitionToSyncingState(pageNumber: pageNumber)
143+
transitionToSyncingState(pageNumber: pageNumber, hasData: couponViewModels.isNotEmpty)
137144
let action = CouponAction
138145
.synchronizeCoupons(siteID: siteID,
139146
pageNumber: pageNumber,
@@ -162,9 +169,9 @@ extension CouponListViewModel: SyncingCoordinatorDelegate {
162169
// MARK: - Pagination
163170
//
164171
private extension CouponListViewModel {
165-
func transitionToSyncingState(pageNumber: Int) {
172+
func transitionToSyncingState(pageNumber: Int, hasData: Bool) {
166173
if pageNumber == 1 {
167-
state = .loading
174+
state = hasData ? .refreshing : .loading
168175
}
169176
}
170177

WooCommerce/WooCommerceTests/ViewRelated/Coupons/CouponListViewModelTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,38 @@ final class CouponListViewModelTests: XCTestCase {
111111
// Then
112112
XCTAssertEqual(sut.state, .empty)
113113
}
114+
115+
func test_refreshCoupon_updates_state_to_refreshing() {
116+
// Given
117+
setUpWithCouponFetched() // we need to have existing data to enter refreshing state
118+
119+
// When
120+
sut.refreshCoupons()
121+
122+
// Then
123+
XCTAssertEqual(sut.state, .refreshing)
124+
}
125+
126+
func test_refreshCoupons_calls_resynchronize_on_syncCoordinator() {
127+
// Given
128+
sut = CouponListViewModel(siteID: 123, syncingCoordinator: mockSyncingCoordinator)
129+
130+
// When
131+
sut.refreshCoupons()
132+
133+
// Then
134+
XCTAssert(mockSyncingCoordinator.spyDidCallResynchronize)
135+
}
136+
137+
func test_handleCouponSyncResult_removes_refreshing_when_refresh_completes() {
138+
// Given
139+
setUpWithCouponFetched()
140+
sut.refreshCoupons()
141+
142+
// When
143+
sut.handleCouponSyncResult(result: .success(false))
144+
145+
// Then
146+
XCTAssertEqual(sut.state, .coupons)
147+
}
114148
}

0 commit comments

Comments
 (0)