Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Change Avatar" button to account settings #1770

Open
wants to merge 10 commits into
base: trunk
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions podcasts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@
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 */; };
91319B0B2C171F69000220A4 /* GravatarSafariViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91319B0A2C171F69000220A4 /* GravatarSafariViewController.swift */; };
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 */; };
Expand Down Expand Up @@ -2462,6 +2463,7 @@
8BF1C61C2881F05D007E80BF /* FolderModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderModelTests.swift; sourceTree = "<group>"; };
8BF9CC0B2ADDA90F004E9B65 /* YearOverYearStory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YearOverYearStory.swift; sourceTree = "<group>"; };
8BFB434D2A1FFB4B00F3D409 /* StatusPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPageViewModel.swift; sourceTree = "<group>"; };
91319B0A2C171F69000220A4 /* GravatarSafariViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GravatarSafariViewController.swift; sourceTree = "<group>"; };
9A9517324EFFD2C905890193 /* Pods-podcasts.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-podcasts.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-podcasts/Pods-podcasts.appstore.xcconfig"; sourceTree = "<group>"; };
9E6FEF10644B78313185D080 /* Pods-PocketCastsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PocketCastsTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PocketCastsTests/Pods-PocketCastsTests.release.xcconfig"; sourceTree = "<group>"; };
A251FE0E92E432C4EF0C8207 /* Pods-PocketCastsTests.staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PocketCastsTests.staging.xcconfig"; path = "Pods/Target Support Files/Pods-PocketCastsTests/Pods-PocketCastsTests.staging.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3854,6 +3856,7 @@
40164A6A23DAAF8B0043907B /* PromotionAcknowledgementViewController.xib */,
408529A5247CA913007FE8AA /* Paid Podcasts */,
40251CE822BB0F5900551C51 /* Acknowledge Modals */,
91319B0A2C171F69000220A4 /* GravatarSafariViewController.swift */,
);
name = "Account Management";
sourceTree = "<group>";
Expand Down Expand Up @@ -8892,6 +8895,7 @@
BD6B432C27561EA8005E4017 /* PCHostingController.swift in Sources */,
BDCA84F21C1FB7260022ED4D /* DownloadSettingsViewController.swift in Sources */,
4084262D2134CBCD0076D82E /* LargeListCell.swift in Sources */,
91319B0B2C171F69000220A4 /* GravatarSafariViewController.swift in Sources */,
C701C25C2A8E82DC00AD9FAB /* PaidFeature.swift in Sources */,
401EBA6923725C6400899F93 /* UpNextNowPlayingCell.swift in Sources */,
C7DC40202A6702A600883D03 /* ToastView.swift in Sources */,
Expand Down
16 changes: 16 additions & 0 deletions podcasts/AccountViewController+TableView.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import PocketCastsDataModel
import SafariServices
import PocketCastsServer
import UIKit

extension AccountViewController: UITableViewDataSource, UITableViewDelegate {

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard section == 0 else {
return UITableView.automaticDimension
Expand Down Expand Up @@ -82,6 +84,14 @@ extension AccountViewController: UITableViewDataSource, UITableViewDelegate {
}
cell.showsDisclosureIndicator = true
return cell
case .changeAvatar:
let cell = tableView.dequeueReusableCell(withIdentifier: AccountViewController.actionCellId, for: indexPath) as! AccountActionCell
cell.cellLabel.text = L10n.settingsChangeAvatar
cell.cellImage.image = UIImage(named: "settings-avatar")?.withRenderingMode(.alwaysTemplate)
cell.iconStyle = .primaryInteractive01
cell.counterView.isHidden = true
cell.showsDisclosureIndicator = false
return cell
case .changeEmail:
let cell = tableView.dequeueReusableCell(withIdentifier: AccountViewController.actionCellId, for: indexPath) as! AccountActionCell
cell.cellLabel.text = L10n.accountChangeEmail
Expand Down Expand Up @@ -172,6 +182,12 @@ extension AccountViewController: UITableViewDataSource, UITableViewDelegate {
case .supporterContributions:
let supporterVC = SupporterContributionsViewController()
navigationController?.pushViewController(supporterVC, animated: true)
case .changeAvatar:
guard let email = headerViewModel.profile.email,
let safariViewController = GravatarSafariViewController(destination: .avatarUpdate(email: email)) else { return }
safariViewController.modalPresentationStyle = .automatic
present(safariViewController, animated: true)
Analytics.track(.accountDetailsChangeAvatar)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update this spreadsheet when adding new events. I went ahead and created the entry for this one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! 🙇

case .changeEmail:
let changeEmailVC = ChangeEmailViewController()
changeEmailVC.delegate = self
Expand Down
6 changes: 4 additions & 2 deletions podcasts/AccountViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PocketCastsUtils
import UIKit

class AccountViewController: UIViewController, ChangeEmailDelegate {
enum TableRow { case upgradeView, changeEmail, changePassword, upgradeAccount, newsletter, cancelSubscription, logout, deleteAccount, privacyPolicy, termsOfUse, supporterContributions }
enum TableRow { case upgradeView, changeAvatar, changeEmail, changePassword, upgradeAccount, newsletter, cancelSubscription, logout, deleteAccount, privacyPolicy, termsOfUse, supporterContributions }
var tableData: [[TableRow]] = [[.changeEmail, .changePassword, .newsletter], [.privacyPolicy, .termsOfUse], [.logout], [.deleteAccount]]

static let newsletterCellId = "NewsletterCellId"
Expand Down Expand Up @@ -106,7 +106,9 @@ class AccountViewController: UIViewController, ChangeEmailDelegate {
} else {
accountOptions = [upgradeRow, .newsletter].compactMap { $0 }
}

if headerViewModel.profile.isLoggedIn {
accountOptions.insert(.changeAvatar, safelyAt: 0)
}
if SubscriptionHelper.hasActiveSubscription() {
var newTableRows: [[TableRow]] = [accountOptions, [.privacyPolicy, .termsOfUse], [.logout], [.deleteAccount]]

Expand Down
1 change: 1 addition & 0 deletions podcasts/Analytics/AnalyticsEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ enum AnalyticsEvent: String {
case accountDetailsCancelTapped
case accountDetailsShowTOS
case accountDetailsShowPrivacyPolicy
case accountDetailsChangeAvatar

// MARK: - Stats View

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Artboard@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Artboard@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
3 changes: 3 additions & 0 deletions podcasts/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ struct Constants {

// End of Year
static let profileSeen = NSNotification.Name(rawValue: "profileSeen")

// Gravatar
static let avatarNeedsRefreshing = NSNotification.Name(rawValue: "avatarNeedsRefreshing")
}

enum UserDefaults {
Expand Down
47 changes: 47 additions & 0 deletions podcasts/GravatarSafariViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import UIKit
import SafariServices

final class GravatarSafariViewController: SFSafariViewController {

enum Destination {
case avatarUpdate(email: String)

var url: URL? {
switch self {
case .avatarUpdate(let email):
guard var components = URLComponents(string: "https://gravatar.com/profile") else { return nil }
components.percentEncodedQueryItems = [
URLQueryItem(name: "is_quick_editor", value: "true"),
URLQueryItem(name: "email", value: email),
URLQueryItem(name: "scope", value: "avatars")
].map({ $0.percentEncoded() })
return components.url
}
}
}

init?(destination: Destination) {
guard let url = destination.url else { return nil }
super.init(url: url, configuration: .appDefault)
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.post(name: Constants.Notifications.avatarNeedsRefreshing, object: nil)
}
}

fileprivate extension URLQueryItem {
func percentEncoded() -> URLQueryItem {
/// addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) encode parameters following RFC 3986
/// According to RFC 3986, `+` is a valid character so it is not encoded.
/// We should encode "+" sign with its HTTP equivalent.

var newQueryItem = self
newQueryItem.value = value?
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)?
.replacingOccurrences(of: "+", with: "%2B")

return newQueryItem
}
}
31 changes: 22 additions & 9 deletions podcasts/Profile - SwiftUI/Common Views/ProfileImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@ import Kingfisher
struct ProfileImage: View {
@EnvironmentObject var theme: Theme
let email: String?
private let avatarRefreshPublisher = NotificationCenter.default.publisher(for: Constants.Notifications.avatarNeedsRefreshing)
@State private var forceRefresh: Bool = false
@State private var reloadId = UUID()

var body: some View {
if let url {
KFImage
.url(url, cacheKey: email)
.placeholder { _ in defaultProfileView }
.resizable()
.aspectRatio(contentMode: .fill)
} else {
defaultProfileView
}
ZStack {
if let url {
KFImage
.url(url, cacheKey: email)
.placeholder { _ in defaultProfileView }
.resizable()
.forceRefresh(forceRefresh)
.onSuccess { result in
forceRefresh = false
}
.aspectRatio(contentMode: .fill)
} else {
defaultProfileView
}
}.onReceive(avatarRefreshPublisher, perform: { _ in
forceRefresh = true
reloadId = UUID() // updating forceRefresh alone doesn't work for reloading the KFImage
})
.id(reloadId)
}

private var url: URL? {
Expand Down
2 changes: 2 additions & 0 deletions podcasts/Strings+Generated.swift

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions podcasts/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2581,6 +2581,9 @@
/* Fun informational message about the skip options available in the settings. */
"settings_skip_msg" = "Skip intro and outro music like the power user you were born to be.";

/* Title for the button that open a page to change the avatar(a.k.a. profile picture). */
"settings_change_avatar" = "Change Avatar";

/* A common string used throughout the app. Refers to the Stats settings menu */
"settings_stats" = "Stats";

Expand Down