From 18fbbc46eb4cad3d5bf9aa7812cdbfc2e57b501d Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 11 Jun 2024 17:40:39 +0300 Subject: [PATCH 1/9] Add displayGravatarProfile FF --- .../Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift b/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift index 14f5d88bf4..2b158d7016 100644 --- a/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift +++ b/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift @@ -50,6 +50,9 @@ public enum FeatureFlag: String, CaseIterable { /// show UpNext tab on the main tab bar case upNextOnTabBar + /// Enhances the profile view to display more fields from the user's Gravatar profile. + case displayGravatarProfile + /// When enabled it updates the code on filter callback to use a safer method to convert unmanaged player references /// This is to fix this: https://a8c.sentry.io/share/issue/39a6d2958b674ec3b7a4d9248b4b5ffa/ case defaultPlayerFilterCallbackFix @@ -112,6 +115,8 @@ public enum FeatureFlag: String, CaseIterable { true case .upNextOnTabBar: true + case .displayGravatarProfile: + false case .downloadFixes: true case .onlyMarkPodcastsUnsyncedForNewUsers: From 13ccfb6cbb6b571c3287c3d898c79d9d0dc4c124 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 11 Jun 2024 17:41:23 +0300 Subject: [PATCH 2/9] Hide some header items when viewModel.shouldDisplayGravatarProfile is true --- .../Profile - SwiftUI/Account/AccountHeaderView.swift | 8 +++++--- .../View Models/ProfileDataViewModel.swift | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift b/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift index dd9a3cca04..b8a9eb4654 100644 --- a/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift +++ b/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift @@ -9,9 +9,11 @@ struct AccountHeaderView: View { var body: some View { container { proxy in VStack(spacing: Constants.padding.vertical) { - SubscriptionProfileImage(viewModel: viewModel) - .frame(width: Constants.imageSize, height: Constants.imageSize) - ProfileInfoLabels(profile: viewModel.profile, alignment: .center, spacing: Constants.spacing) + if !viewModel.shouldDisplayGravatarProfile { + SubscriptionProfileImage(viewModel: viewModel) + .frame(width: Constants.imageSize, height: Constants.imageSize) + ProfileInfoLabels(profile: viewModel.profile, alignment: .center, spacing: Constants.spacing) + } // Subscription badge viewModel.subscription.map { SubscriptionBadge(tier: $0.tier) diff --git a/podcasts/Profile - SwiftUI/View Models/ProfileDataViewModel.swift b/podcasts/Profile - SwiftUI/View Models/ProfileDataViewModel.swift index ab12b58b3c..d49f251869 100644 --- a/podcasts/Profile - SwiftUI/View Models/ProfileDataViewModel.swift +++ b/podcasts/Profile - SwiftUI/View Models/ProfileDataViewModel.swift @@ -2,6 +2,7 @@ import Foundation import Combine import PocketCastsServer import PocketCastsDataModel +import PocketCastsUtils /// Represents a view that will display information about the users profile such as email, subscription status, and stats class ProfileDataViewModel: ObservableObject { @@ -19,6 +20,9 @@ class ProfileDataViewModel: ObservableObject { /// Listening Stats var stats: UserInfo.Stats = .init() + /// If true we let the Gravatar SDK to display the profile view. + var shouldDisplayGravatarProfile: Bool = false + private var notifications = Set() init() { @@ -37,7 +41,7 @@ class ProfileDataViewModel: ObservableObject { profile = .init() subscription = .init(loggedIn: profile.isLoggedIn) stats = .init() - + shouldDisplayGravatarProfile = FeatureFlag.displayGravatarProfile.enabled && profile.isLoggedIn objectWillChange.send() } From 622191c4bdf949f08df3ceef5d61df5ffbcee86d Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 11 Jun 2024 17:43:06 +0300 Subject: [PATCH 3/9] Add Gravatar SDK --- podcasts.xcodeproj/project.pbxproj | 9 +++++++++ .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/podcasts.xcodeproj/project.pbxproj b/podcasts.xcodeproj/project.pbxproj index 451341c1fb..be5bae5771 100644 --- a/podcasts.xcodeproj/project.pbxproj +++ b/podcasts.xcodeproj/project.pbxproj @@ -7796,6 +7796,7 @@ 8B365E4E2B62E82000143DAC /* XCRemoteSwiftPackageReference "Agrume" */, 8B1762752B6808F700F44450 /* XCRemoteSwiftPackageReference "JLRoutes" */, 8B1762782B684E7100F44450 /* XCRemoteSwiftPackageReference "Kingfisher" */, + 91319B4A2C1899D1000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */, ); productRefGroup = BDBD53ED17019B2A0048C8C5 /* Products */; projectDirPath = ""; @@ -11848,6 +11849,14 @@ minimumVersion = 5.6.12; }; }; + 91319B4A2C1899D1000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Automattic/Gravatar-SDK-iOS.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; BD44F74E267C850000810148 /* XCRemoteSwiftPackageReference "DifferenceKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ra1028/DifferenceKit"; diff --git a/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved b/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved index 94bc85632c..761331229e 100644 --- a/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -118,6 +118,15 @@ "version": "7.13.1" } }, + { + "package": "Gravatar", + "repositoryURL": "https://github.com/Automattic/Gravatar-SDK-iOS.git", + "state": { + "branch": null, + "revision": "9855eb4cd835dc6bbf7aadec2dd015dae841fe19", + "version": "2.0.0" + } + }, { "package": "gRPC", "repositoryURL": "https://github.com/google/grpc-binary.git", From 2adb836f84e1fc9a940eef83e7c261f741c4cc6c Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 12 Jun 2024 16:24:03 +0300 Subject: [PATCH 4/9] Point gravatar to local --- podcasts.xcodeproj/project.pbxproj | 33 +++++++++++++------ .../xcshareddata/swiftpm/Package.resolved | 9 ----- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/podcasts.xcodeproj/project.pbxproj b/podcasts.xcodeproj/project.pbxproj index be5bae5771..b31fb986bc 100644 --- a/podcasts.xcodeproj/project.pbxproj +++ b/podcasts.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXAggregateTarget section */ @@ -624,6 +624,8 @@ 8BF1C61D2881F05D007E80BF /* FolderModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF1C61C2881F05D007E80BF /* FolderModelTests.swift */; }; 8BF9CC0C2ADDA90F004E9B65 /* YearOverYearStory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BF9CC0B2ADDA90F004E9B65 /* YearOverYearStory.swift */; }; 8BFB434E2A1FFB4B00F3D409 /* StatusPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFB434D2A1FFB4B00F3D409 /* StatusPageViewModel.swift */; }; + 91319B512C1984AD000220A4 /* Gravatar in Frameworks */ = {isa = PBXBuildFile; productRef = 91319B502C1984AD000220A4 /* Gravatar */; }; + 91319B532C1984AD000220A4 /* GravatarUI in Frameworks */ = {isa = PBXBuildFile; productRef = 91319B522C1984AD000220A4 /* GravatarUI */; }; BD001B892174260B00504DD3 /* FilterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD001B882174260B00504DD3 /* FilterManager.swift */; }; BD00CB2B24BD20CD00A10257 /* TimeStepperCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD00CB2924BD20CD00A10257 /* TimeStepperCell.swift */; }; BD00CB2C24BD20CD00A10257 /* TimeStepperCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BD00CB2A24BD20CD00A10257 /* TimeStepperCell.xib */; }; @@ -3552,11 +3554,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 91319B512C1984AD000220A4 /* Gravatar in Frameworks */, 46C22EE028F73D9A00F4173B /* AuthenticationServices.framework in Frameworks */, 468F00D327CD575C00FFAAAA /* FirebasePerformance in Frameworks */, BD11C41D2524741C00E308E6 /* CarPlay.framework in Frameworks */, BD44F750267C850000810148 /* DifferenceKit in Frameworks */, C72CED34289DA28B0017883A /* AutomatticTracks in Frameworks */, + 91319B532C1984AD000220A4 /* GravatarUI in Frameworks */, BDD8AABF267C858D0013494D /* Fuse in Frameworks */, 46305CF0272B082C003AC87B /* AutomatticEncryptedLogs in Frameworks */, 468F00CF27CD575C00FFAAAA /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, @@ -7630,6 +7634,8 @@ 8B365E4F2B62E82000143DAC /* Agrume */, 8B1762762B6808F700F44450 /* JLRoutes */, 8B1762792B684E7100F44450 /* Kingfisher */, + 91319B502C1984AD000220A4 /* Gravatar */, + 91319B522C1984AD000220A4 /* GravatarUI */, ); productName = podcasts; productReference = BDBD53EC17019B2A0048C8C5 /* podcasts.app */; @@ -7796,7 +7802,7 @@ 8B365E4E2B62E82000143DAC /* XCRemoteSwiftPackageReference "Agrume" */, 8B1762752B6808F700F44450 /* XCRemoteSwiftPackageReference "JLRoutes" */, 8B1762782B684E7100F44450 /* XCRemoteSwiftPackageReference "Kingfisher" */, - 91319B4A2C1899D1000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */, + 91319B4F2C1984AD000220A4 /* XCLocalSwiftPackageReference "../Gravatar-SDK-iOS" */, ); productRefGroup = BDBD53ED17019B2A0048C8C5 /* Products */; projectDirPath = ""; @@ -11800,6 +11806,13 @@ }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 91319B4F2C1984AD000220A4 /* XCLocalSwiftPackageReference "../Gravatar-SDK-iOS" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../Gravatar-SDK-iOS"; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ 3FB8642E28896539003A86BE /* XCRemoteSwiftPackageReference "test-collector-swift" */ = { isa = XCRemoteSwiftPackageReference; @@ -11849,14 +11862,6 @@ minimumVersion = 5.6.12; }; }; - 91319B4A2C1899D1000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Automattic/Gravatar-SDK-iOS.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; - }; - }; BD44F74E267C850000810148 /* XCRemoteSwiftPackageReference "DifferenceKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ra1028/DifferenceKit"; @@ -11986,6 +11991,14 @@ isa = XCSwiftPackageProductDependency; productName = PocketCastsDataModel; }; + 91319B502C1984AD000220A4 /* Gravatar */ = { + isa = XCSwiftPackageProductDependency; + productName = Gravatar; + }; + 91319B522C1984AD000220A4 /* GravatarUI */ = { + isa = XCSwiftPackageProductDependency; + productName = GravatarUI; + }; BD44F74F267C850000810148 /* DifferenceKit */ = { isa = XCSwiftPackageProductDependency; package = BD44F74E267C850000810148 /* XCRemoteSwiftPackageReference "DifferenceKit" */; diff --git a/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved b/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved index 761331229e..94bc85632c 100644 --- a/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -118,15 +118,6 @@ "version": "7.13.1" } }, - { - "package": "Gravatar", - "repositoryURL": "https://github.com/Automattic/Gravatar-SDK-iOS.git", - "state": { - "branch": null, - "revision": "9855eb4cd835dc6bbf7aadec2dd015dae841fe19", - "version": "2.0.0" - } - }, { "package": "gRPC", "repositoryURL": "https://github.com/google/grpc-binary.git", From 0e321c23181ce91aa2a7900f608b7f0810ce39e3 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 12 Jun 2024 16:30:19 +0300 Subject: [PATCH 5/9] Use profile view from gravatar sdk --- podcasts/AccountViewController.swift | 185 +++++++++++++++++- .../Account/AccountHeaderView.swift | 4 +- .../Profile/ProfileHeaderView.swift | 2 +- 3 files changed, 184 insertions(+), 7 deletions(-) diff --git a/podcasts/AccountViewController.swift b/podcasts/AccountViewController.swift index 952ce0c85c..dab7414c02 100644 --- a/podcasts/AccountViewController.swift +++ b/podcasts/AccountViewController.swift @@ -1,8 +1,15 @@ +import Combine import PocketCastsServer import PocketCastsUtils import UIKit +import GravatarUI class AccountViewController: UIViewController, ChangeEmailDelegate { + enum UIConstants { + enum Gravatar { + static let padding: UIEdgeInsets = .init(top: 12, left: 12, bottom: 12, right: 12) + } + } enum TableRow { case upgradeView, changeEmail, changePassword, upgradeAccount, newsletter, cancelSubscription, logout, deleteAccount, privacyPolicy, termsOfUse, supporterContributions } var tableData: [[TableRow]] = [[.changeEmail, .changePassword, .newsletter], [.privacyPolicy, .termsOfUse], [.logout], [.deleteAccount]] @@ -12,7 +19,6 @@ class AccountViewController: UIViewController, ChangeEmailDelegate { private var isUsernamePasswordLogin: Bool { ServerSettings.syncingPassword() != nil } - @IBOutlet var tableView: ThemeableTable! { didSet { tableView.applyInsetForMiniPlayer() @@ -38,6 +44,52 @@ class AccountViewController: UIViewController, ChangeEmailDelegate { return viewModel }() + lazy var headerStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [gravatarProfileViewContainer, updatedHeaderContentView]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.distribution = .fill + stackView.axis = .vertical + return stackView + }() + + private lazy var gravatarProfileViewContainer: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(gravatarProfileView) + let horizontalPadding: CGFloat = 16 + let verticalPadding: CGFloat = 16 + NSLayoutConstraint.activate([ + gravatarProfileView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -horizontalPadding), + gravatarProfileView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: horizontalPadding), + gravatarProfileView.topAnchor.constraint(equalTo: view.topAnchor, constant: verticalPadding), + gravatarProfileView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -verticalPadding) + ]) + return view + }() + + private lazy var gravatarProfileView: UIView & UIContentView = { + let contentView = LargeProfileSummaryView(avatarType: .custom(self)) + contentView.translatesAutoresizingMaskIntoConstraints = false + contentView.layer.cornerRadius = 8 + contentView.clipsToBounds = true + return contentView + }() + + private let gravatarViewModel: ProfileViewModel = .init() + + private var cancellables = Set() + + private lazy var gravatarConfiguration: ProfileViewConfiguration = { + return ProfileViewConfiguration.largeSummary().updatedForPocketCasts(delegate: self) + }() { + didSet { + gravatarProfileView.configuration = gravatarConfiguration + tableView.setNeedsLayout() + tableView.layoutIfNeeded() + tableView.reloadData() + } + } + lazy var updatedHeaderContentView: UIView = { let headerView = AccountHeaderView(viewModel: headerViewModel) @@ -47,6 +99,21 @@ class AccountViewController: UIViewController, ChangeEmailDelegate { return view }() + private lazy var subscriptionAvatarView: UIView = { + let view = SubscriptionProfileImage(viewModel: headerViewModel) + .frame(width: ProfileHeaderView.Constants.imageSize, height: ProfileHeaderView.Constants.imageSize) + let avatarView = view.themedUIView + avatarView.backgroundColor = .clear + return avatarView + }() + + private func toggleGravatarProfileView() { + gravatarProfileViewContainer.isHidden = !headerViewModel.shouldDisplayGravatarProfile + if headerViewModel.shouldDisplayGravatarProfile { + fetchGravatarProfile() + } + } + override func viewDidLoad() { super.viewDidLoad() title = L10n.accountTitle @@ -55,8 +122,9 @@ class AccountViewController: UIViewController, ChangeEmailDelegate { NotificationCenter.default.addObserver(self, selector: #selector(iapProductsFailed), name: ServerNotifications.iapProductsFailed, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(subscriptionStatusChanged), name: ServerNotifications.subscriptionStatusChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: Constants.Notifications.themeChanged, object: nil) - - tableView.tableHeaderView = updatedHeaderContentView + tableView.tableHeaderView = headerStackView + tableView.widthAnchor.constraint(equalTo: headerStackView.widthAnchor).isActive = true + receiveGravatarViewModelUpdates() } override func viewWillAppear(_ animated: Bool) { @@ -91,7 +159,7 @@ class AccountViewController: UIViewController, ChangeEmailDelegate { private func updateDisplayedData() { headerViewModel.update() - + toggleGravatarProfileView() // Show the upsell if the users subscription is expiring in the next 30 days let isExpiring = (SubscriptionHelper.timeToSubscriptionExpiry() ?? .infinity) < Constants.Limits.maxSubscriptionExpirySeconds @@ -173,3 +241,112 @@ class AccountViewController: UIViewController, ChangeEmailDelegate { } } } + +extension AccountViewController: ProfileViewDelegate { + + private func receiveGravatarViewModelUpdates() { + gravatarViewModel.$profileFetchingResult.sink { [weak self] result in + guard let self else { return } + guard let result else { + var newConfig = self.gravatarConfiguration.updatedForPocketCasts(delegate: self) + newConfig.model = nil + newConfig.summaryModel = nil + self.gravatarConfiguration = newConfig + return + } + + switch result { + case .success(let profile): + var newConfig = self.gravatarConfiguration.updatedForPocketCasts(delegate: self) + newConfig.model = profile + newConfig.summaryModel = profile + self.gravatarConfiguration = newConfig + case .failure(Gravatar.APIError.responseError(reason: let reason)) where reason.httpStatusCode == 404: + // No Gravatar profile found, switch to the "claim profile" state. + var claimProfileConfig = ProfileViewConfiguration.claimProfile(profileStyle: gravatarConfiguration.profileStyle) + claimProfileConfig.padding = UIConstants.Gravatar.padding + claimProfileConfig.delegate = self + claimProfileConfig.avatarConfiguration.placeholder = UIImage(named: "profileAvatar")?.withRenderingMode(.alwaysTemplate).tintedImage(ThemeColor.primaryUi05()) + claimProfileConfig.palette = .custom(claimProfileConfig.pocketCastsPalette) + self.gravatarConfiguration = claimProfileConfig + case .failure: + // TODO: handle error + break + } + }.store(in: &cancellables) + + gravatarViewModel.$isLoading.sink { [weak self] isLoading in + guard let self else { return } + var newConfig = self.gravatarConfiguration + newConfig.isLoading = isLoading + self.gravatarConfiguration = newConfig + }.store(in: &cancellables) + } + + public func clearGravatarProfile() { + gravatarViewModel.clear() + } + + public func fetchGravatarProfile() { + guard headerViewModel.shouldDisplayGravatarProfile, let email = headerViewModel.profile.email else { return } + Task { + await gravatarViewModel.fetchProfile(profileIdentifier: ProfileIdentifier.email(email)) + } + } + func profileView(_ view: GravatarUI.BaseProfileView, didTapOnProfileButtonWithStyle style: GravatarUI.ProfileButtonStyle, profileURL: URL?) { + + } + + func profileView(_ view: GravatarUI.BaseProfileView, didTapOnAccountButtonWithModel accountModel: any GravatarUI.AccountModel) { + + } + + func profileView(_ view: GravatarUI.BaseProfileView, didTapOnAvatarWithID avatarID: Gravatar.AvatarIdentifier?) { + + } +} + +extension AccountViewController: AvatarProviding { + var avatarView: UIView { + subscriptionAvatarView + } + + func setImage(with source: URL?, placeholder: UIImage?, options: [GravatarUI.ImageSettingOption]?) async throws { + // no need + } + + func setImage(_ image: UIImage?) { + // no need + } + + func refresh(with paletteType: GravatarUI.PaletteType) { + // no need + } +} + +fileprivate extension ProfileViewConfiguration { + + func updatedForPocketCasts(delegate: ProfileViewDelegate) -> ProfileViewConfiguration { + var config = self + config.avatarConfiguration.placeholder = UIImage(named: "profileAvatar")?.withRenderingMode(.alwaysTemplate).tintedImage(ThemeColor.primaryUi05()) + config.padding = AccountViewController.UIConstants.Gravatar.padding + config.profileButtonStyle = .edit + config.delegate = delegate + config.palette = .custom(pocketCastsPalette) + return config + } + + func pocketCastsPalette() -> GravatarUI.Palette { + GravatarUI.Palette(name: Theme.sharedTheme.activeTheme.description, + foreground: ForegroundColors(primary: ThemeColor.primaryText01(), + primarySlightlyDimmed: ThemeColor.secondaryText01(), + secondary: ThemeColor.primaryText02()), + background: BackgroundColors(primary: ThemeColor.primaryUi02()), + avatar: AvatarColors(border: ThemeColor.primaryUi05(), + background: ThemeColor.primaryUi04()), + border: ThemeColor.primaryUi05(), + placeholder: PlaceholderColors(backgroundColor: ThemeColor.primaryUi04(), + loadingAnimationColors: [ThemeColor.primaryUi04(), ThemeColor.primaryUi05()]), + preferredUserInterfaceStyle: Theme.sharedTheme.activeTheme.isDark ? .dark: .light) + } +} diff --git a/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift b/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift index b8a9eb4654..d9c6bbcfcc 100644 --- a/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift +++ b/podcasts/Profile - SwiftUI/Account/AccountHeaderView.swift @@ -12,8 +12,8 @@ struct AccountHeaderView: View { if !viewModel.shouldDisplayGravatarProfile { SubscriptionProfileImage(viewModel: viewModel) .frame(width: Constants.imageSize, height: Constants.imageSize) - ProfileInfoLabels(profile: viewModel.profile, alignment: .center, spacing: Constants.spacing) } + ProfileInfoLabels(profile: viewModel.profile, alignment: .center, spacing: Constants.spacing) // Subscription badge viewModel.subscription.map { SubscriptionBadge(tier: $0.tier) @@ -109,7 +109,7 @@ struct AccountHeaderView: View { content(proxy) .frame(maxWidth: .infinity) } - .padding(.top, Constants.padding.top) + .padding(.top, viewModel.shouldDisplayGravatarProfile ? Constants.padding.vertical : Constants.padding.top) .padding(.bottom, Constants.padding.bottom) .padding(.horizontal, Constants.padding.horizontal) } contentSizeUpdated: { size in diff --git a/podcasts/Profile - SwiftUI/Profile/ProfileHeaderView.swift b/podcasts/Profile - SwiftUI/Profile/ProfileHeaderView.swift index c17d0b393a..759444347a 100644 --- a/podcasts/Profile - SwiftUI/Profile/ProfileHeaderView.swift +++ b/podcasts/Profile - SwiftUI/Profile/ProfileHeaderView.swift @@ -212,7 +212,7 @@ struct ProfileHeaderView: View { } // MARK: - View Constants - private enum Constants { + enum Constants { static let spacing = 16.0 static let imageSize = 104.0 static let paddingTop = 30.0 From d8c9d27f248173cdf0e19133e0d52ea76568aea0 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 12 Jun 2024 17:27:52 +0300 Subject: [PATCH 6/9] Implement delegate tap handlers --- podcasts/AccountViewController.swift | 53 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/podcasts/AccountViewController.swift b/podcasts/AccountViewController.swift index dab7414c02..0d072ed9e5 100644 --- a/podcasts/AccountViewController.swift +++ b/podcasts/AccountViewController.swift @@ -1,8 +1,9 @@ import Combine +import GravatarUI import PocketCastsServer import PocketCastsUtils import UIKit -import GravatarUI +import SafariServices class AccountViewController: UIViewController, ChangeEmailDelegate { enum UIConstants { @@ -266,8 +267,7 @@ extension AccountViewController: ProfileViewDelegate { var claimProfileConfig = ProfileViewConfiguration.claimProfile(profileStyle: gravatarConfiguration.profileStyle) claimProfileConfig.padding = UIConstants.Gravatar.padding claimProfileConfig.delegate = self - claimProfileConfig.avatarConfiguration.placeholder = UIImage(named: "profileAvatar")?.withRenderingMode(.alwaysTemplate).tintedImage(ThemeColor.primaryUi05()) - claimProfileConfig.palette = .custom(claimProfileConfig.pocketCastsPalette) + claimProfileConfig.palette = .custom(Palette.pocketCasts) self.gravatarConfiguration = claimProfileConfig case .failure: // TODO: handle error @@ -293,17 +293,20 @@ extension AccountViewController: ProfileViewDelegate { await gravatarViewModel.fetchProfile(profileIdentifier: ProfileIdentifier.email(email)) } } - func profileView(_ view: GravatarUI.BaseProfileView, didTapOnProfileButtonWithStyle style: GravatarUI.ProfileButtonStyle, profileURL: URL?) { + func profileView(_ view: GravatarUI.BaseProfileView, didTapOnProfileButtonWithStyle style: GravatarUI.ProfileButtonStyle, profileURL: URL?) { + guard let profileURL else { return } + let safari = SFSafariViewController(url: profileURL) + present(safari, animated: true) } func profileView(_ view: GravatarUI.BaseProfileView, didTapOnAccountButtonWithModel accountModel: any GravatarUI.AccountModel) { - + guard let accountURL = accountModel.accountURL else { return } + let safari = SFSafariViewController(url: accountURL) + present(safari, animated: true) } - func profileView(_ view: GravatarUI.BaseProfileView, didTapOnAvatarWithID avatarID: Gravatar.AvatarIdentifier?) { - - } + func profileView(_ view: GravatarUI.BaseProfileView, didTapOnAvatarWithID avatarID: Gravatar.AvatarIdentifier?) {} } extension AccountViewController: AvatarProviding { @@ -328,25 +331,31 @@ fileprivate extension ProfileViewConfiguration { func updatedForPocketCasts(delegate: ProfileViewDelegate) -> ProfileViewConfiguration { var config = self - config.avatarConfiguration.placeholder = UIImage(named: "profileAvatar")?.withRenderingMode(.alwaysTemplate).tintedImage(ThemeColor.primaryUi05()) config.padding = AccountViewController.UIConstants.Gravatar.padding config.profileButtonStyle = .edit config.delegate = delegate - config.palette = .custom(pocketCastsPalette) + config.palette = .custom(Palette.pocketCasts) return config } +} - func pocketCastsPalette() -> GravatarUI.Palette { - GravatarUI.Palette(name: Theme.sharedTheme.activeTheme.description, - foreground: ForegroundColors(primary: ThemeColor.primaryText01(), - primarySlightlyDimmed: ThemeColor.secondaryText01(), - secondary: ThemeColor.primaryText02()), - background: BackgroundColors(primary: ThemeColor.primaryUi02()), - avatar: AvatarColors(border: ThemeColor.primaryUi05(), - background: ThemeColor.primaryUi04()), - border: ThemeColor.primaryUi05(), - placeholder: PlaceholderColors(backgroundColor: ThemeColor.primaryUi04(), - loadingAnimationColors: [ThemeColor.primaryUi04(), ThemeColor.primaryUi05()]), - preferredUserInterfaceStyle: Theme.sharedTheme.activeTheme.isDark ? .dark: .light) +extension Palette { + static func pocketCasts() -> GravatarUI.Palette { + GravatarUI.Palette( + name: Theme.sharedTheme.activeTheme.description, + foreground: ForegroundColors(primary: ThemeColor.primaryText01(), + primarySlightlyDimmed: ThemeColor.secondaryText01(), + secondary: ThemeColor.primaryText02()), + background: BackgroundColors(primary: ThemeColor.primaryUi02()), + avatar: AvatarColors(border: ThemeColor.primaryUi05(), + background: ThemeColor.primaryUi04()), + border: ThemeColor.primaryUi05(), + placeholder: PlaceholderColors(backgroundColor: ThemeColor.primaryText01().withAlphaComponent(0.05), + loadingAnimationColors: [ + ThemeColor.primaryText01().withAlphaComponent(0.05), + ThemeColor.primaryText01().withAlphaComponent(0.1) + ]), + preferredUserInterfaceStyle: Theme.sharedTheme.activeTheme.isDark ? .dark: .light + ) } } From d05e3c2b3ad543a7d03adc3ee2e3b7cb25ad40a7 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 12 Jun 2024 17:29:40 +0300 Subject: [PATCH 7/9] Point gravatar to commit --- podcasts.xcodeproj/project.pbxproj | 35 ++++++++++++++----- .../xcshareddata/swiftpm/Package.resolved | 9 +++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/podcasts.xcodeproj/project.pbxproj b/podcasts.xcodeproj/project.pbxproj index b31fb986bc..f27d4354a4 100644 --- a/podcasts.xcodeproj/project.pbxproj +++ b/podcasts.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -626,6 +626,8 @@ 8BFB434E2A1FFB4B00F3D409 /* StatusPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BFB434D2A1FFB4B00F3D409 /* StatusPageViewModel.swift */; }; 91319B512C1984AD000220A4 /* Gravatar in Frameworks */ = {isa = PBXBuildFile; productRef = 91319B502C1984AD000220A4 /* Gravatar */; }; 91319B532C1984AD000220A4 /* GravatarUI in Frameworks */ = {isa = PBXBuildFile; productRef = 91319B522C1984AD000220A4 /* GravatarUI */; }; + 91319B562C19E838000220A4 /* Gravatar in Frameworks */ = {isa = PBXBuildFile; productRef = 91319B552C19E838000220A4 /* Gravatar */; }; + 91319B582C19E838000220A4 /* GravatarUI in Frameworks */ = {isa = PBXBuildFile; productRef = 91319B572C19E838000220A4 /* GravatarUI */; }; BD001B892174260B00504DD3 /* FilterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD001B882174260B00504DD3 /* FilterManager.swift */; }; BD00CB2B24BD20CD00A10257 /* TimeStepperCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD00CB2924BD20CD00A10257 /* TimeStepperCell.swift */; }; BD00CB2C24BD20CD00A10257 /* TimeStepperCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BD00CB2A24BD20CD00A10257 /* TimeStepperCell.xib */; }; @@ -3562,6 +3564,7 @@ C72CED34289DA28B0017883A /* AutomatticTracks in Frameworks */, 91319B532C1984AD000220A4 /* GravatarUI in Frameworks */, BDD8AABF267C858D0013494D /* Fuse in Frameworks */, + 91319B562C19E838000220A4 /* Gravatar in Frameworks */, 46305CF0272B082C003AC87B /* AutomatticEncryptedLogs in Frameworks */, 468F00CF27CD575C00FFAAAA /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, 8BAD6E5E2975ADB800DB7259 /* GoogleSignIn in Frameworks */, @@ -3572,6 +3575,7 @@ 8B365E502B62E82000143DAC /* Agrume in Frameworks */, 8BE36E002873552500E35313 /* PocketCastsServer in Frameworks */, BDD239BD267C869B0047750C /* SwipeCellKit in Frameworks */, + 91319B582C19E838000220A4 /* GravatarUI in Frameworks */, 8B1762772B6808F700F44450 /* JLRoutes in Frameworks */, 8B17627A2B684E7100F44450 /* Kingfisher in Frameworks */, 1C312783007F5948E428F709 /* libPods-podcasts.a in Frameworks */, @@ -7636,6 +7640,8 @@ 8B1762792B684E7100F44450 /* Kingfisher */, 91319B502C1984AD000220A4 /* Gravatar */, 91319B522C1984AD000220A4 /* GravatarUI */, + 91319B552C19E838000220A4 /* Gravatar */, + 91319B572C19E838000220A4 /* GravatarUI */, ); productName = podcasts; productReference = BDBD53EC17019B2A0048C8C5 /* podcasts.app */; @@ -7802,7 +7808,7 @@ 8B365E4E2B62E82000143DAC /* XCRemoteSwiftPackageReference "Agrume" */, 8B1762752B6808F700F44450 /* XCRemoteSwiftPackageReference "JLRoutes" */, 8B1762782B684E7100F44450 /* XCRemoteSwiftPackageReference "Kingfisher" */, - 91319B4F2C1984AD000220A4 /* XCLocalSwiftPackageReference "../Gravatar-SDK-iOS" */, + 91319B542C19E837000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */, ); productRefGroup = BDBD53ED17019B2A0048C8C5 /* Products */; projectDirPath = ""; @@ -11806,13 +11812,6 @@ }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - 91319B4F2C1984AD000220A4 /* XCLocalSwiftPackageReference "../Gravatar-SDK-iOS" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = "../Gravatar-SDK-iOS"; - }; -/* End XCLocalSwiftPackageReference section */ - /* Begin XCRemoteSwiftPackageReference section */ 3FB8642E28896539003A86BE /* XCRemoteSwiftPackageReference "test-collector-swift" */ = { isa = XCRemoteSwiftPackageReference; @@ -11862,6 +11861,14 @@ minimumVersion = 5.6.12; }; }; + 91319B542C19E837000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Automattic/Gravatar-SDK-iOS.git"; + requirement = { + kind = revision; + revision = afe715820691a17f286fac9891afc5290d0e2981; + }; + }; BD44F74E267C850000810148 /* XCRemoteSwiftPackageReference "DifferenceKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ra1028/DifferenceKit"; @@ -11999,6 +12006,16 @@ isa = XCSwiftPackageProductDependency; productName = GravatarUI; }; + 91319B552C19E838000220A4 /* Gravatar */ = { + isa = XCSwiftPackageProductDependency; + package = 91319B542C19E837000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */; + productName = Gravatar; + }; + 91319B572C19E838000220A4 /* GravatarUI */ = { + isa = XCSwiftPackageProductDependency; + package = 91319B542C19E837000220A4 /* XCRemoteSwiftPackageReference "Gravatar-SDK-iOS" */; + productName = GravatarUI; + }; BD44F74F267C850000810148 /* DifferenceKit */ = { isa = XCSwiftPackageProductDependency; package = BD44F74E267C850000810148 /* XCRemoteSwiftPackageReference "DifferenceKit" */; diff --git a/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved b/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved index 94bc85632c..8f9ff05f9e 100644 --- a/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/podcasts.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -118,6 +118,15 @@ "version": "7.13.1" } }, + { + "package": "Gravatar", + "repositoryURL": "https://github.com/Automattic/Gravatar-SDK-iOS.git", + "state": { + "branch": null, + "revision": "afe715820691a17f286fac9891afc5290d0e2981", + "version": null + } + }, { "package": "gRPC", "repositoryURL": "https://github.com/google/grpc-binary.git", From 25d7b70adf4cdbf20eecf16d0870232d6d41f556 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 12 Jun 2024 18:12:04 +0300 Subject: [PATCH 8/9] Small color update --- podcasts/AccountViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podcasts/AccountViewController.swift b/podcasts/AccountViewController.swift index 0d072ed9e5..4c9768565d 100644 --- a/podcasts/AccountViewController.swift +++ b/podcasts/AccountViewController.swift @@ -346,7 +346,7 @@ extension Palette { foreground: ForegroundColors(primary: ThemeColor.primaryText01(), primarySlightlyDimmed: ThemeColor.secondaryText01(), secondary: ThemeColor.primaryText02()), - background: BackgroundColors(primary: ThemeColor.primaryUi02()), + background: BackgroundColors(primary: ThemeColor.primaryUi01()), avatar: AvatarColors(border: ThemeColor.primaryUi05(), background: ThemeColor.primaryUi04()), border: ThemeColor.primaryUi05(), From c5fb4229f667fff019aecddfa1906a00a57129e0 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 12 Jun 2024 18:15:47 +0300 Subject: [PATCH 9/9] set displayGravatarProfile to true --- .../Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift b/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift index 2b158d7016..d5dcb42d1c 100644 --- a/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift +++ b/Modules/Utils/Sources/PocketCastsUtils/Feature Flags/FeatureFlag.swift @@ -116,7 +116,7 @@ public enum FeatureFlag: String, CaseIterable { case .upNextOnTabBar: true case .displayGravatarProfile: - false + true case .downloadFixes: true case .onlyMarkPodcastsUnsyncedForNewUsers: