11import Foundation
2+ import SwiftUI
3+ import WordPressUI
4+
5+ // MARK: - ReaderHeader
26
37extension ReaderStreamViewController {
48 // Convenience type for Reader's headers
59 typealias ReaderHeader = UIView & ReaderStreamHeader
610
7- // A simple struct defining a title and message for use with a NoResultsViewController
8- public struct NoResultsResponse {
9- var title : String
10- var message : String
11- }
12-
1311 func headerForStream( _ topic: ReaderAbstractTopic ? , isLoggedIn: Bool , container: UITableViewController ) -> UIView ? {
1412 if let topic, ReaderHelpers . topicIsFollowing ( topic) {
1513 return ReaderHeaderView . makeForFollowing ( )
@@ -44,19 +42,26 @@ extension ReaderStreamViewController {
4442 }
4543 return nil
4644 }
45+ }
4746
48- static let defaultResponse = NoResultsResponse (
49- title: NSLocalizedString ( " No recent posts " , comment: " A message title " ) ,
50- message: NSLocalizedString ( " No posts have been made recently " , comment: " A default message shown when the reader can find no post to display " ) )
51-
52- /// Returns a NoResultsResponse instance appropriate for the specified ReaderTopic
53- ///
54- /// - Parameter topic: A ReaderTopic.
55- ///
56- /// - Returns: An NoResultsResponse instance.
57- ///
58- public class func responseForNoResults( _ topic: ReaderAbstractTopic ) -> NoResultsResponse {
59- // if following
47+ // MARK: - EmptyStateView (ReaderAbstractTopic)
48+
49+ extension ReaderStreamViewController {
50+ func makeEmptyStateView( for topic: ReaderAbstractTopic ) -> UIView {
51+ let response = ReaderStreamViewController . responseForNoResults ( topic)
52+ return UIHostingView ( view: EmptyStateView (
53+ response. title,
54+ image: " wp-illustration-reader-empty " ,
55+ description: response. message
56+ ) )
57+ }
58+
59+ private struct NoResultsResponse {
60+ var title : String
61+ var message : String
62+ }
63+
64+ private class func responseForNoResults( _ topic: ReaderAbstractTopic ) -> NoResultsResponse {
6065 if ReaderHelpers . topicIsFollowing ( topic) {
6166 return NoResultsResponse (
6267 title: NSLocalizedString ( " Welcome to the Reader " , comment: " A message title " ) ,
@@ -67,24 +72,18 @@ extension ReaderStreamViewController {
6772 )
6873 )
6974 }
70-
71- // if liked
7275 if ReaderHelpers . topicIsLiked ( topic) {
7376 return NoResultsResponse (
7477 title: NSLocalizedString ( " Nothing liked yet " , comment: " A message title " ) ,
7578 message: NSLocalizedString ( " Posts that you like will appear here. " , comment: " A message explaining the Posts I Like feature in the reader " )
7679 )
7780 }
78-
79- // if tag
8081 if ReaderHelpers . isTopicTag ( topic) {
8182 return NoResultsResponse (
8283 title: NSLocalizedString ( " No recent posts " , comment: " A message title " ) ,
8384 message: NSLocalizedString ( " No posts have been made recently with this tag. " , comment: " Message shown whent the reader finds no posts for the chosen tag " )
8485 )
8586 }
86-
87- // if site (blog)
8887 if ReaderHelpers . isTopicSite ( topic) {
8988 return NoResultsResponse (
9089 title: NSLocalizedString ( " No posts " , comment: " A message title " ) ,
@@ -95,8 +94,6 @@ extension ReaderStreamViewController {
9594 )
9695 )
9796 }
98-
99- // if list
10097 if ReaderHelpers . isTopicList ( topic) {
10198 return NoResultsResponse (
10299 title: NSLocalizedString ( " No recent posts " , comment: " A message title " ) ,
@@ -107,42 +104,105 @@ extension ReaderStreamViewController {
107104 )
108105 )
109106 }
110-
111- // if search topic
112107 if ReaderHelpers . isTopicSearchTopic ( topic) {
113108 let message = NSLocalizedString ( " No posts found matching %@ in your language. " , comment: " Message shown when the reader finds no posts for the specified search phrase. The %@ is a placeholder for the search phrase. " )
114109 return NoResultsResponse (
115110 title: NSLocalizedString ( " No posts found " , comment: " A message title " ) ,
116111 message: NSString ( format: message as NSString , topic. title) as String
117112 )
118113 }
119-
120- // Default message
121114 return defaultResponse
122115 }
116+
117+ private static let defaultResponse = NoResultsResponse (
118+ title: NSLocalizedString ( " No recent posts " , comment: " A message title " ) ,
119+ message: NSLocalizedString ( " No posts have been made recently " , comment: " A default message shown when the reader can find no post to display " )
120+ )
123121}
124122
125- // MARK: - No Results for saved posts
123+ // MARK: - EmptyStateView (EmptyStateViewType)
124+
126125extension ReaderStreamViewController {
126+ enum EmptyStateViewType {
127+ case discover
128+ case noSavedPosts
129+ case noFollowedSites
130+ case noConnection
131+ case steamLoadingFailed
132+ }
127133
128- func configureNoResultsViewForSavedPosts( ) {
134+ func makeEmptyStateView( _ type: EmptyStateViewType ) -> UIView {
135+ UIHostingView ( view: _makeEmptyStateView ( type) )
136+ }
129137
130- let noResultsResponse = NoResultsResponse ( title: NSLocalizedString ( " No saved posts " ,
131- comment: " Message displayed in Reader Saved Posts view if a user hasn't yet saved any posts. " ) ,
132- message: NSLocalizedString ( " Tap [bookmark-outline] to save a post to your list. " ,
133- comment: " A hint displayed in the Saved Posts section of the Reader. The '[bookmark-outline]' placeholder will be replaced by an icon at runtime – please leave that string intact. " ) )
138+ @ViewBuilder
139+ private func _makeEmptyStateView( _ type: EmptyStateViewType ) -> some View {
140+ switch type {
141+ case . steamLoadingFailed:
142+ EmptyStateView (
143+ ResultsStatusText . loadingErrorTitle,
144+ systemImage: " exclamationmark.circle " ,
145+ description: ResultsStatusText . loadingErrorMessage
146+ )
147+ case . noSavedPosts:
148+ EmptyStateView ( label: {
149+ Label ( NSLocalizedString ( " No saved posts " , comment: " Message displayed in Reader Saved Posts view if a user hasn't yet saved any posts. " ) , image: " wp-illustration-reader-empty " )
150+ } , description: {
151+ // Had to use UIKit because Text(AttributedString()) won't render the attachment
152+ HostedAttributedLabel ( text: self . makeSavedPostsEmptyViewDescription ( ) )
153+ . fixedSize ( )
154+ } , actions: {
155+ EmptyView ( )
156+ } )
157+ case . discover:
158+ EmptyStateView (
159+ ReaderStreamViewController . defaultResponse. title,
160+ image: " wp-illustration-reader-empty " ,
161+ description: ReaderStreamViewController . defaultResponse. message
162+ )
163+ case . noConnection:
164+ EmptyStateView (
165+ ResultsStatusText . noConnectionTitle,
166+ systemImage: " network.slash " ,
167+ description: noConnectionMessage ( )
168+ )
169+ case . noFollowedSites:
170+ EmptyStateView ( label: {
171+ Label ( Strings . noFollowedSitesTitle, systemImage: " checkmark.circle " )
172+ } , description: {
173+ Text ( Strings . noFollowedSitesSubtitle)
174+ } , actions: {
175+ Button ( Strings . noFollowedSitesButtonTitle) {
176+ RootViewCoordinator . sharedPresenter. showReader ( path: . discover)
177+ }
178+ . buttonStyle ( . primary)
179+ } )
180+ }
181+ }
134182
135- var messageText = NSMutableAttributedString ( string: noResultsResponse. message)
183+ private func makeSavedPostsEmptyViewDescription( ) -> NSAttributedString {
184+ let details = NSLocalizedString ( " Tap [bookmark-outline] to save a post to your list. " , comment: " A hint displayed in the Saved Posts section of the Reader. The '[bookmark-outline]' placeholder will be replaced by an icon at runtime – please leave that string intact. " )
185+ let string = NSMutableAttributedString ( string: details, attributes: [
186+ . font: UIFont . preferredFont ( forTextStyle: . subheadline)
187+ ] )
188+ let icon = UIImage . gridicon ( . bookmarkOutline, size: CGSize ( width: 18 , height: 18 ) )
189+ string. replace ( " [bookmark-outline] " , with: icon)
190+ string. addAttribute ( . foregroundColor, value: UIColor . secondaryLabel, range: NSRange ( location: 0 , length: string. length) )
191+ return string
192+ }
193+ }
136194
137- // Get attributed string styled for No Results so it gets the correct font attributes added to it.
138- // The font is used by the attributed string `replace(_:with:)` method below to correctly position the icon.
139- let styledText = resultsStatusView. applyMessageStyleTo ( attributedString: messageText)
140- messageText = NSMutableAttributedString ( attributedString: styledText)
195+ private struct HostedAttributedLabel : UIViewRepresentable {
196+ let text : NSAttributedString
141197
142- let icon = UIImage . gridicon ( . bookmarkOutline, size: CGSize ( width: 18 , height: 18 ) )
143- messageText. replace ( " [bookmark-outline] " , with: icon)
198+ func makeUIView( context: Context ) -> UILabel {
199+ let label = UILabel ( )
200+ label. attributedText = text
201+ return label
202+ }
144203
145- resultsStatusView. configureForLocalData ( title: noResultsResponse. title, attributedSubtitle: messageText, image: " wp-illustration-reader-empty " )
204+ func updateUIView( _ uiView: UILabel , context: Context ) {
205+ // Do nothing
146206 }
147207}
148208
@@ -152,3 +212,21 @@ extension ReaderStreamViewController {
152212 WPAnalytics . trackReader ( . readerSavedListShown, properties: [ " source " : ReaderSaveForLaterOrigin . readerMenu. viewAllPostsValue] )
153213 }
154214}
215+
216+ private struct Strings {
217+ static let noFollowedSitesTitle = NSLocalizedString (
218+ " reader.no.blogs.title " ,
219+ value: " No blog subscriptions " ,
220+ comment: " Title for the no followed blogs result screen "
221+ )
222+ static let noFollowedSitesSubtitle = NSLocalizedString (
223+ " reader.no.blogs.subtitle " ,
224+ value: " Subscribe to blogs in Discover and you’ll see their latest posts here. Or search for a blog that you like already. " ,
225+ comment: " Subtitle for the no followed blogs result screen "
226+ )
227+ static let noFollowedSitesButtonTitle = NSLocalizedString (
228+ " reader.no.blogs.button " ,
229+ value: " Discover Blogs " ,
230+ comment: " Title for button on the no followed blogs result screen "
231+ )
232+ }
0 commit comments