Skip to content

Commit

Permalink
Reader: Safe Area and Empty State View (#23755)
Browse files Browse the repository at this point in the history
* Fix an issue with stream not adjusting insets due to the lack of child-parent configuration:

* Replae NoResultsVC with EmptyStateView

* EmptyStateView to update for dynamic type

* Remove redundant tableView.contentInsetAdjustmentBehavior = .always

* Disable Reader tests for now
  • Loading branch information
kean authored Nov 4, 2024
1 parent b13db78 commit 5a19927
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 237 deletions.
7 changes: 5 additions & 2 deletions Modules/Sources/WordPressUI/Views/EmptyStateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ public struct EmptyStateView<Label: View, Description: View, Actions: View>: Vie
@ViewBuilder let label: () -> Label
@ViewBuilder var description: () -> Description
@ViewBuilder var actions: () -> Actions

@ScaledMetric(relativeTo: .title) var maxWidthCompact = 320
@ScaledMetric(relativeTo: .title) var maxWidthRegular = 420
@Environment(\.horizontalSizeClass) var horizontalSizeClass

public init(
Expand All @@ -30,7 +33,8 @@ public struct EmptyStateView<Label: View, Description: View, Actions: View>: Vie
}
actions()
}
.frame(maxWidth: horizontalSizeClass == .compact ? 300 : 420)
.frame(maxWidth: horizontalSizeClass == .compact ? maxWidthCompact : maxWidthRegular)
.padding()
}
}

Expand Down Expand Up @@ -85,6 +89,5 @@ private struct EmptyStateViewLabelStyle: LabelStyle {
Text("Create Tag")
}
.buttonStyle(.borderedProminent)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,7 @@ extension NoResultsViewController {
controller.labelStackViewSpacing = 8
controller.labelButtonStackViewSpacing = 18
controller.loadViewIfNeeded()
controller.setupReaderButtonStyles()

return controller
}
}

extension NoResultsViewController {

func setupReaderButtonStyles() {
actionButton.primaryNormalBackgroundColor = .label
actionButton.primaryTitleColor = .systemBackground
actionButton.primaryHighlightBackgroundColor = .label
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import Foundation
import SwiftUI
import WordPressUI

// MARK: - ReaderHeader

extension ReaderStreamViewController {
// Convenience type for Reader's headers
typealias ReaderHeader = UIView & ReaderStreamHeader

// A simple struct defining a title and message for use with a NoResultsViewController
public struct NoResultsResponse {
var title: String
var message: String
}

func headerForStream(_ topic: ReaderAbstractTopic?, isLoggedIn: Bool, container: UITableViewController) -> UIView? {
if let topic, ReaderHelpers.topicIsFollowing(topic) {
return ReaderHeaderView.makeForFollowing()
Expand Down Expand Up @@ -44,19 +42,26 @@ extension ReaderStreamViewController {
}
return nil
}
}

static let defaultResponse = NoResultsResponse(
title: NSLocalizedString("No recent posts", comment: "A message title"),
message: NSLocalizedString("No posts have been made recently", comment: "A default message shown when the reader can find no post to display"))

/// Returns a NoResultsResponse instance appropriate for the specified ReaderTopic
///
/// - Parameter topic: A ReaderTopic.
///
/// - Returns: An NoResultsResponse instance.
///
public class func responseForNoResults(_ topic: ReaderAbstractTopic) -> NoResultsResponse {
// if following
// MARK: - EmptyStateView (ReaderAbstractTopic)

extension ReaderStreamViewController {
func makeEmptyStateView(for topic: ReaderAbstractTopic) -> UIView {
let response = ReaderStreamViewController.responseForNoResults(topic)
return UIHostingView(view: EmptyStateView(
response.title,
image: "wp-illustration-reader-empty",
description: response.message
))
}

private struct NoResultsResponse {
var title: String
var message: String
}

private class func responseForNoResults(_ topic: ReaderAbstractTopic) -> NoResultsResponse {
if ReaderHelpers.topicIsFollowing(topic) {
return NoResultsResponse(
title: NSLocalizedString("Welcome to the Reader", comment: "A message title"),
Expand All @@ -67,24 +72,18 @@ extension ReaderStreamViewController {
)
)
}

// if liked
if ReaderHelpers.topicIsLiked(topic) {
return NoResultsResponse(
title: NSLocalizedString("Nothing liked yet", comment: "A message title"),
message: NSLocalizedString("Posts that you like will appear here.", comment: "A message explaining the Posts I Like feature in the reader")
)
}

// if tag
if ReaderHelpers.isTopicTag(topic) {
return NoResultsResponse(
title: NSLocalizedString("No recent posts", comment: "A message title"),
message: NSLocalizedString("No posts have been made recently with this tag.", comment: "Message shown whent the reader finds no posts for the chosen tag")
)
}

// if site (blog)
if ReaderHelpers.isTopicSite(topic) {
return NoResultsResponse(
title: NSLocalizedString("No posts", comment: "A message title"),
Expand All @@ -95,8 +94,6 @@ extension ReaderStreamViewController {
)
)
}

// if list
if ReaderHelpers.isTopicList(topic) {
return NoResultsResponse(
title: NSLocalizedString("No recent posts", comment: "A message title"),
Expand All @@ -107,42 +104,105 @@ extension ReaderStreamViewController {
)
)
}

// if search topic
if ReaderHelpers.isTopicSearchTopic(topic) {
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.")
return NoResultsResponse(
title: NSLocalizedString("No posts found", comment: "A message title"),
message: NSString(format: message as NSString, topic.title) as String
)
}

// Default message
return defaultResponse
}

private static let defaultResponse = NoResultsResponse(
title: NSLocalizedString("No recent posts", comment: "A message title"),
message: NSLocalizedString("No posts have been made recently", comment: "A default message shown when the reader can find no post to display")
)
}

// MARK: - No Results for saved posts
// MARK: - EmptyStateView (EmptyStateViewType)

extension ReaderStreamViewController {
enum EmptyStateViewType {
case discover
case noSavedPosts
case noFollowedSites
case noConnection
case steamLoadingFailed
}

func configureNoResultsViewForSavedPosts() {
func makeEmptyStateView(_ type: EmptyStateViewType) -> UIView {
UIHostingView(view: _makeEmptyStateView(type))
}

let noResultsResponse = NoResultsResponse(title: NSLocalizedString("No saved posts",
comment: "Message displayed in Reader Saved Posts view if a user hasn't yet saved any posts."),
message: 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."))
@ViewBuilder
private func _makeEmptyStateView(_ type: EmptyStateViewType) -> some View {
switch type {
case .steamLoadingFailed:
EmptyStateView(
ResultsStatusText.loadingErrorTitle,
systemImage: "exclamationmark.circle",
description: ResultsStatusText.loadingErrorMessage
)
case .noSavedPosts:
EmptyStateView(label: {
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")
}, description: {
// Had to use UIKit because Text(AttributedString()) won't render the attachment
HostedAttributedLabel(text: self.makeSavedPostsEmptyViewDescription())
.fixedSize()
}, actions: {
EmptyView()
})
case .discover:
EmptyStateView(
ReaderStreamViewController.defaultResponse.title,
image: "wp-illustration-reader-empty",
description: ReaderStreamViewController.defaultResponse.message
)
case .noConnection:
EmptyStateView(
ResultsStatusText.noConnectionTitle,
systemImage: "network.slash",
description: noConnectionMessage()
)
case .noFollowedSites:
EmptyStateView(label: {
Label(Strings.noFollowedSitesTitle, systemImage: "checkmark.circle")
}, description: {
Text(Strings.noFollowedSitesSubtitle)
}, actions: {
Button(Strings.noFollowedSitesButtonTitle) {
RootViewCoordinator.sharedPresenter.showReader(path: .discover)
}
.buttonStyle(.primary)
})
}
}

var messageText = NSMutableAttributedString(string: noResultsResponse.message)
private func makeSavedPostsEmptyViewDescription() -> NSAttributedString {
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.")
let string = NSMutableAttributedString(string: details, attributes: [
.font: UIFont.preferredFont(forTextStyle: .subheadline)
])
let icon = UIImage.gridicon(.bookmarkOutline, size: CGSize(width: 18, height: 18))
string.replace("[bookmark-outline]", with: icon)
string.addAttribute(.foregroundColor, value: UIColor.secondaryLabel, range: NSRange(location: 0, length: string.length))
return string
}
}

// Get attributed string styled for No Results so it gets the correct font attributes added to it.
// The font is used by the attributed string `replace(_:with:)` method below to correctly position the icon.
let styledText = resultsStatusView.applyMessageStyleTo(attributedString: messageText)
messageText = NSMutableAttributedString(attributedString: styledText)
private struct HostedAttributedLabel: UIViewRepresentable {
let text: NSAttributedString

let icon = UIImage.gridicon(.bookmarkOutline, size: CGSize(width: 18, height: 18))
messageText.replace("[bookmark-outline]", with: icon)
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.attributedText = text
return label
}

resultsStatusView.configureForLocalData(title: noResultsResponse.title, attributedSubtitle: messageText, image: "wp-illustration-reader-empty")
func updateUIView(_ uiView: UILabel, context: Context) {
// Do nothing
}
}

Expand All @@ -152,3 +212,21 @@ extension ReaderStreamViewController {
WPAnalytics.trackReader(.readerSavedListShown, properties: ["source": ReaderSaveForLaterOrigin.readerMenu.viewAllPostsValue])
}
}

private struct Strings {
static let noFollowedSitesTitle = NSLocalizedString(
"reader.no.blogs.title",
value: "No blog subscriptions",
comment: "Title for the no followed blogs result screen"
)
static let noFollowedSitesSubtitle = NSLocalizedString(
"reader.no.blogs.subtitle",
value: "Subscribe to blogs in Discover and you’ll see their latest posts here. Or search for a blog that you like already.",
comment: "Subtitle for the no followed blogs result screen"
)
static let noFollowedSitesButtonTitle = NSLocalizedString(
"reader.no.blogs.button",
value: "Discover Blogs",
comment: "Title for button on the no followed blogs result screen"
)
}
Loading

0 comments on commit 5a19927

Please sign in to comment.