Skip to content

Commit 2c53cc5

Browse files
wpmobilebotjkmasselcrazytonyli
authored
Merge release/27.4 into trunk (#26183)
* Reader: Keep the article toolbar icons in one colour style after a like (#26169) * Update the Reader article toolbar buttons in place Rebuilding every toolbar item on each like, save or count change let iOS 26 give the new items the dark style while the floating toolbar stayed light, so some icons turned white on a light bar (CMM-2548). The toolbar now keeps its buttons and reconfigures them. It only rebuilds them when the comment or like button appears or disappears. Custom-view buttons don't resize by themselves when their title changes, so they're resized after each update. * Add a release note for the Reader toolbar fix * Name the check that decides whether to rebuild the Reader toolbar The toolbar reuses its buttons unless the comment or like button has to appear or disappear. That check compared two optionals against two flags in one condition, which was hard to read. It now compares a small `Layout` value, with a comment saying when the toolbar is rebuilt. Adds a test for the rebuild case: the first comment on a post whose comments are closed adds the comment button. * Reader: Stop clipping the like count when it gets slightly wider (#26177) After a like, the like button could keep its old width and clip the new count, e.g. 1 to 2 on iOS 18, or 1 to 9 on iOS 26. `sizeToFit()` measures the button at its current width, and the button fits a slightly wider title into that width instead of growing. Size the button from an unconstrained measurement instead. * Reader: Put the article toolbar buttons on frosted capsules so they stay readable (#26176) * Put the Reader article toolbar buttons on frosted capsules On iOS 26 and later, the toolbar's shared Liquid Glass takes its colour from the content under it. Over some images it ends up close in brightness to the icons: white icons on light glass in Dark Mode on iOS 27, the green like and save icons on grey glass, and white icons on light glass over busy images on iOS 26 (CMM-2548). The buttons now sit on two frosted capsules (systemThickMaterial). They stay near white or near black and switch together with the icons, so the icons stay legible over any content. Selected icons use one shade darker green in light mode, and counts stay in the label colour. With no shared glass, iOS 27 draws a frosted band behind the whole toolbar, so the scroll view's bottom edge effect is hidden. A capsule pins each button to its sizeThatFits width. A button's intrinsic content size lags behind a new configuration, and a stack view sized from it clipped a count after it changed width. * Add a release note for the frosted Reader toolbar * Hide the Reader reblog button when the post can't be reblogged Signed out, or on a private site, the reblog button was shown disabled. On iOS 26+ the frosted capsules set the icon colour directly, so the disabled reblog icon no longer greyed out and looked tappable. Leave the button out instead, on every iOS version. * Find the like button inside its frosted capsule in the growth test On iOS 26+ the like button sits on a capsule, so it is no longer the toolbar's last custom view, and its width is pinned by a constraint. * Patch invalid GlotPress placeholders before localization linting (#26182) * Update app translations – `Localizable.strings` * Update plural translations from GlotPress * Update WordPress metadata translations * Update Jetpack metadata translations * Bump version number --------- Co-authored-by: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Co-authored-by: Tony Li <tony.li@automattic.com>
1 parent f5bcd7c commit 2c53cc5

48 files changed

Lines changed: 2865 additions & 4054 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎RELEASE-NOTES.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* [*] Reader: Fix inline images appearing blank on iOS 27 [#26120]
88
* [*] iPad: Fix the app getting stuck in the compact tab bar layout after resizing or switching apps [#26123]
99
* [*] Reader: Fix post tag outlines almost disappearing after switching between light and dark mode [#26168]
10+
* [*] Reader: Fix some article toolbar icons turning white after liking a post on iOS 26 [#26169]
11+
* [*] Reader: Make the article toolbar buttons easier to see over images on iOS 26 and later [#26176]
1012

1113
27.3
1214
-----
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import Testing
2+
import UIKit
3+
@testable import WordPress
4+
@testable import WordPressData
5+
6+
@MainActor
7+
@Suite("ReaderDetailToolbar")
8+
struct ReaderDetailToolbarTests {
9+
@Test("a like updates the existing toolbar items instead of recreating them")
10+
func likeKeepsTheSameItems() throws {
11+
let context = ContextManager.forTesting().mainContext
12+
let post = ReaderPostBuilder(context).build()
13+
post.isLikesEnabled = true
14+
post.likeCount = 2
15+
let viewController = UIViewController()
16+
let toolbar = ReaderDetailToolbar()
17+
18+
toolbar.configure(for: post, in: viewController)
19+
let itemsBefore = try #require(viewController.toolbarItems)
20+
21+
post.isLiked = true
22+
post.likeCount = 1234
23+
24+
let itemsAfter = try #require(viewController.toolbarItems)
25+
#expect(itemsAfter.count == itemsBefore.count)
26+
#expect(zip(itemsBefore, itemsAfter).allSatisfy { $0 === $1 })
27+
28+
let likeButton = try #require(buttons(in: itemsAfter).last)
29+
#expect(likeButton.configuration?.title == 1234.formatted(.number.notation(.compactName)))
30+
}
31+
32+
@Test("the like button resizes when its count gets shorter")
33+
func likeButtonResizesToItsCount() throws {
34+
let context = ContextManager.forTesting().mainContext
35+
let post = ReaderPostBuilder(context).build()
36+
post.isLikesEnabled = true
37+
post.likeCount = 1234
38+
let viewController = UIViewController()
39+
let toolbar = ReaderDetailToolbar()
40+
41+
toolbar.configure(for: post, in: viewController)
42+
let likeButton = try #require(buttons(in: viewController.toolbarItems).last)
43+
let wideWidth = width(of: likeButton)
44+
45+
post.likeCount = 2
46+
47+
#expect(width(of: likeButton) < wideWidth)
48+
#expect(abs(width(of: likeButton) - likeButton.sizeThatFits(UIView.layoutFittingCompressedSize).width) < 1)
49+
}
50+
51+
@Test("the like button grows when its count gets slightly wider")
52+
func likeButtonGrowsToItsCount() throws {
53+
let context = ContextManager.forTesting().mainContext
54+
let post = ReaderPostBuilder(context).build()
55+
post.isLikesEnabled = true
56+
post.likeCount = 1
57+
let viewController = UIViewController()
58+
let toolbar = ReaderDetailToolbar()
59+
60+
toolbar.configure(for: post, in: viewController)
61+
let likeButton = try #require(buttons(in: viewController.toolbarItems).last)
62+
let narrowWidth = width(of: likeButton)
63+
64+
post.likeCount = 9
65+
66+
#expect(width(of: likeButton) > narrowWidth)
67+
#expect(abs(width(of: likeButton) - likeButton.sizeThatFits(UIView.layoutFittingCompressedSize).width) < 1)
68+
}
69+
70+
@Test("the toolbar is rebuilt when the comment button has to appear")
71+
func firstCommentAddsTheCommentButton() throws {
72+
let context = ContextManager.forTesting().mainContext
73+
let post = ReaderPostBuilder(context).build()
74+
post.isWPCom = true
75+
post.isLikesEnabled = false
76+
post.commentsOpen = false
77+
post.commentCount = 0
78+
let viewController = UIViewController()
79+
let toolbar = ReaderDetailToolbar(isLoggedIn: { true })
80+
81+
toolbar.configure(for: post, in: viewController)
82+
#expect(buttons(in: viewController.toolbarItems).count == 2) // Save and reblog
83+
84+
post.commentCount = 1
85+
86+
let buttonsAfter = buttons(in: viewController.toolbarItems)
87+
#expect(buttonsAfter.count == 3)
88+
#expect(buttonsAfter.last?.configuration?.title == 1.formatted(.number.notation(.compactName)))
89+
}
90+
91+
@Test(
92+
"the reblog button is shown only when the post can be reblogged",
93+
arguments: [
94+
(isLoggedIn: true, isBlogPrivate: false, showsReblog: true),
95+
(isLoggedIn: true, isBlogPrivate: true, showsReblog: false),
96+
(isLoggedIn: false, isBlogPrivate: false, showsReblog: false)
97+
]
98+
)
99+
func reblogButtonVisibility(isLoggedIn: Bool, isBlogPrivate: Bool, showsReblog: Bool) throws {
100+
let context = ContextManager.forTesting().mainContext
101+
let post = ReaderPostBuilder(context).build()
102+
post.isBlogPrivate = isBlogPrivate
103+
post.isLikesEnabled = false
104+
post.commentsOpen = false
105+
post.commentCount = 0
106+
let viewController = UIViewController()
107+
let toolbar = ReaderDetailToolbar(isLoggedIn: { isLoggedIn })
108+
109+
toolbar.configure(for: post, in: viewController)
110+
111+
let labels = buttons(in: viewController.toolbarItems).map(\.accessibilityLabel)
112+
#expect(labels.contains("Reblog post") == showsReblog)
113+
#expect(labels.count == (showsReblog ? 2 : 1)) // Save, and reblog when shown
114+
}
115+
116+
@Test("a frosted capsule fits its buttons after a count changes width")
117+
func capsuleFitsItsButtons() throws {
118+
guard #available(iOS 26, *) else { return }
119+
120+
let context = ContextManager.forTesting().mainContext
121+
let post = ReaderPostBuilder(context).build()
122+
post.isLikesEnabled = true
123+
post.likeCount = 3
124+
let viewController = UIViewController()
125+
let toolbar = ReaderDetailToolbar()
126+
127+
toolbar.configure(for: post, in: viewController)
128+
let capsule = try #require(viewController.toolbarItems?.last?.customView)
129+
let narrowWidth = capsule.intrinsicContentSize.width
130+
131+
post.likeCount = 1234
132+
133+
#expect(capsule.intrinsicContentSize.width > narrowWidth)
134+
#expect(capsule.frame.width == capsule.intrinsicContentSize.width)
135+
for button in buttons(in: capsule) {
136+
#expect(width(of: button) >= button.sizeThatFits(UIView.layoutFittingCompressedSize).width)
137+
}
138+
139+
post.likeCount = 3
140+
141+
#expect(capsule.intrinsicContentSize.width == narrowWidth)
142+
#expect(capsule.frame.width == narrowWidth)
143+
}
144+
145+
// MARK: - Helpers
146+
147+
/// The toolbar's buttons in order, whether each is its own item or they share a frosted capsule (iOS 26+).
148+
private func buttons(in items: [UIBarButtonItem]?) -> [UIButton] {
149+
(items ?? []).compactMap(\.customView).flatMap { buttons(in: $0) }
150+
}
151+
152+
private func buttons(in view: UIView) -> [UIButton] {
153+
if let button = view as? UIButton {
154+
return [button]
155+
}
156+
return view.subviews.flatMap { buttons(in: $0) }
157+
}
158+
159+
/// The width the toolbar gives a button: its pinned width on a frosted capsule (iOS 26+), or its frame.
160+
private func width(of button: UIButton) -> CGFloat {
161+
let pinned = button.constraints.first { $0.firstAttribute == .width && $0.secondItem == nil }
162+
return pinned?.constant ?? button.frame.width
163+
}
164+
}

‎WordPress/Classes/Plurals.xcstrings‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,13 +388,13 @@
388388
"plural": {
389389
"one": {
390390
"stringUnit": {
391-
"state": "needs_review",
391+
"state": "translated",
392392
"value": "%1$d volta a settimana"
393393
}
394394
},
395395
"other": {
396396
"stringUnit": {
397-
"state": "needs_review",
397+
"state": "translated",
398398
"value": "%1$d volte a settimana"
399399
}
400400
}
@@ -1090,13 +1090,13 @@
10901090
"plural": {
10911091
"one": {
10921092
"stringUnit": {
1093-
"state": "needs_review",
1093+
"state": "translated",
10941094
"value": "%1$ld parola"
10951095
}
10961096
},
10971097
"other": {
10981098
"stringUnit": {
1099-
"state": "needs_review",
1099+
"state": "translated",
11001100
"value": "%1$ld parole"
11011101
}
11021102
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
192192
configureHeader()
193193
configureRelatedPosts()
194194
configureToolbar()
195+
if #available(iOS 26, *) {
196+
// The toolbar buttons sit on their own frosted capsules (see `ReaderDetailToolbar`). With them,
197+
// the automatic edge effect draws a frosted band behind the whole toolbar on iOS 27.
198+
scrollView.bottomEdgeEffect.isHidden = true
199+
}
195200
configureNoResultsViewController()
196201
observeWebViewHeight()
197202
configureNotifications()
@@ -258,7 +263,6 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
258263
func render(_ post: ReaderPost) {
259264
configureDiscoverAttribution(post)
260265

261-
toolbar.configure(for: post, in: self)
262266
updateToolbarItems()
263267
cachedExcerpt = post.getUserProvidedExcerpt()
264268
configureHeaderView(with: post)
@@ -784,8 +788,7 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
784788

785789
private func updateToolbarItems() {
786790
guard let post else { return }
787-
let items = toolbar.createToolbarItems(for: post, in: self)
788-
setToolbarItems(items, animated: false)
791+
toolbar.configure(for: post, in: self)
789792
}
790793

791794
private func configureDiscoverAttribution(_ post: ReaderPost) {

0 commit comments

Comments
 (0)