From 550213a09646503d4b63f20373b080834f617fe1 Mon Sep 17 00:00:00 2001 From: Eduardo Toledo Date: Wed, 11 Jun 2025 22:18:00 +0200 Subject: [PATCH 1/6] Using Gravatar Quick Editor as My Profile editor --- Modules/Package.resolved | 5 +- Modules/Package.swift | 2 +- .../Me/Me Main/MeViewController.swift | 40 ++++++---- .../GravatarQuickEditorPresenter.swift | 74 ++++++++++++------- .../Me/My Profile/MyProfileHeaderView.swift | 9 ++- .../ViewRelated/Me/Views/MeHeaderView.swift | 15 ++++ .../Epilogues/EpilogueUserInfoCell.swift | 9 ++- .../Resources/en.lproj/Localizable.strings | 3 + 8 files changed, 103 insertions(+), 54 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index cbe798cb19c7..57fe0d56480b 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "43fbce57dedae27ed83f0ebd59ff2f81e3f0deffd1a163762504793d8642905b", + "originHash" : "5caf5fff0a051bd0d7157befd768b607d7cd75a05df68de3b3a87915bfd55bd8", "pins" : [ { "identity" : "alamofire", @@ -131,8 +131,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/Gravatar-SDK-iOS", "state" : { - "revision" : "693616b9fb95432ac25ebb1fc3194767c1bd3904", - "version" : "3.2.0" + "revision" : "dc61d00e4b3e5a75b254cc353b944c403ac3d1ad" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index ef20d1152e0f..1461a4bddff1 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), .package(url: "https://github.com/Automattic/Automattic-Tracks-iOS", from: "3.5.2"), .package(url: "https://github.com/Automattic/AutomatticAbout-swift", from: "1.1.5"), - .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", from: "3.2.0"), + .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", revision: "dc61d00e4b3e5a75b254cc353b944c403ac3d1ad"), .package(url: "https://github.com/Automattic/Gridicons-iOS", branch: "develop"), .package(url: "https://github.com/Automattic/ScreenObject", from: "0.3.0"), .package(url: "https://github.com/buildkite/test-collector-swift", from: "0.3.0"), diff --git a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift index 49afcd107c0f..8283593a3065 100644 --- a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift @@ -3,6 +3,7 @@ import BuildSettingsKit import WordPressData import WordPressShared import AutomatticAbout +import GravatarUI public class MeViewController: UITableViewController { var handler: ImmuTableViewHandler! @@ -37,6 +38,7 @@ public class MeViewController: UITableViewController { /// considered to be ` .compact` size class, so it has to be invoked manually. headerView.configureHorizontalMode() } + headerView.delegate = self ImmuTable.registerRows([ VerifyEmailRow.self, @@ -130,7 +132,7 @@ public class MeViewController: UITableViewController { icon: UIImage(named: "site-menu-people")?.withRenderingMode(.alwaysTemplate), tintColor: .label, accessoryType: accessoryType, - action: pushMyProfile(), + action: presentGravatarAboutEditorAction(), accessibilityIdentifier: "myProfile") let qrLogin = NavigationItemRow( @@ -251,24 +253,24 @@ public class MeViewController: UITableViewController { // MARK: - Actions - fileprivate var myProfileViewController: UIViewController? { - guard let account = self.defaultAccount() else { - let error = "Tried to push My Profile without a default account. This shouldn't happen" - assertionFailure(error) - DDLogError("\(error)") - return nil + fileprivate func presentGravatarAboutEditorAction() -> ImmuTableAction { + return { [unowned self] row in + presentGravatarQuickEditor(scope: .aboutEditor(.init( + presentationStyle: .large(), + fields: [.displayName, .aboutMe, .firstName, .lastName] + ))) } - - return MyProfileViewController(account: account) } - fileprivate func pushMyProfile() -> ImmuTableAction { - return { [unowned self] row in - if let myProfileViewController = self.myProfileViewController { - WPAppAnalytics.track(.openedMyProfile) - self.showOrPushController(myProfileViewController) - } + fileprivate func presentGravatarQuickEditor(scope: QuickEditorScopeOption) { + let presenter = GravatarQuickEditorPresenter() { [weak self] in + self?.refreshAccountDetailsAndSettings() } + + presenter?.presentQuickEditor( + on: self, + scope: scope + ) } fileprivate func pushAccountSettings() -> ImmuTableAction { @@ -634,6 +636,14 @@ extension MeViewController { } } +// MARK: - Header avatar tap handler (MeHeaderViewDelegate) + +extension MeViewController: MeHeaderViewDelegate { + func meHeaderViewDidTapOnIconView(_ view: MeHeaderView) { + presentGravatarQuickEditor(scope: .avatarPicker()) + } +} + private enum Strings { static let submitFeedback = NSLocalizedString("meMenu.submitFeedback", value: "Send Feedback", comment: "Me tab menu items") } diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/GravatarQuickEditorPresenter.swift b/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/GravatarQuickEditorPresenter.swift index 7cc00218d39c..6af9a05e684b 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/GravatarQuickEditorPresenter.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/Gravatar/GravatarQuickEditorPresenter.swift @@ -10,54 +10,74 @@ struct GravatarQuickEditorPresenter { let authToken: String let emailVerificationStatus: WPAccount.VerificationStatus - init?(email: String) { + let onAccountUpdated: (() -> Void)? + + init?(onAccountUpdated: (() -> Void)? = nil) { let context = ContextManager.shared.mainContext - guard let account = try? WPAccount.lookupDefaultWordPressComAccount(in: context), let authToken = account.authToken else { + guard + let account = try? WPAccount.lookupDefaultWordPressComAccount(in: context), + let authToken = account.authToken, + let email = account.email + else { return nil } self.email = email self.authToken = authToken self.emailVerificationStatus = account.verificationStatus + self.onAccountUpdated = onAccountUpdated } - func presentQuickEditor(on presentingViewController: UIViewController) { + func presentQuickEditor(on presentingViewController: UIViewController, scope: QuickEditorScopeOption) { guard emailVerificationStatus == .verified else { - let alert = UIAlertController( - title: nil, - message: NSLocalizedString( - "avatar.update.email.verification.required", - value: "To update your avatar, you need to verify your email address first.", - comment: "An error message displayed when attempting to update an avatar while the user's email address is not verified." - ), - preferredStyle: .alert - ) - alert.addAction(UIAlertAction(title: SharedStrings.Button.ok, style: .default)) - presentingViewController.present(alert, animated: true) + presentAlert(on: presentingViewController) return } let presenter = QuickEditorPresenter( email: Email(email), - scope: .avatarPicker(AvatarPickerConfiguration(contentLayout: .horizontal())), - configuration: .init( - interfaceStyle: presentingViewController.traitCollection.userInterfaceStyle - ), + scopeOption: scope, token: authToken ) presenter.present( in: presentingViewController, - onAvatarUpdated: { - AuthenticatorAnalyticsTracker.shared.track(click: .selectAvatar) - Task { - // Purge the cache otherwise the old avatars remain around. - await ImageDownloader.shared.clearURLSessionCache() - await ImageDownloader.shared.clearMemoryCache() - NotificationCenter.default.post(name: .GravatarQEAvatarUpdateNotification, - object: self, - userInfo: [GravatarQEAvatarUpdateNotificationKeys.email.rawValue: email]) + onUpdate: { update in + switch update { + case is QuickEditorUpdate.Avatar: + onAvatarUpdate() + case is QuickEditorUpdate.AboutInfo: + onAccountUpdated?() + default: break } }, onDismiss: { // No op. } ) } + + private func presentAlert(on presentingViewController: UIViewController) { + let alert = UIAlertController( + title: nil, + message: NSLocalizedString( + "profile.update.email.verification.required", + value: "To update your profile, you need to verify your email address first.", + comment: "An error message displayed when attempting to update their profile while the user's email address is not verified." + ), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: SharedStrings.Button.ok, style: .default)) + presentingViewController.present(alert, animated: true) + } + + private func onAvatarUpdate() { + AuthenticatorAnalyticsTracker.shared.track(click: .selectAvatar) + Task { + // Purge the cache otherwise the old avatars remain around. + await ImageDownloader.shared.clearURLSessionCache() + await ImageDownloader.shared.clearMemoryCache() + NotificationCenter.default.post( + name: .GravatarQEAvatarUpdateNotification, + object: self, + userInfo: [GravatarQEAvatarUpdateNotificationKeys.email.rawValue: email] + ) + } + } } diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift b/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift index 85851651a0f4..730a086922b0 100644 --- a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift @@ -104,9 +104,10 @@ class MyProfileHeaderView: UITableViewHeaderFooterView, NibLoadable { } @objc private func gravatarButtonTapped() { - guard let email = gravatarEmail, - let presenter = GravatarQuickEditorPresenter(email: email), - let presentingViewController else { return } - presenter.presentQuickEditor(on: presentingViewController) + guard + let presenter = GravatarQuickEditorPresenter(), + let presentingViewController + else { return } + presenter.presentQuickEditor(on: presentingViewController, scope: .avatarPicker()) } } diff --git a/WordPress/Classes/ViewRelated/Me/Views/MeHeaderView.swift b/WordPress/Classes/ViewRelated/Me/Views/MeHeaderView.swift index d4339f101e86..4c572b97805a 100644 --- a/WordPress/Classes/ViewRelated/Me/Views/MeHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Me/Views/MeHeaderView.swift @@ -1,12 +1,18 @@ import UIKit import WordPressUI +protocol MeHeaderViewDelegate: NSObjectProtocol { + func meHeaderViewDidTapOnIconView(_ view: MeHeaderView) +} + final class MeHeaderView: UIView { private let iconView = UIImageView() private let titleLabel = UILabel() private let detailsLabel = UILabel() private var viewModel: MeHeaderViewModel? + weak var delegate: MeHeaderViewDelegate? + private lazy var stackView = UIStackView( axis: .vertical, alignment: .center, @@ -44,6 +50,15 @@ final class MeHeaderView: UIView { $0.priority = UILayoutPriority(999) } NotificationCenter.default.addObserver(self, selector: #selector(refreshAvatar), name: .GravatarQEAvatarUpdateNotification, object: nil) + + iconView.isUserInteractionEnabled = true + iconView.addGestureRecognizer( + UITapGestureRecognizer(target: self, action: #selector(onIconViewTap)) + ) + } + + @objc func onIconViewTap() { + delegate?.meHeaderViewDidTapOnIconView(self) } required init?(coder: NSCoder) { diff --git a/WordPress/Classes/ViewRelated/NUX/Views/Epilogues/EpilogueUserInfoCell.swift b/WordPress/Classes/ViewRelated/NUX/Views/Epilogues/EpilogueUserInfoCell.swift index 5f8a8ad587a8..474e608a76a1 100644 --- a/WordPress/Classes/ViewRelated/NUX/Views/Epilogues/EpilogueUserInfoCell.swift +++ b/WordPress/Classes/ViewRelated/NUX/Views/Epilogues/EpilogueUserInfoCell.swift @@ -87,10 +87,11 @@ class EpilogueUserInfoCell: UITableViewCell, NibLoadable { } @objc private func gravatarButtonTapped() { - guard let email, - let presenter = GravatarQuickEditorPresenter(email: email), - let viewController else { return } - presenter.presentQuickEditor(on: viewController) + guard + let presenter = GravatarQuickEditorPresenter(), + let viewController + else { return } + presenter.presentQuickEditor(on: viewController, scope: .avatarPicker()) } private func downloadGravatar(forceRefresh: Bool = false) { diff --git a/WordPress/Resources/en.lproj/Localizable.strings b/WordPress/Resources/en.lproj/Localizable.strings index aff12c132195..2098a8b67da3 100644 --- a/WordPress/Resources/en.lproj/Localizable.strings +++ b/WordPress/Resources/en.lproj/Localizable.strings @@ -1030,6 +1030,9 @@ /* An error message displayed when attempting to update an avatar while the user's email address is not verified. */ "avatar.update.email.verification.required" = "To update your avatar, you need to verify your email address first."; +/* An error message displayed when attempting to update their profile while the user's email address is not verified. */ +"profile.update.email.verification.required" = "To update your profile, you need to verify your email address first."; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "Unable to load the image. Please choose a different one or try again later."; From 43362a2485147e96398654483a4b112ca1a2d084 Mon Sep 17 00:00:00 2001 From: Eduardo Toledo Date: Wed, 11 Jun 2025 22:22:34 +0200 Subject: [PATCH 2/6] Update Gravatar SDK reference --- Modules/Package.resolved | 4 ++-- Modules/Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 57fe0d56480b..ac106a2b8b91 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5caf5fff0a051bd0d7157befd768b607d7cd75a05df68de3b3a87915bfd55bd8", + "originHash" : "15e3512b0a5b000ae63187acdab2fbf0d502b0799ec3f6a0f0e2bd253771eee8", "pins" : [ { "identity" : "alamofire", @@ -131,7 +131,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/Gravatar-SDK-iOS", "state" : { - "revision" : "dc61d00e4b3e5a75b254cc353b944c403ac3d1ad" + "revision" : "41e2fd52a0e5ea2693a9b953381cef09e3091ab3" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 1461a4bddff1..b4c35ce6e5f9 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), .package(url: "https://github.com/Automattic/Automattic-Tracks-iOS", from: "3.5.2"), .package(url: "https://github.com/Automattic/AutomatticAbout-swift", from: "1.1.5"), - .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", revision: "dc61d00e4b3e5a75b254cc353b944c403ac3d1ad"), + .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", revision: "41e2fd52a0e5ea2693a9b953381cef09e3091ab3"), .package(url: "https://github.com/Automattic/Gridicons-iOS", branch: "develop"), .package(url: "https://github.com/Automattic/ScreenObject", from: "0.3.0"), .package(url: "https://github.com/buildkite/test-collector-swift", from: "0.3.0"), From 890df28d9e3dc402a5414f641c1aa85a2a0e52a1 Mon Sep 17 00:00:00 2001 From: Eduardo Toledo Date: Fri, 13 Jun 2025 17:39:47 +0200 Subject: [PATCH 3/6] Update Gravatar version --- Modules/Package.resolved | 4 ++-- Modules/Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index ac106a2b8b91..8067b5140c6a 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "15e3512b0a5b000ae63187acdab2fbf0d502b0799ec3f6a0f0e2bd253771eee8", + "originHash" : "3b803b29d58d0ef94f89e6ae349a0be30ee2911c0cb07046ce4b3163ae42ddcb", "pins" : [ { "identity" : "alamofire", @@ -131,7 +131,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/Gravatar-SDK-iOS", "state" : { - "revision" : "41e2fd52a0e5ea2693a9b953381cef09e3091ab3" + "revision" : "ce04275c6237131576f424a215197bfaeaefc416" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index b4c35ce6e5f9..3955e02b360d 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), .package(url: "https://github.com/Automattic/Automattic-Tracks-iOS", from: "3.5.2"), .package(url: "https://github.com/Automattic/AutomatticAbout-swift", from: "1.1.5"), - .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", revision: "41e2fd52a0e5ea2693a9b953381cef09e3091ab3"), + .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", revision: "ce04275c6237131576f424a215197bfaeaefc416"), .package(url: "https://github.com/Automattic/Gridicons-iOS", branch: "develop"), .package(url: "https://github.com/Automattic/ScreenObject", from: "0.3.0"), .package(url: "https://github.com/buildkite/test-collector-swift", from: "0.3.0"), From 86e12a749f860de6765cff1bfa2c22ee25b4fd7b Mon Sep 17 00:00:00 2001 From: Eduardo Toledo Date: Fri, 13 Jun 2025 17:40:02 +0200 Subject: [PATCH 4/6] Using Avatar & About scopes on Gravatar Quick Editor --- .../ViewRelated/Me/Me Main/MeViewController.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift index 8283593a3065..3489b1bb72f7 100644 --- a/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift +++ b/WordPress/Classes/ViewRelated/Me/Me Main/MeViewController.swift @@ -255,14 +255,17 @@ public class MeViewController: UITableViewController { fileprivate func presentGravatarAboutEditorAction() -> ImmuTableAction { return { [unowned self] row in - presentGravatarQuickEditor(scope: .aboutEditor(.init( - presentationStyle: .large(), - fields: [.displayName, .aboutMe, .firstName, .lastName] - ))) + presentGravatarQuickEditor(initialPage: .aboutEditor) } } - fileprivate func presentGravatarQuickEditor(scope: QuickEditorScopeOption) { + fileprivate func presentGravatarQuickEditor(initialPage: AvatarPickerAndAboutEditorConfiguration.Page) { + let scope = QuickEditorScopeOption.avatarPickerAndAboutInfoEditor(.init( + contentLayout: .horizontal(), + fields: [.displayName, .aboutMe, .firstName, .lastName], + initialPage: initialPage + )) + let presenter = GravatarQuickEditorPresenter() { [weak self] in self?.refreshAccountDetailsAndSettings() } @@ -640,7 +643,7 @@ extension MeViewController { extension MeViewController: MeHeaderViewDelegate { func meHeaderViewDidTapOnIconView(_ view: MeHeaderView) { - presentGravatarQuickEditor(scope: .avatarPicker()) + presentGravatarQuickEditor(initialPage: .avatarPicker) } } From cef3a010eaf14e0e83145d1d9e49714dc210d477 Mon Sep 17 00:00:00 2001 From: Eduardo Toledo Date: Fri, 13 Jun 2025 17:52:51 +0200 Subject: [PATCH 5/6] Removing types not longer used --- .../Me/My Profile/MyProfileHeaderView.swift | 113 ---------- .../Me/My Profile/MyProfileHeaderView.xib | 54 ----- .../My Profile/MyProfileViewController.swift | 213 ------------------ WordPress/WordPress.xcodeproj/project.pbxproj | 20 +- 4 files changed, 10 insertions(+), 390 deletions(-) delete mode 100644 WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift delete mode 100644 WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.xib delete mode 100644 WordPress/Classes/ViewRelated/Me/My Profile/MyProfileViewController.swift diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift b/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift deleted file mode 100644 index 730a086922b0..000000000000 --- a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.swift +++ /dev/null @@ -1,113 +0,0 @@ -import Foundation -import WordPressUI - -class MyProfileHeaderView: UITableViewHeaderFooterView, NibLoadable { - // MARK: - Public Properties and Outlets - @IBOutlet var gravatarImageView: CircularImageView! - @IBOutlet var gravatarButton: UIButton! - weak var presentingViewController: UIViewController? - // A fake button displayed on top of gravatarImageView. - let imageViewButton = UIButton(type: .system) - - let activityIndicator = UIActivityIndicatorView(style: .medium) - var showsActivityIndicator: Bool { - get { - return activityIndicator.isAnimating - } - set { - if newValue == true { - activityIndicator.startAnimating() - } else { - activityIndicator.stopAnimating() - } - } - } - var gravatarEmail: String? = nil { - didSet { - if gravatarEmail != nil { - downloadAvatar() - } - } - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - // MARK: - Convenience Initializers - override func awakeFromNib() { - super.awakeFromNib() - - // Make sure the Outlets are loaded - assert(gravatarImageView != nil) - assert(gravatarButton != nil) - - configureActivityIndicator() - configureGravatarImageView() - configureGravatarButton() - } - - private func downloadAvatar(forceRefresh: Bool = false) { - if let email = gravatarEmail { - gravatarImageView.downloadGravatar(for: email, gravatarRating: .x, forceRefresh: forceRefresh) - } - } - - @objc private func refreshAvatar(_ notification: Foundation.Notification) { - guard let email = gravatarEmail, - notification.userInfoHasEmail(email) else { return } - downloadAvatar(forceRefresh: true) - } - - /// Overrides the current Gravatar Image (set via Email) with a given image reference. - /// Plus, the internal image cache is updated, to prevent undesired glitches upon refresh. - /// - func overrideGravatarImage(_ image: UIImage) { - guard !RemoteFeatureFlag.gravatarQuickEditor.enabled() else { return } - gravatarImageView.image = image - - // Note: - // We need to update the image internal cache. Otherwise, any upcoming query to refresh the gravatar - // might return the cached (outdated) image, and the UI will end up in an inconsistent state. - // - if let email = gravatarEmail { - gravatarImageView.overrideGravatarImageCache(image, gravatarRating: ObjcGravatarRating.x, email: email) - gravatarImageView.updateGravatar(image: image, email: email) - } - } - - private func configureActivityIndicator() { - activityIndicator.hidesWhenStopped = true - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - } - - private func configureGravatarImageView() { - gravatarImageView.shouldRoundCorners = true - gravatarImageView.addSubview(activityIndicator) - gravatarImageView.pinSubviewAtCenter(activityIndicator) - layoutIfNeeded() - - gravatarImageView.addSubview(imageViewButton) - imageViewButton.translatesAutoresizingMaskIntoConstraints = false - imageViewButton.pinSubviewToAllEdges(gravatarImageView) - if RemoteFeatureFlag.gravatarQuickEditor.enabled() { - imageViewButton.addTarget(self, action: #selector(gravatarButtonTapped), for: .touchUpInside) - NotificationCenter.default.addObserver(self, selector: #selector(refreshAvatar), name: .GravatarQEAvatarUpdateNotification, object: nil) - } - } - - private func configureGravatarButton() { - gravatarButton.tintColor = UIAppColor.primary - if RemoteFeatureFlag.gravatarQuickEditor.enabled() { - gravatarButton.addTarget(self, action: #selector(gravatarButtonTapped), for: .touchUpInside) - } - } - - @objc private func gravatarButtonTapped() { - guard - let presenter = GravatarQuickEditorPresenter(), - let presentingViewController - else { return } - presenter.presentQuickEditor(on: presentingViewController, scope: .avatarPicker()) - } -} diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.xib b/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.xib deleted file mode 100644 index 5dc2f420377b..000000000000 --- a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileHeaderView.xib +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileViewController.swift b/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileViewController.swift deleted file mode 100644 index 9ce9caee67b9..000000000000 --- a/WordPress/Classes/ViewRelated/Me/My Profile/MyProfileViewController.swift +++ /dev/null @@ -1,213 +0,0 @@ -import UIKit -import WordPressShared - -func MyProfileViewController(account: WPAccount) -> ImmuTableViewController? { - guard let api = account.wordPressComRestApi, let userID = account.userID else { - return nil - } - - let service = AccountSettingsService(userID: userID.intValue, api: api) - let headerView = makeHeaderView(account: account) - return MyProfileViewController(account: account, service: service, headerView: headerView) -} - -func MyProfileViewController(account: WPAccount, service: AccountSettingsService, headerView: MyProfileHeaderView) -> ImmuTableViewController { - let controller = MyProfileController(account: account, service: service, headerView: headerView) - let viewController = ImmuTableViewController(controller: controller, style: .insetGrouped) - controller.tableView = viewController.tableView - headerView.presentingViewController = viewController - if !RemoteFeatureFlag.gravatarQuickEditor.enabled() { - let menuController = AvatarMenuController(viewController: viewController) - menuController.onAvatarSelected = { [weak controller, weak viewController] image in - guard let controller, let viewController else { return } - controller.uploadGravatarImage(image, presenter: viewController) - } - objc_setAssociatedObject(viewController, &associateObjectKey, menuController, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - - for button in [headerView.imageViewButton, headerView.gravatarButton] as [UIButton] { - button.menu = menuController.makeMenu() - button.showsMenuAsPrimaryAction = true - } - } - viewController.tableView.tableHeaderView = headerView - return viewController -} - -private var associateObjectKey: UInt8 = 0 - -private func makeHeaderView(account: WPAccount) -> MyProfileHeaderView { - let defaultImage = UIImage.gravatarPlaceholderImage - let headerView = MyProfileHeaderView.loadFromNib() - if let email = account.email { - headerView.gravatarEmail = email - } else { - headerView.gravatarImageView.image = defaultImage - } - - if headerView.gravatarImageView.image == defaultImage { - headerView.gravatarButton.setTitle(NSLocalizedString("Add a Profile Photo", comment: "Add a profile photo to Me > My Profile"), for: .normal) - } else { - headerView.gravatarButton.setTitle(NSLocalizedString("Update Profile Photo", comment: "Update profile photo in Me > My Profile"), for: .normal) - } - return headerView -} - -/// MyProfileController requires the `presenter` to be set before using. -/// To avoid problems, it's marked private and should only be initialized using the -/// `MyProfileViewController` factory functions. -private class MyProfileController: SettingsController { - var trackingKey: String { - return "my_profile" - } - weak var tableView: UITableView? - - // MARK: - Private Properties - - fileprivate var headerView: MyProfileHeaderView - fileprivate var gravatarUploadInProgress = false { - didSet { - headerView.showsActivityIndicator = gravatarUploadInProgress - headerView.isUserInteractionEnabled = !gravatarUploadInProgress - } - } - - // MARK: - ImmuTableController - - let title = NSLocalizedString("My Profile", comment: "My Profile view title") - - var immuTableRows: [ImmuTableRow.Type] { - return [EditableTextRow.self, - GravatarInfoRow.self, - ExternalLinkButtonRow.self] - } - - // MARK: - Initialization - - let account: WPAccount - let service: AccountSettingsService - var settings: AccountSettings? { - didSet { - NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: ImmuTableViewController.modelChangedNotification), object: nil) - } - } - var noticeMessage: String? { - didSet { - NotificationCenter.default.post(name: Foundation.Notification.Name(rawValue: ImmuTableViewController.modelChangedNotification), object: nil) - } - } - - init(account: WPAccount, service: AccountSettingsService, headerView: MyProfileHeaderView) { - self.account = account - self.service = service - self.headerView = headerView - let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, selector: #selector(MyProfileController.loadStatus), name: NSNotification.Name.AccountSettingsServiceRefreshStatusChanged, object: nil) - notificationCenter.addObserver(self, selector: #selector(MyProfileController.loadSettings), name: NSNotification.Name.AccountSettingsChanged, object: nil) - } - - func refreshModel() { - service.refreshSettings() - } - - @objc func loadStatus() { - noticeMessage = service.status.errorMessage - } - - @objc func loadSettings() { - settings = service.settings - } - - // MARK: - ImmuTableViewController - - func tableViewModelWithPresenter(_ presenter: ImmuTablePresenter) -> ImmuTable { - return mapViewModel(settings, presenter: presenter) - } - - // MARK: - Model mapping - - func mapViewModel(_ settings: AccountSettings?, presenter: ImmuTablePresenter) -> ImmuTable { - let firstNameRow = EditableTextRow( - title: NSLocalizedString("First Name", comment: "My Profile first name label"), - value: settings?.firstName ?? "", - action: presenter.push(editText(AccountSettingsChange.firstName, service: service)), - fieldName: "first_name") - - let lastNameRow = EditableTextRow( - title: NSLocalizedString("Last Name", comment: "My Profile last name label"), - value: settings?.lastName ?? "", - action: presenter.push(editText(AccountSettingsChange.lastName, service: service)), - fieldName: "last_name") - - let displayNameRow = EditableTextRow( - title: NSLocalizedString("Display Name", comment: "My Profile display name label"), - value: settings?.displayName ?? "", - action: presenter.push(editText(AccountSettingsChange.displayName, service: service)), - fieldName: "display_name") - - let aboutMeRow = EditableTextRow( - title: NSLocalizedString("About Me", comment: "My Profile 'About me' label"), - value: settings?.aboutMe ?? "", - action: presenter.push(editMultilineText(AccountSettingsChange.aboutMe, - hint: NSLocalizedString("Tell us a bit about you.", comment: "My Profile 'About me' hint text"), - service: service)), - fieldName: "about_me") - - let gravatarInfoRow = GravatarInfoRow(title: GravatarInfoConstants.title, - description: GravatarInfoConstants.description, - action: nil) - let gravatarLinkRow = ExternalLinkButtonRow(title: GravatarInfoConstants.linkText, - accessibilityHint: GravatarInfoConstants.gravatarLinkAccessibilityHint, - action: visitGravatarWebsiteAction(), - accessibilityIdentifier: "visit-gravatar-website-button") - - return ImmuTable(sections: [ - ImmuTableSection(rows: [ - firstNameRow, - lastNameRow, - displayNameRow, - aboutMeRow - ]), - ImmuTableSection(rows: [ - gravatarInfoRow, - gravatarLinkRow - ]) - ]) - } - - // MARK: - Helpers - - fileprivate func uploadGravatarImage(_ newGravatar: UIImage, presenter: ImmuTableViewController) { - guard let account = defaultAccount() else { - return - } - - WPAppAnalytics.track(.gravatarUploaded) - - gravatarUploadInProgress = true - headerView.overrideGravatarImage(newGravatar) - - let service = GravatarService() - service.uploadImage(newGravatar, forAccount: account) { [weak self] error in - DispatchQueue.main.async(execute: { - self?.gravatarUploadInProgress = false - self?.refreshModel() - }) - } - } - - fileprivate func visitGravatarWebsiteAction() -> ImmuTableAction { - return { [weak self] row in - guard let url = URL(string: GravatarInfoConstants.gravatarLink) else { - return - } - self?.tableView?.deselectSelectedRowWithAnimation(true) - UIApplication.shared.open(url) - } - } - - // FIXME: (@koke 2015-12-17) Not cool. Let's stop passing managed objects - // and initializing stuff with safer values like userID - fileprivate func defaultAccount() -> WPAccount? { - try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext) - } -} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 843a15cd880f..0bf9bf2e395d 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -151,8 +151,8 @@ 4ABCAB2A2DE52E80005A6B84 /* Secrets-JetpackDraftActionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCAB272DE52E6B005A6B84 /* Secrets-JetpackDraftActionExtension.swift */; }; 4ABCAB2B2DE52E86005A6B84 /* Secrets-JetpackShareExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCAB282DE52E6B005A6B84 /* Secrets-JetpackShareExtension.swift */; }; 4ABCAB2E2DE53092005A6B84 /* Secrets-JetpackNotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCAB2D2DE53092005A6B84 /* Secrets-JetpackNotificationServiceExtension.swift */; }; - 4ABCAB3A2DE533A5005A6B84 /* Secrets-WordPressNotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCAB392DE533A5005A6B84 /* Secrets-WordPressNotificationServiceExtension.swift */; }; 4ABCAB332DE53168005A6B84 /* Secrets-JetpackIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCAB322DE53168005A6B84 /* Secrets-JetpackIntents.swift */; }; + 4ABCAB3A2DE533A5005A6B84 /* Secrets-WordPressNotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ABCAB392DE533A5005A6B84 /* Secrets-WordPressNotificationServiceExtension.swift */; }; 4AC9545A2DE51FE40095EA51 /* Secrets-JetpackStatsWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC954592DE51FE40095EA51 /* Secrets-JetpackStatsWidgets.swift */; }; 4AC9F8182DE528E40095EA51 /* Secrets-WordPressShareExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AC9F8172DE528E40095EA51 /* Secrets-WordPressShareExtension.swift */; }; 4AD953C72C21451700D0EEFA /* WordPressAuthenticator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AD953B42C21451700D0EEFA /* WordPressAuthenticator.framework */; }; @@ -824,8 +824,8 @@ 4ABCAB272DE52E6B005A6B84 /* Secrets-JetpackDraftActionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-JetpackDraftActionExtension.swift"; path = "../Secrets/Secrets-JetpackDraftActionExtension.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 4ABCAB282DE52E6B005A6B84 /* Secrets-JetpackShareExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-JetpackShareExtension.swift"; path = "../Secrets/Secrets-JetpackShareExtension.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 4ABCAB2D2DE53092005A6B84 /* Secrets-JetpackNotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-JetpackNotificationServiceExtension.swift"; path = "../Secrets/Secrets-JetpackNotificationServiceExtension.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; - 4ABCAB392DE533A5005A6B84 /* Secrets-WordPressNotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-WordPressNotificationServiceExtension.swift"; path = "../Secrets/Secrets-WordPressNotificationServiceExtension.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 4ABCAB322DE53168005A6B84 /* Secrets-JetpackIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-JetpackIntents.swift"; path = "../Secrets/Secrets-JetpackIntents.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4ABCAB392DE533A5005A6B84 /* Secrets-WordPressNotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-WordPressNotificationServiceExtension.swift"; path = "../Secrets/Secrets-WordPressNotificationServiceExtension.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 4AC954592DE51FE40095EA51 /* Secrets-JetpackStatsWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-JetpackStatsWidgets.swift"; path = "../Secrets/Secrets-JetpackStatsWidgets.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 4AC9F8172DE528E40095EA51 /* Secrets-WordPressShareExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Secrets-WordPressShareExtension.swift"; path = "../Secrets/Secrets-WordPressShareExtension.swift"; sourceTree = BUILT_PRODUCTS_DIR; }; 4AD953B42C21451700D0EEFA /* WordPressAuthenticator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WordPressAuthenticator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1206,19 +1206,19 @@ ); target = 80F6D01F28EE866A00953C1A /* JetpackNotificationServiceExtension */; }; - 4ABCAB382DE5333C005A6B84 /* Exceptions for "Classes" folder in "WordPressNotificationServiceExtension" target */ = { + 4ABCAB352DE531B6005A6B84 /* Exceptions for "Classes" folder in "JetpackIntents" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( "System/ApiCredentials+BuildSecrets.swift", ); - target = 7358E6B7210BD318002323EB /* WordPressNotificationServiceExtension */; + target = 0107E13828FE9DB200DE87DB /* JetpackIntents */; }; - 4ABCAB352DE531B6005A6B84 /* Exceptions for "Classes" folder in "JetpackIntents" target */ = { + 4ABCAB382DE5333C005A6B84 /* Exceptions for "Classes" folder in "WordPressNotificationServiceExtension" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( "System/ApiCredentials+BuildSecrets.swift", ); - target = 0107E13828FE9DB200DE87DB /* JetpackIntents */; + target = 7358E6B7210BD318002323EB /* WordPressNotificationServiceExtension */; }; 4AC9CF442DE5228C0095EA51 /* Exceptions for "Classes" folder in "JetpackStatsWidgets" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; @@ -3292,7 +3292,7 @@ shellPath = /bin/sh; shellScript = "$SRCROOT/../Scripts/BuildPhases/GenerateCredentials.sh\n"; }; - 4ABCAB362DE532E7005A6B84 /* Generate Credentials */ = { + 4ABCAB312DE530F1005A6B84 /* Generate Credentials */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3306,13 +3306,13 @@ outputFileListPaths = ( ); outputPaths = ( - "$(BUILD_DIR)/Secrets/Secrets-WordPressNotificationServiceExtension.swift", + "$(BUILD_DIR)/Secrets/Secrets-JetpackIntents.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "$SRCROOT/../Scripts/BuildPhases/GenerateCredentials.sh\n"; }; - 4ABCAB312DE530F1005A6B84 /* Generate Credentials */ = { + 4ABCAB362DE532E7005A6B84 /* Generate Credentials */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -3326,7 +3326,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(BUILD_DIR)/Secrets/Secrets-JetpackIntents.swift", + "$(BUILD_DIR)/Secrets/Secrets-WordPressNotificationServiceExtension.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; From 4f9370d6a4c65544ad575c0faa2d78ae5850d4be Mon Sep 17 00:00:00 2001 From: Eduardo Toledo Date: Mon, 23 Jun 2025 20:16:14 +0200 Subject: [PATCH 6/6] Update Gravatar SDK to v3.4.0 --- Modules/Package.resolved | 5 +++-- Modules/Package.swift | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index 8067b5140c6a..167da10924b5 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3b803b29d58d0ef94f89e6ae349a0be30ee2911c0cb07046ce4b3163ae42ddcb", + "originHash" : "34274d2219ab44827498e28747fd717bc112ee0571b21bf82e2b7811b04610ad", "pins" : [ { "identity" : "alamofire", @@ -131,7 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/Gravatar-SDK-iOS", "state" : { - "revision" : "ce04275c6237131576f424a215197bfaeaefc416" + "revision" : "10ea47af09875f830adf63bc72bf5fb686bffc05", + "version" : "3.4.0" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 3955e02b360d..619bac27b53b 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.0.0"), .package(url: "https://github.com/Automattic/Automattic-Tracks-iOS", from: "3.5.2"), .package(url: "https://github.com/Automattic/AutomatticAbout-swift", from: "1.1.5"), - .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", revision: "ce04275c6237131576f424a215197bfaeaefc416"), + .package(url: "https://github.com/Automattic/Gravatar-SDK-iOS", from: "3.4.0"), .package(url: "https://github.com/Automattic/Gridicons-iOS", branch: "develop"), .package(url: "https://github.com/Automattic/ScreenObject", from: "0.3.0"), .package(url: "https://github.com/buildkite/test-collector-swift", from: "0.3.0"),