Skip to content

Commit 41b8062

Browse files
authored
Integrate Gravatar Quick Editor (#23729)
* Add remote FF gravatar_quick_editor * Add forceRefresh option for image download methods * Force refresh on gravatar update notification * Add `GravatarQuickEditorPresenter` * Display QuickEditor instead of the avatar upload menu * Move listenForGravatarChanges to upper level Add forceRefresh option * Add one more FF check * Adjust the obj-c call * Listen to changes on the MeHeaderView * Await the image download to update the cached image * Listen to gravatar changes on the signup epilogue * Add one more FF check * Separate the notification name for the QE Add email check when handling the notification `GravatarQEAvatarUpdateNotification` * Add a release note * Revert whitespace change * Use overrideImageCache` * Fix the old avatar issue * Update unit test * Move `addObserver` to viewDidLoad * Mode addObserver to init` * Add unit tests for `appendingGravatarCacheBusterParam` and handle the canonical URL as well * Fix "unused var" warning * Update release notes * Revert "Update release notes" This reverts commit f0b3eb9.
1 parent ba5f4af commit 41b8062

File tree

23 files changed

+329
-48
lines changed

23 files changed

+329
-48
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Foundation
2+
3+
public enum GravatarQEAvatarUpdateNotificationKeys: String {
4+
case email
5+
}
6+
7+
public extension NSNotification.Name {
8+
/// Gravatar Quick Editor updated the avatar
9+
static let GravatarQEAvatarUpdateNotification = NSNotification.Name(rawValue: "GravatarQEAvatarUpdateNotification")
10+
}
11+
12+
extension Foundation.Notification {
13+
public func userInfoHasEmail(_ email: String) -> Bool {
14+
guard let userInfo = userInfo,
15+
let notificationEmail = userInfo[GravatarQEAvatarUpdateNotificationKeys.email.rawValue] as? String else {
16+
return false
17+
}
18+
return email == notificationEmail
19+
}
20+
}

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
25.6
22
-----
33
* [*] [internal] Update Gravatar SDK to 3.0.0 [#23701]
4+
* [*] Use the Gravatar Quick Editor to update the avatar [#23729]
45
* [*] (Hidden under a feature flag) User Management for self-hosted sites. [#23768]
56

67
25.5

WordPress/Classes/Extensions/URL+Helpers.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,15 @@ extension URL {
151151
components?.queryItems = queryItems
152152
return components?.url ?? self
153153
}
154+
155+
/// Gravatar doesn't support "Cache-Control: none" header. So we add a random query parameter to
156+
/// bypass the backend cache and get the latest image.
157+
public func appendingGravatarCacheBusterParam() -> URL {
158+
var urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: false)
159+
if urlComponents?.queryItems == nil {
160+
urlComponents?.queryItems = []
161+
}
162+
urlComponents?.queryItems?.append(.init(name: "_", value: "\(NSDate().timeIntervalSince1970)"))
163+
return urlComponents?.url ?? self
164+
}
154165
}

WordPress/Classes/Utility/BuildInformation/RemoteFeatureFlag.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ enum RemoteFeatureFlag: Int, CaseIterable {
2727
case inAppRating
2828
case siteMonitoring
2929
case inAppUpdates
30+
case gravatarQuickEditor
3031
case dotComWebLogin
3132

3233
var defaultValue: Bool {
@@ -81,6 +82,8 @@ enum RemoteFeatureFlag: Int, CaseIterable {
8182
return false
8283
case .inAppUpdates:
8384
return false
85+
case .gravatarQuickEditor:
86+
return BuildConfiguration.current ~= [.localDeveloper, .a8cBranchTest, .a8cPrereleaseTesting]
8487
case .dotComWebLogin:
8588
return false
8689
}
@@ -139,6 +142,8 @@ enum RemoteFeatureFlag: Int, CaseIterable {
139142
return "site_monitoring"
140143
case .inAppUpdates:
141144
return "in_app_updates"
145+
case .gravatarQuickEditor:
146+
return "gravatar_quick_editor"
142147
case .dotComWebLogin:
143148
return "jp_wpcom_web_login"
144149
}
@@ -196,6 +201,8 @@ enum RemoteFeatureFlag: Int, CaseIterable {
196201
return "Site Monitoring"
197202
case .inAppUpdates:
198203
return "In-App Updates"
204+
case .gravatarQuickEditor:
205+
return "Gravatar Quick Editor"
199206
case .dotComWebLogin:
200207
return "Log in to WordPress.com from web browser"
201208
}

WordPress/Classes/Utility/Media/ImageDownloader+Gravatar.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@ import Gravatar
44

55
extension ImageDownloader {
66

7-
nonisolated func downloadGravatarImage(with email: String, completion: @escaping (UIImage?) -> Void) {
7+
nonisolated func downloadGravatarImage(with email: String, forceRefresh: Bool = false, completion: @escaping (UIImage?) -> Void) {
88

99
guard let url = AvatarURL.url(for: email) else {
1010
completion(nil)
1111
return
1212
}
1313

14-
if let cachedImage = ImageCache.shared.getImage(forKey: url.absoluteString) {
14+
if !forceRefresh, let cachedImage = ImageCache.shared.getImage(forKey: url.absoluteString) {
1515
completion(cachedImage)
1616
return
1717
}
18-
19-
downloadImage(at: url) { image, _ in
18+
var urlToDownload = url
19+
if forceRefresh {
20+
urlToDownload = url.appendingGravatarCacheBusterParam()
21+
}
22+
downloadImage(at: urlToDownload) { image, _ in
2023
DispatchQueue.main.async {
2124

2225
guard let image else {

WordPress/Classes/Utility/Media/ImageDownloader.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ actor ImageDownloader {
102102
return imageURL.absoluteString + (size.map { "?size=\($0)" } ?? "")
103103
}
104104

105+
func clearURLSessionCache() {
106+
urlSessionWithCache.configuration.urlCache?.removeAllCachedResponses()
107+
urlSession.configuration.urlCache?.removeAllCachedResponses()
108+
}
109+
110+
func clearMemoryCache() {
111+
self.cache.removeAllObjects()
112+
}
113+
105114
// MARK: - Networking
106115

107116
private func data(for request: URLRequest, options: ImageRequestOptions) async throws -> Data {

WordPress/Classes/Utility/Media/ImageViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ final class ImageViewController {
66
var downloader: ImageDownloader = .shared
77
var onStateChanged: (State) -> Void = { _ in }
88

9-
private var task: Task<Void, Never>?
9+
private(set) var task: Task<Void, Never>?
1010

1111
enum State {
1212
case loading

WordPress/Classes/Utility/Media/MemoryCache.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import WordPressUI
44

55
protocol MemoryCacheProtocol: AnyObject {
66
subscript(key: String) -> UIImage? { get set }
7+
func removeAllObjects()
78
}
89

910
/// - note: The type is thread-safe because it uses thread-safe `NSCache`.
1011
final class MemoryCache: MemoryCacheProtocol, @unchecked Sendable {
12+
1113
/// A shared image cache used by the entire system.
1214
static let shared = MemoryCache()
1315

@@ -23,6 +25,10 @@ final class MemoryCache: MemoryCacheProtocol, @unchecked Sendable {
2325
cache.removeAllObjects()
2426
}
2527

28+
func removeAllObjects() {
29+
cache.removeAllObjects()
30+
}
31+
2632
// MARK: - UIImage
2733

2834
subscript(key: String) -> UIImage? {

WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController+Me.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import Gravatar
44

55
extension BlogDetailsViewController {
66

7-
@objc func downloadGravatarImage(for row: BlogDetailsRow) {
7+
@objc func downloadGravatarImage(for row: BlogDetailsRow, forceRefresh: Bool = false) {
88
guard let email = blog.account?.email else {
99
return
1010
}
1111

12-
ImageDownloader.shared.downloadGravatarImage(with: email) { [weak self] image in
12+
ImageDownloader.shared.downloadGravatarImage(with: email, forceRefresh: forceRefresh) { [weak self] image in
1313
guard let image,
1414
let gravatarIcon = image.gravatarIcon(size: Metrics.iconSize) else {
1515
return
@@ -21,9 +21,17 @@ extension BlogDetailsViewController {
2121
}
2222

2323
@objc func observeGravatarImageUpdate() {
24+
NotificationCenter.default.addObserver(self, selector: #selector(refreshAvatar(_:)), name: .GravatarQEAvatarUpdateNotification, object: nil)
2425
NotificationCenter.default.addObserver(self, selector: #selector(updateGravatarImage(_:)), name: .GravatarImageUpdateNotification, object: nil)
2526
}
2627

28+
@objc private func refreshAvatar(_ notification: Foundation.Notification) {
29+
guard let meRow,
30+
let email = blog.account?.email,
31+
notification.userInfoHasEmail(email) else { return }
32+
downloadGravatarImage(for: meRow, forceRefresh: true)
33+
}
34+
2735
@objc private func updateGravatarImage(_ notification: Foundation.Notification) {
2836
guard let userInfo = notification.userInfo,
2937
let email = userInfo["email"] as? String,

WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,7 @@ - (BlogDetailsSection *)configurationSectionViewModel
13501350
callback:^{
13511351
[weakSelf showMe];
13521352
}];
1353-
[self downloadGravatarImageFor:row];
1353+
[self downloadGravatarImageFor:row forceRefresh: NO];
13541354
self.meRow = row;
13551355
[rows addObject:row];
13561356
}

0 commit comments

Comments
 (0)