Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension StatsFileDownloadsTimeIntervalData: StatsTimeIntervalData {
public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let fileDownloadsDict = unwrappedDays["files"] as? [[String: AnyObject]]
let fileDownloadsDict = Bamboozled.parseArray(unwrappedDays["files"])
else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ extension StatsSearchTermTimeIntervalData: StatsTimeIntervalData {
let totalSearchTerms = unwrappedDays["total_search_terms"] as? Int,
let hiddenSearchTerms = unwrappedDays["encrypted_search_terms"] as? Int,
let otherSearchTerms = unwrappedDays["other_search_terms"] as? Int,
let searchTermsDict = unwrappedDays["search_terms"] as? [[String: AnyObject]]
let searchTermsDict = Bamboozled.parseArray(unwrappedDays["search_terms"])
else {
return nil
}
Expand Down
30 changes: 30 additions & 0 deletions Modules/Sources/WordPressKit/StatsServiceRemoteV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,37 @@ extension StatsTimeIntervalData {
}
return nil
}
}

enum Bamboozled {
/// Sometimes PHP returns a dictionary with numbers as keys instead of an
/// actual array. This fixes it.
static func parseArray(_ object: AnyObject?) -> [[String: AnyObject]]? {
guard let object else {
return nil
}
if let array = object as? [[String: AnyObject]] {
return array
}
if let dictionary = object as? [String: [String: AnyObject]] {
return dictionary.sorted { lhs, rhs in
if let lhs = Int(lhs.key), let rhs = Int(rhs.key) {
return lhs < rhs
}
return lhs.key.compare(rhs.key, options: .numeric) == .orderedAscending
}.map {
$0.value
}
}
if let dictionary = object as? [Int: [String: AnyObject]] {
return dictionary.sorted { lhs, rhs in
lhs.key < rhs.key
}.map {
$0.value
}
}
return nil
}
}

// We'll bring `StatsPeriodUnit` into this file when the "old" `WPStatsServiceRemote` gets removed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ extension StatsTopAuthorsTimeIntervalData: StatsTimeIntervalData {
public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let authors = unwrappedDays["authors"] as? [[String: AnyObject]]
let authors = Bamboozled.parseArray(unwrappedDays["authors"])
else {
return nil
}
Expand All @@ -87,7 +87,7 @@ extension StatsTopAuthor {
let name = jsonDictionary["name"] as? String,
let views = jsonDictionary["views"] as? Int,
let avatar = jsonDictionary["avatar"] as? String,
let posts = jsonDictionary["posts"] as? [[String: AnyObject]]
let posts = Bamboozled.parseArray(jsonDictionary["posts"])
else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension StatsTopClicksTimeIntervalData: StatsTimeIntervalData {
public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let clicks = unwrappedDays["clicks"] as? [[String: AnyObject]]
let clicks = Bamboozled.parseArray(unwrappedDays["clicks"])
else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extension StatsTopCountryTimeIntervalData: StatsTimeIntervalData {
public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let countriesViews = unwrappedDays["views"] as? [[String: AnyObject]]
let countriesViews = Bamboozled.parseArray(unwrappedDays["views"])
else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension StatsTopPostsTimeIntervalData: StatsTimeIntervalData {
public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let posts = unwrappedDays["postviews"] as? [[String: AnyObject]]
let posts = Bamboozled.parseArray(unwrappedDays["postviews"])
else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ extension StatsTopReferrersTimeIntervalData: StatsTimeIntervalData {
public init?(date: Date, period: StatsPeriodUnit, jsonDictionary: [String: AnyObject]) {
guard
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let referrers = unwrappedDays["groups"] as? [[String: AnyObject]]
let referrers = Bamboozled.parseArray(unwrappedDays["groups"])
else {
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ extension StatsTopVideosTimeIntervalData: StatsTimeIntervalData {
let unwrappedDays = type(of: self).unwrapDaysDictionary(jsonDictionary: jsonDictionary),
let totalPlayCount = unwrappedDays["total_plays"] as? Int,
let otherPlays = unwrappedDays["other_plays"] as? Int,
let videos = unwrappedDays["plays"] as? [[String: AnyObject]]
let videos = Bamboozled.parseArray(unwrappedDays["plays"])
else {
return nil
}
Expand Down
5 changes: 5 additions & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
26.4
-----

26.3.1
------
* [*] Fix an issue with Top Posts not loading for some users [#24860]
* [*] Fix crash in Reader Article view [#24857]
* [*] Fix more minor visual glitches on iOS 26 [#24858]

26.3
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ class AbstractPostListViewController: UIViewController,
searchResultsViewController.configure(searchController, self as? InteractivePostViewDelegate)

if #available(iOS 26, *) {
navigationItem.preferredSearchBarPlacement = .integrated
if tabBarController?.isTabBarHidden == false {
navigationItem.preferredSearchBarPlacement = .integratedButton
} else {
navigationItem.preferredSearchBarPlacement = .integrated
}
}

definesPresentationContext = true
Expand Down Expand Up @@ -850,28 +854,34 @@ class AbstractPostListViewController: UIViewController,
// MARK: - Misc

private func showRefreshingIndicator() {
guard navigationItem.titleView == nil else {
return
}
// TODO: Design a better way to show it that doesn't affect the navigation bar

let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
spinner.tintColor = .secondaryLabel
spinner.transform = .init(scaleX: 0.8, y: 0.8)
if #unavailable(iOS 26) {
guard navigationItem.titleView == nil else {
return
}

let titleView = UILabel()
titleView.text = Strings.updating + "..."
titleView.font = UIFont.preferredFont(forTextStyle: .headline)
titleView.textColor = UIColor.secondaryLabel
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
spinner.tintColor = .secondaryLabel
spinner.transform = .init(scaleX: 0.8, y: 0.8)

let stack = UIStackView(arrangedSubviews: [spinner, titleView])
stack.spacing = 8
let titleView = UILabel()
titleView.text = Strings.updating + "..."
titleView.font = UIFont.preferredFont(forTextStyle: .headline)
titleView.textColor = UIColor.secondaryLabel

navigationItem.titleView = stack
let stack = UIStackView(arrangedSubviews: [spinner, titleView])
stack.spacing = 8

navigationItem.titleView = stack
}
}

private func hideRefreshingIndicator() {
navigationItem.titleView = nil
if #unavailable(iOS 26) {
navigationItem.titleView = nil
}
}

private func setFooterHidden(_ isHidden: Bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
private lazy var toolbar = ReaderDetailToolbar()
private var lastContentOffset: CGFloat = 0

private var toolbarUpdateTimer: Timer?

/// Likes summary view
private let likesSummary: ReaderDetailLikesView = .loadFromNib()

Expand Down Expand Up @@ -136,6 +138,8 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {
/// has a comment anchor fragment.
private var hasAutomaticallyTriggeredCommentAction = false

private var isToolbarHidden = false

// Reader customization model
private lazy var displaySettingStore: ReaderDisplaySettingStore = {
let store = ReaderDisplaySettingStore()
Expand Down Expand Up @@ -445,6 +449,7 @@ class ReaderDetailViewController: UIViewController, ReaderDetailView {

deinit {
scrollObserver?.invalidate()
toolbarUpdateTimer?.invalidate()
NotificationCenter.default.removeObserver(self)
}

Expand Down Expand Up @@ -900,8 +905,23 @@ extension ReaderDetailViewController: UIScrollViewDelegate {
layoutHeroView()
}

private func setNeedsToolbarHidden(_ isHidden: Bool) {
// Debounce to prevent it from quickly switching between states when
// on the edge of the scroll threshold.
toolbarUpdateTimer?.invalidate()
toolbarUpdateTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { [weak self] _ in
self?.setToolbarHidden(isHidden, animated: true)
}
}

private func setToolbarHidden(_ isHidden: Bool, animated: Bool) {
guard navigationController?.isToolbarHidden != isHidden else { return } // Important
guard scrollView.contentSize.height > view.bounds.height * 2.5 else {
return // No point in briefly hiding it
}
guard isToolbarHidden != isHidden else {
return
}
isToolbarHidden = isHidden
navigationController?.setToolbarHidden(isHidden, animated: animated)
}
}
Expand Down
4 changes: 2 additions & 2 deletions config/Version.public.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VERSION_LONG = 26.3.0.8
VERSION_SHORT = 26.3
VERSION_LONG = 26.3.1.0
VERSION_SHORT = 26.3.1