Skip to content

Commit f035b18

Browse files
authored
Reader: Enhance cover image (#23897)
* Replace DimensionFetcher usage * Remove ImageLoader usage from ReaderDetailFeaturedImageView * Remove ImageLoader from StatsLatestPostSummaryInsightsCell * Remove ImageDimensionsFetcher usage * Remove unused useCompatibilityMode * Move reading preferences to the more menu * Add a bit more spacing for posts in Reader * Refine AsyncImageView API * Fix dark/light transition on scroll * Update release notes
1 parent 3cff8fc commit f035b18

File tree

16 files changed

+134
-154
lines changed

16 files changed

+134
-154
lines changed

RELEASE-NOTES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* [*] Fix minor appearance issues in the Blaze campaign list [#23891]
1414
* [*] Improve the sidebar animations and layout on some iPad models [#23886]
1515
* [*] Fix an issue with posts shown embedded in the notifications popover on iPad [#23889]
16+
* [*] The post cover now uses the standard aspect ratio for covers, so there is no jumping. There are also a few minor improvements to the layout and animations of the cover [#23897]
17+
* [*] Move the "Reading Preferences" button to the "More" menu [#23897]
1618

1719
25.5
1820
-----

WordPress/Classes/Extensions/Interpolation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation
1+
import UIKit
22

33
extension CGFloat {
44
/// Interpolates a CGFloat

WordPress/Classes/Networking/MediaHost+ReaderPost.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import Foundation
44
/// initialize it from a given `Blog`.
55
///
66
extension MediaHost {
7-
enum ReaderPostError: Swift.Error {
8-
case baseInitializerError(error: Error)
9-
}
10-
11-
init(with post: ReaderPost, failure: (ReaderPostError) -> ()) {
7+
init(with post: ReaderPost) {
128
let isAccessibleThroughWPCom = post.isWPCom || post.isJetpack
139

1410
// This is the only way in which we can obtain the username and authToken here.
@@ -28,7 +24,7 @@ extension MediaHost {
2824
username: username,
2925
authToken: authToken,
3026
failure: { error in
31-
failure(ReaderPostError.baseInitializerError(error: error))
27+
WordPressAppDelegate.crashLogging?.logError(error)
3228
}
3329
)
3430
}

WordPress/Classes/Utility/Media/AsyncImageView.swift

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,59 @@ import Gifu
55
/// (see ``AnimatedImage``).
66
@MainActor
77
final class AsyncImageView: UIView {
8-
let imageView = GIFImageView()
9-
8+
private let imageView = GIFImageView()
109
private var errorView: UIImageView?
1110
private var spinner: UIActivityIndicatorView?
1211
private let controller = ImageViewController()
1312

1413
enum LoadingStyle {
14+
/// Shows a secondary background color during the download.
1515
case background
16+
/// Shows a spinner during the download.
1617
case spinner
1718
}
1819

19-
var isErrorViewEnabled = true
20-
var loadingStyle = LoadingStyle.background
20+
struct Configuration {
21+
/// Image tint color.
22+
var tintColor: UIColor?
23+
24+
/// Image view content mode.
25+
var contentMode: UIView.ContentMode?
26+
27+
/// Enabled by default and shows an error icon on failures.
28+
var isErrorViewEnabled = true
29+
30+
/// By default, `background`.
31+
var loadingStyle = LoadingStyle.background
32+
}
33+
34+
var configuration = Configuration() {
35+
didSet { didUpdateConfiguration(configuration) }
36+
}
37+
38+
/// The currently displayed image. If the image is animated, returns an
39+
/// instance of ``AnimatedImage``.
40+
var image: UIImage? {
41+
didSet {
42+
if let image {
43+
imageView.configure(image: image)
44+
} else {
45+
imageView.prepareForReuse()
46+
}
47+
}
48+
}
2149

2250
override init(frame: CGRect) {
2351
super.init(frame: frame)
52+
setupView()
53+
}
54+
55+
required init?(coder: NSCoder) {
56+
super.init(coder: coder)
57+
setupView()
58+
}
2459

60+
private func setupView() {
2561
controller.onStateChanged = { [weak self] in self?.setState($0) }
2662

2763
addSubview(imageView)
@@ -35,18 +71,10 @@ final class AsyncImageView: UIView {
3571
backgroundColor = .secondarySystemBackground
3672
}
3773

38-
required init?(coder: NSCoder) {
39-
fatalError("init(coder:) has not been implemented")
40-
}
41-
74+
/// Removes the current image and stops the outstanding downloads.
4275
func prepareForReuse() {
4376
controller.prepareForReuse()
44-
45-
if imageView.isAnimatingGIF {
46-
imageView.prepareForReuse()
47-
} else {
48-
imageView.image = nil
49-
}
77+
image = nil
5078
}
5179

5280
/// - parameter size: Target image size in pixels.
@@ -66,23 +94,32 @@ final class AsyncImageView: UIView {
6694

6795
switch state {
6896
case .loading:
69-
switch loadingStyle {
97+
switch configuration.loadingStyle {
7098
case .background:
7199
backgroundColor = .secondarySystemBackground
72100
case .spinner:
73101
makeSpinner().startAnimating()
74102
}
75103
case .success(let image):
76-
imageView.configure(image: image)
104+
self.image = image
77105
imageView.isHidden = false
78106
backgroundColor = .clear
79107
case .failure:
80-
if isErrorViewEnabled {
108+
if configuration.isErrorViewEnabled {
81109
makeErrorView().isHidden = false
82110
}
83111
}
84112
}
85113

114+
private func didUpdateConfiguration(_ configuration: Configuration) {
115+
if let tintColor = configuration.tintColor {
116+
imageView.tintColor = tintColor
117+
}
118+
if let contentMode = configuration.contentMode {
119+
imageView.contentMode = contentMode
120+
}
121+
}
122+
86123
private func makeSpinner() -> UIActivityIndicatorView {
87124
if let spinner {
88125
return spinner
@@ -119,4 +156,12 @@ extension GIFImageView {
119156
self.image = image
120157
}
121158
}
159+
160+
private func prepareForReuse() {
161+
if isAnimatingGIF {
162+
prepareForReuse()
163+
} else {
164+
image = nil
165+
}
166+
}
122167
}

WordPress/Classes/Utility/Media/ImageLoader.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,7 @@ import WordPressShared
9191
@objc(loadImageWithURL:fromReaderPost:preferredSize:placeholder:success:error:)
9292
func loadImage(with url: URL, from readerPost: ReaderPost, preferredSize size: CGSize = .zero, placeholder: UIImage?, success: ImageLoaderSuccessBlock?, error: ImageLoaderFailureBlock?) {
9393

94-
let host = MediaHost(with: readerPost, failure: { error in
95-
WordPressAppDelegate.crashLogging?.logError(error)
96-
})
97-
94+
let host = MediaHost(with: readerPost)
9895
loadImage(with: url, from: host, preferredSize: size, placeholder: placeholder, success: success, error: error)
9996
}
10097

WordPress/Classes/ViewRelated/Comments/Views/Detail/ContentRenderer/RichCommentContentRenderer.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,7 @@ private extension RichCommentContentRenderer {
7676
WordPressAppDelegate.crashLogging?.logError(error)
7777
})
7878
} else if let post = comment.post as? ReaderPost, post.isBlogPrivate {
79-
return MediaHost(with: post, failure: { error in
80-
// We'll log the error, so we know it's there, but we won't halt execution.
81-
WordPressAppDelegate.crashLogging?.logError(error)
82-
})
79+
return MediaHost(with: post)
8380
}
8481

8582
return .publicSite

WordPress/Classes/ViewRelated/Reader/Cards/ReaderPostCell.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ private final class ReaderPostCellView: UIView {
156156
avatarView.centerYAnchor.constraint(equalTo: timeLabel.centerYAnchor),
157157
avatarView.trailingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: -8),
158158

159-
headerView.topAnchor.constraint(equalTo: topAnchor, constant: 4),
159+
headerView.topAnchor.constraint(equalTo: topAnchor, constant: 6),
160160
headerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left),
161161
headerView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -50),
162162

@@ -391,7 +391,7 @@ private func makeButton(systemImage: String, font: UIFont = UIFont.preferredFont
391391
configuration.imagePadding = 6
392392
configuration.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(font: font)
393393
configuration.baseForegroundColor = .secondaryLabel
394-
configuration.contentInsets = .init(top: 16, leading: 12, bottom: 14, trailing: 12)
394+
configuration.contentInsets = .init(top: 16, leading: 12, bottom: 16, trailing: 12)
395395

396396
let button = UIButton(configuration: configuration)
397397
if #available(iOS 17.0, *) {

WordPress/Classes/ViewRelated/Reader/Detail/ReaderDetailCoordinator.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -481,20 +481,12 @@ class ReaderDetailCoordinator {
481481

482482
/// Show the featured image fullscreen
483483
///
484-
private func showFeaturedImage(_ sender: CachedAnimatedImageView) {
485-
guard let post else {
486-
return
487-
}
488-
489-
var controller: WPImageViewController
490-
if post.featuredImageURL.isGif, let data = sender.animatedGifData {
491-
controller = WPImageViewController(gifData: data)
492-
} else if let featuredImage = sender.image {
493-
controller = WPImageViewController(image: featuredImage)
494-
} else {
484+
private func showFeaturedImage(_ sender: AsyncImageView) {
485+
guard let post, let imageURL = post.featuredImage.flatMap(URL.init) else {
495486
return
496487
}
497-
488+
let controller = WPImageViewController(url: imageURL)
489+
controller.readerPost = post
498490
controller.modalTransitionStyle = .crossDissolve
499491
controller.modalPresentationStyle = .fullScreen
500492
viewController?.present(controller, animated: true)
@@ -722,7 +714,7 @@ extension ReaderDetailCoordinator: ReaderDetailHeaderViewDelegate {
722714

723715
// MARK: - ReaderDetailFeaturedImageViewDelegate
724716
extension ReaderDetailCoordinator: ReaderDetailFeaturedImageViewDelegate {
725-
func didTapFeaturedImage(_ sender: CachedAnimatedImageView) {
717+
func didTapFeaturedImage(_ sender: AsyncImageView) {
726718
showFeaturedImage(sender)
727719
}
728720
}

WordPress/Classes/ViewRelated/Reader/Detail/ReaderDetailViewController.swift

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,6 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
582582

583583
private func configureHeader() {
584584
header.displaySetting = displaySetting
585-
header.useCompatibilityMode = useCompatibilityMode
586585
header.delegate = coordinator
587586
headerContainerView.addSubview(header)
588587
headerContainerView.translatesAutoresizingMaskIntoConstraints = false
@@ -719,7 +718,7 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
719718
coordinator?.openInBrowser()
720719
}
721720

722-
@objc func didTapDisplaySettingButton(_ sender: UIBarButtonItem) {
721+
@objc func didTapDisplaySettingButton() {
723722
let viewController = ReaderDisplaySettingViewController(initialSetting: displaySetting,
724723
source: .readerPostNavBar) { [weak self] newSetting in
725724
// no need to refresh if there are no changes to the display setting.
@@ -1051,8 +1050,7 @@ private extension ReaderDetailViewController {
10511050
let rightItems = [
10521051
moreButtonItem(enabled: enableRightBarButtons),
10531052
shareButtonItem(enabled: enableRightBarButtons),
1054-
safariButtonItem(),
1055-
displaySettingButtonItem()
1053+
safariButtonItem()
10561054
]
10571055
navigationItem.largeTitleDisplayMode = .never
10581056
navigationItem.rightBarButtonItems = rightItems.compactMap({ $0 })
@@ -1095,17 +1093,6 @@ private extension ReaderDetailViewController {
10951093
dismiss(animated: true)
10961094
}
10971095

1098-
func displaySettingButtonItem() -> UIBarButtonItem? {
1099-
guard ReaderDisplaySetting.customizationEnabled,
1100-
let icon = UIImage(named: "reader-reading-preferences") else {
1101-
return nil
1102-
}
1103-
let button = barButtonItem(with: icon, action: #selector(didTapDisplaySettingButton(_:)))
1104-
button.accessibilityLabel = Strings.displaySettingAccessibilityLabel
1105-
1106-
return button
1107-
}
1108-
11091096
func safariButtonItem() -> UIBarButtonItem? {
11101097
let button = barButtonItem(with: .gridicon(.globe), action: #selector(didTapBrowserButton(_:)))
11111098
button.accessibilityLabel = Strings.safariButtonAccessibilityLabel
@@ -1132,12 +1119,20 @@ private extension ReaderDetailViewController {
11321119
guard let post else {
11331120
return []
11341121
}
1135-
return ReaderPostMenu(
1122+
var elements = ReaderPostMenu(
11361123
post: post,
11371124
topic: nil,
11381125
anchor: anchor,
11391126
viewController: self
11401127
).makeMenu()
1128+
1129+
if ReaderDisplaySetting.customizationEnabled {
1130+
elements.append(UIAction(title: Strings.displaySettingsLabel, image: UIImage(systemName: "textformat.size")) { [weak self] _ in
1131+
self?.didTapDisplaySettingButton()
1132+
})
1133+
}
1134+
1135+
return elements
11411136
}
11421137

11431138
func shareButtonItem(enabled: Bool = true) -> UIBarButtonItem? {
@@ -1186,8 +1181,8 @@ extension ReaderDetailViewController {
11861181
value: "Dismiss",
11871182
comment: "Spoken accessibility label"
11881183
)
1189-
static let displaySettingAccessibilityLabel = NSLocalizedString(
1190-
"readerDetail.displaySettingButton.accessibilityLabel",
1184+
static let displaySettingsLabel = NSLocalizedString(
1185+
"readerDetail.displaySettingButton.displaySettingsLabel",
11911186
value: "Reading Preferences",
11921187
comment: "Spoken accessibility label for the Reading Preferences menu.")
11931188
static let safariButtonAccessibilityLabel = NSLocalizedString(

0 commit comments

Comments
 (0)