Skip to content

Commit fedbd94

Browse files
Merge pull request #17839 from wordpress-mobile/task/17697-msd-show-error-retry
My Site Dashboard: add error messages when posts fails to load
2 parents 0a87420 + 44f51be commit fedbd94

File tree

5 files changed

+141
-4
lines changed

5 files changed

+141
-4
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import UIKit
2+
3+
protocol DashboardCardInnerErrorViewDelegate: AnyObject {
4+
func retry()
5+
}
6+
7+
class DashboardCardInnerErrorView: UIStackView {
8+
weak var delegate: DashboardCardInnerErrorViewDelegate?
9+
10+
private lazy var errorTitle: UILabel = {
11+
let errorTitle = UILabel()
12+
errorTitle.textAlignment = .center
13+
errorTitle.textColor = .textSubtle
14+
WPStyleGuide.configureLabel(errorTitle, textStyle: .callout, fontWeight: .semibold)
15+
return errorTitle
16+
}()
17+
18+
private lazy var retryLabel: UILabel = {
19+
let retryLabel = UILabel()
20+
retryLabel.textAlignment = .center
21+
retryLabel.text = Strings.tapToRetry
22+
retryLabel.textColor = .textSubtle
23+
WPStyleGuide.configureLabel(retryLabel, textStyle: .callout, fontWeight: .regular)
24+
return retryLabel
25+
}()
26+
27+
convenience init(message: String, canRetry: Bool) {
28+
self.init(arrangedSubviews: [])
29+
30+
errorTitle.text = message
31+
addArrangedSubview(errorTitle)
32+
33+
axis = .vertical
34+
spacing = Constants.spacing
35+
36+
if canRetry {
37+
addArrangedSubview(retryLabel)
38+
39+
isUserInteractionEnabled = true
40+
let tap = UITapGestureRecognizer(target: self, action: #selector(didTap))
41+
addGestureRecognizer(tap)
42+
}
43+
}
44+
45+
@objc func didTap() {
46+
delegate?.retry()
47+
}
48+
49+
private enum Constants {
50+
static let spacing: CGFloat = 8
51+
}
52+
53+
private enum Strings {
54+
static let tapToRetry = NSLocalizedString("Tap to retry", comment: "Label for a button to retry loading posts")
55+
}
56+
}

WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewController.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import UIKit
1111

1212
private var viewModel: PostsCardViewModel!
1313
private var ghostableTableView: UITableView?
14+
private var errorView: DashboardCardInnerErrorView?
1415

1516
private let status: BasePost.Status = .draft
1617

@@ -31,6 +32,11 @@ import UIKit
3132
viewModel.viewDidLoad()
3233
}
3334

35+
override func viewWillAppear(_ animated: Bool) {
36+
super.viewWillAppear(animated)
37+
hideSeparatorForGhostCells()
38+
}
39+
3440
override func viewDidAppear(_ animated: Bool) {
3541
super.viewDidAppear(animated)
3642
tableView.dataSource = viewModel
@@ -49,14 +55,15 @@ private extension PostsCardViewController {
4955
func configureTableView() {
5056
view.addSubview(tableView)
5157
tableView.translatesAutoresizingMaskIntoConstraints = false
58+
tableView.isScrollEnabled = false
5259
view.pinSubviewToAllEdges(tableView)
5360
let postCompactCellNib = PostCompactCell.defaultNib
5461
tableView.register(postCompactCellNib, forCellReuseIdentifier: PostCompactCell.defaultReuseID)
5562
tableView.separatorStyle = .none
5663
}
5764

5865
func configureGhostableTableView() {
59-
let ghostableTableView = IntrinsicTableView()
66+
let ghostableTableView = PostCardTableView()
6067

6168
view.addSubview(ghostableTableView)
6269

@@ -83,6 +90,11 @@ private extension PostsCardViewController {
8390
ghostableTableView?.removeFromSuperview()
8491
}
8592

93+
func hideSeparatorForGhostCells() {
94+
ghostableTableView?.visibleCells
95+
.forEach { ($0 as? PostCompactCell)?.hideSeparator() }
96+
}
97+
8698
enum Constants {
8799
static let numberOfPosts = 3
88100
}
@@ -105,8 +117,21 @@ extension PostsCardViewController: PostsCardView {
105117
}
106118

107119
func hideLoading() {
120+
errorView?.removeFromSuperview()
108121
removeGhostableTableView()
109122
}
123+
124+
func showError(message: String, retry: Bool) {
125+
let errorView = DashboardCardInnerErrorView(message: message, canRetry: retry)
126+
errorView.delegate = self
127+
errorView.translatesAutoresizingMaskIntoConstraints = false
128+
tableView.addSubview(withFadeAnimation: errorView)
129+
tableView.pinSubviewToSafeArea(errorView)
130+
self.errorView = errorView
131+
132+
// Force the table view to recalculate its height
133+
_ = tableView.intrinsicContentSize
134+
}
110135
}
111136

112137
// MARK: - EditorAnalyticsProperties
@@ -126,6 +151,14 @@ extension PostsCardViewController: EditorAnalyticsProperties {
126151
}
127152
}
128153

154+
// MARK: - DashboardCardInnerErrorViewDelegate
155+
156+
extension PostsCardViewController: DashboardCardInnerErrorViewDelegate {
157+
func retry() {
158+
viewModel.retry()
159+
}
160+
}
161+
129162
// MARK: - PostCardTableView
130163

131164
extension NSNotification.Name {

WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Posts/PostsCardViewModel.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ protocol PostsCardView: AnyObject {
77

88
func showLoading()
99
func hideLoading()
10+
func showError(message: String, retry: Bool)
1011
}
1112

1213
/// Responsible for populating a table view with posts
@@ -52,6 +53,11 @@ class PostsCardViewModel: NSObject {
5253
}
5354
}
5455

56+
func retry() {
57+
viewController?.showLoading()
58+
sync()
59+
}
60+
5561
/// Set up the view model to be ready for use
5662
func viewDidLoad() {
5763
viewController?.showLoading()
@@ -73,6 +79,10 @@ class PostsCardViewModel: NSObject {
7379
// MARK: - Private methods
7480

7581
private extension PostsCardViewModel {
82+
private var numberOfPosts: Int {
83+
fetchedResultsController.fetchedObjects?.count ?? 0
84+
}
85+
7686
func createFetchedResultsController() {
7787
fetchedResultsController?.delegate = nil
7888
fetchedResultsController = nil
@@ -121,17 +131,36 @@ private extension PostsCardViewModel {
121131
ofType: .post,
122132
with: options,
123133
for: blog,
124-
success: { _ in
134+
success: { [weak self] _ in
135+
if self?.numberOfPosts == 0 {
136+
self?.showEmptyPostsError()
137+
}
138+
}, failure: { [weak self] _ in
139+
if self?.numberOfPosts == 0 {
140+
self?.showLoadingFailureError()
141+
}
142+
})
143+
}
125144

126-
}, failure: { (error: Error?) -> () in
145+
func showEmptyPostsError() {
146+
viewController?.hideLoading()
147+
viewController?.showError(message: Strings.noPostsMessage, retry: false)
148+
}
127149

128-
})
150+
func showLoadingFailureError() {
151+
viewController?.hideLoading()
152+
viewController?.showError(message: Strings.loadingFailure, retry: true)
129153
}
130154

131155
enum Constants {
132156
static let numberOfPosts = 3
133157
static let numberOfPostsToSync: NSNumber = 4
134158
}
159+
160+
enum Strings {
161+
static let noPostsMessage = NSLocalizedString("You don't have any posts", comment: "Displayed when the user views the dashboard posts card but they have no posts")
162+
static let loadingFailure = NSLocalizedString("Unable to load posts right now.", comment: "Message for when posts fail to load on the dashboard")
163+
}
135164
}
136165

137166
// MARK: - UITableViewDataSource

WordPress/Classes/ViewRelated/Post/PostCompactCell.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,4 +232,8 @@ extension PostCompactCell {
232232
trailingContentConstraint.constant = Constants.margin
233233
headerStackView.spacing = Constants.margin
234234
}
235+
236+
func hideSeparator() {
237+
separator.isHidden = true
238+
}
235239
}

WordPress/WordPress.xcodeproj/project.pbxproj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,8 @@
14371437
8B821F3C240020E2006B697E /* PostServiceUploadingListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B821F3B240020E2006B697E /* PostServiceUploadingListTests.swift */; };
14381438
8B85AEDA259230FC00ADBEC9 /* ABTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B85AED9259230FC00ADBEC9 /* ABTest.swift */; };
14391439
8B8C814D2318073300A0E620 /* BasePostTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8C814C2318073300A0E620 /* BasePostTests.swift */; };
1440+
8B8E50B627A4692000C89979 /* DashboardCardInnerErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8E50B527A4692000C89979 /* DashboardCardInnerErrorView.swift */; };
1441+
8B8E50B727A4692000C89979 /* DashboardCardInnerErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8E50B527A4692000C89979 /* DashboardCardInnerErrorView.swift */; };
14401442
8B8FE8582343955500F9AD2E /* PostAutoUploadMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8FE8562343952B00F9AD2E /* PostAutoUploadMessages.swift */; };
14411443
8B93412F257029F60097D0AC /* FilterChipButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B93412E257029F50097D0AC /* FilterChipButton.swift */; };
14421444
8B93856E22DC08060010BF02 /* PageListSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */; };
@@ -6077,6 +6079,7 @@
60776079
8B821F3B240020E2006B697E /* PostServiceUploadingListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostServiceUploadingListTests.swift; sourceTree = "<group>"; };
60786080
8B85AED9259230FC00ADBEC9 /* ABTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ABTest.swift; sourceTree = "<group>"; };
60796081
8B8C814C2318073300A0E620 /* BasePostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasePostTests.swift; sourceTree = "<group>"; };
6082+
8B8E50B527A4692000C89979 /* DashboardCardInnerErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardCardInnerErrorView.swift; sourceTree = "<group>"; };
60806083
8B8FE8562343952B00F9AD2E /* PostAutoUploadMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostAutoUploadMessages.swift; sourceTree = "<group>"; };
60816084
8B93412E257029F50097D0AC /* FilterChipButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterChipButton.swift; sourceTree = "<group>"; };
60826085
8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListSectionHeaderView.swift; sourceTree = "<group>"; };
@@ -11317,6 +11320,7 @@
1131711320
isa = PBXGroup;
1131811321
children = (
1131911322
8B4DDF24278F3AF60022494D /* Posts */,
11323+
8B8E50B427A468E800C89979 /* Helpers */,
1132011324
);
1132111325
path = Cards;
1132211326
sourceTree = "<group>";
@@ -11371,6 +11375,15 @@
1137111375
path = "AB Testing";
1137211376
sourceTree = "<group>";
1137311377
};
11378+
8B8E50B427A468E800C89979 /* Helpers */ = {
11379+
isa = PBXGroup;
11380+
children = (
11381+
8B8E50B527A4692000C89979 /* DashboardCardInnerErrorView.swift */,
11382+
);
11383+
name = Helpers;
11384+
path = Posts/Helpers;
11385+
sourceTree = "<group>";
11386+
};
1137411387
8B93412D257029E30097D0AC /* Filter */ = {
1137511388
isa = PBXGroup;
1137611389
children = (
@@ -18167,6 +18180,7 @@
1816718180
C76F48EE25BA20EF00BFEC87 /* JetpackScanHistoryCoordinator.swift in Sources */,
1816818181
17FCA6811FD84B4600DBA9C8 /* NoticeStore.swift in Sources */,
1816918182
9A162F2921C271D300FDC035 /* RevisionBrowserState.swift in Sources */,
18183+
8B8E50B627A4692000C89979 /* DashboardCardInnerErrorView.swift in Sources */,
1817018184
FAADE43A26159B2800BF29FE /* AppConstants.swift in Sources */,
1817118185
1751E5911CE0E552000CA08D /* KeyValueDatabase.swift in Sources */,
1817218186
8BF0B607247D88EB009A7457 /* UITableViewCell+enableDisable.swift in Sources */,
@@ -19790,6 +19804,7 @@
1979019804
93CDC72226CD342900C8A3A8 /* DestructiveAlertHelper.swift in Sources */,
1979119805
FABB22612602FC2C00C8785C /* WPStyleGuide+AlertView.swift in Sources */,
1979219806
DC76668426FD9AC9009254DD /* TimeZoneRow.swift in Sources */,
19807+
8B8E50B727A4692000C89979 /* DashboardCardInnerErrorView.swift in Sources */,
1979319808
FABB22622602FC2C00C8785C /* ErrorStateViewConfiguration.swift in Sources */,
1979419809
46B1A16C26A774E500F058AE /* CollapsableHeaderView.swift in Sources */,
1979519810
FABB22632602FC2C00C8785C /* UIFont+Styles.swift in Sources */,

0 commit comments

Comments
 (0)