Skip to content

Commit

Permalink
Revert "Remove unused Gravatar"
Browse files Browse the repository at this point in the history
This reverts commit 5f04d80.
  • Loading branch information
kean committed Jun 27, 2024
1 parent 1bc9958 commit 06b0b76
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 0 deletions.
148 changes: 148 additions & 0 deletions Sources/WordPressUI/Extensions/Gravatar/Gravatar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import Foundation

/// Helper Enum that specifies all of the available Gravatar Image Ratings
/// TODO: Convert into a pure Swift String Enum. It's done this way to maintain ObjC Compatibility
///
@available(*, deprecated, message: "Use `Rating` from the Gravatar iOS SDK. See: https://github.com/Automattic/Gravatar-SDK-iOS.")
@objc
public enum GravatarRatings: Int {
case g
case pg
case r
case x
case `default`

func stringValue() -> String {
switch self {
case .default:
fallthrough
case .g:
return "g"
case .pg:
return "pg"
case .r:
return "r"
case .x:
return "x"
}
}
}

/// Helper Enum that specifies some of the options for default images
/// To see all available options, visit : https://en.gravatar.com/site/implement/images/
///
@available(*, deprecated, message: "Use `DefaultAvatarOption` from the Gravatar iOS SDK. See: https://github.com/Automattic/Gravatar-SDK-iOS.")
public enum GravatarDefaultImage: String {
case fileNotFound = "404"
case mp
case identicon
}

@available(*, deprecated, message: "Use `AvatarURL` from the Gravatar iOS SDK. See: https://github.com/Automattic/Gravatar-SDK-iOS")
public struct Gravatar {
fileprivate struct Defaults {
static let scheme = "https"
static let host = "secure.gravatar.com"
static let unknownHash = "ad516503a11cd5ca435acc9bb6523536"
static let baseURL = "https://gravatar.com/avatar"
static let imageSize = 80
}

public let canonicalURL: URL

public func urlWithSize(_ size: Int, defaultImage: GravatarDefaultImage? = nil) -> URL {
var components = URLComponents(url: canonicalURL, resolvingAgainstBaseURL: false)!
components.query = "s=\(size)&d=\(defaultImage?.rawValue ?? GravatarDefaultImage.fileNotFound.rawValue)"
return components.url!
}

public static func isGravatarURL(_ url: URL) -> Bool {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return false
}

guard let host = components.host, host.hasSuffix(".gravatar.com") else {
return false
}

guard url.path.hasPrefix("/avatar/") else {
return false
}

return true
}

/// Returns the Gravatar URL, for a given email, with the specified size + rating.
///
/// - Parameters:
/// - email: the user's email
/// - size: required download size
/// - rating: image rating filtering
///
/// - Returns: Gravatar's URL
///
public static func gravatarUrl(for email: String,
defaultImage: GravatarDefaultImage? = nil,
size: Int? = nil,
rating: GravatarRatings = .default) -> URL? {
let hash = gravatarHash(of: email)
let targetURL = String(format: "%@/%@?d=%@&s=%d&r=%@",
Defaults.baseURL,
hash,
defaultImage?.rawValue ?? GravatarDefaultImage.fileNotFound.rawValue,
size ?? Defaults.imageSize,
rating.stringValue())
return URL(string: targetURL)
}

/// Returns the gravatar hash of an email
///
/// - Parameter email: the email associated with the gravatar
/// - Returns: hashed email
///
/// This really ought to be in a different place, like Gravatar.swift, but there's
/// lots of duplication around gravatars -nh
private static func gravatarHash(of email: String) -> String {
return email
.lowercased()
.trimmingCharacters(in: .whitespaces)
.sha256Hash()
}
}

@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
extension Gravatar: Equatable {}

@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
public func ==(lhs: Gravatar, rhs: Gravatar) -> Bool {
return lhs.canonicalURL == rhs.canonicalURL
}

@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
public extension Gravatar {
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
init?(_ url: URL) {
guard Gravatar.isGravatarURL(url) else {
return nil
}

guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}

components.scheme = Defaults.scheme
components.host = Defaults.host
components.query = nil

// Treat [email protected] as a nil url
guard url.lastPathComponent != Defaults.unknownHash else {
return nil
}

guard let sanitizedURL = components.url else {
return nil
}

self.canonicalURL = sanitizedURL
}
}
194 changes: 194 additions & 0 deletions Sources/WordPressUI/Extensions/UIImageView+Gravatar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import Foundation
import UIKit

#if SWIFT_PACKAGE
import WordPressUIObjC
#endif

/// Wrapper class used to ensure removeObserver is called
private class GravatarNotificationWrapper {
let observer: NSObjectProtocol

init(observer: NSObjectProtocol) {
self.observer = observer
}

deinit {
NotificationCenter.default.removeObserver(observer)
}
}

/// UIImageView Helper Methods that allow us to download a Gravatar, given the User's Email
///
extension UIImageView {

/// Downloads and sets the User's Gravatar, given his email.
/// TODO: This is a convenience method. Please, remove once all of the code has been migrated over to Swift.
///
/// - Parameters:
/// - email: the user's email
/// - rating: expected image rating
///
/// This method uses deprecated types. Please check the deprecation warning in `GravatarRatings`. Also check out the UIImageView extension from the Gravatar iOS SDK as an alternative to download images. See: https://github.com/Automattic/Gravatar-SDK-iOS.
@available(*, deprecated, message: "Usage of the deprecated type: GravatarRatings.")
@objc
public func downloadGravatarWithEmail(_ email: String, rating: GravatarRatings) {
downloadGravatarWithEmail(email, rating: rating, placeholderImage: .gravatarPlaceholderImage)
}

/// Downloads and sets the User's Gravatar, given his email.
///
/// - Parameters:
/// - email: the user's email
/// - rating: expected image rating
/// - placeholderImage: Image to be used as Placeholder
/// This method uses deprecated types. Please check the deprecation warning in `GravatarRatings`. Also check out the UIImageView extension from the Gravatar iOS SDK as an alternative to download images. See: https://github.com/Automattic/Gravatar-SDK-iOS.
@available(*, deprecated, message: "Usage of the deprecated type: GravatarRatings.")
@objc
public func downloadGravatarWithEmail(_ email: String, rating: GravatarRatings = .default, placeholderImage: UIImage = .gravatarPlaceholderImage) {
let gravatarURL = Gravatar.gravatarUrl(for: email, size: gravatarDefaultSize(), rating: rating)

listenForGravatarChanges(forEmail: email)
downloadImage(from: gravatarURL, placeholderImage: placeholderImage)
}

/// Configures the UIImageView to listen for changes to the gravatar it is displaying
public func listenForGravatarChanges(forEmail trackedEmail: String) {
if let currentObersver = gravatarWrapper?.observer {
NotificationCenter.default.removeObserver(currentObersver)
gravatarWrapper = nil
}

let observer = NotificationCenter.default.addObserver(forName: .GravatarImageUpdateNotification, object: nil, queue: nil) { [weak self] (notification) in
guard let userInfo = notification.userInfo,
let email = userInfo[Defaults.emailKey] as? String,
email == trackedEmail,
let image = userInfo[Defaults.imageKey] as? UIImage else {
return
}

self?.image = image
}
gravatarWrapper = GravatarNotificationWrapper(observer: observer)
}

/// Stores the gravatar observer
///
fileprivate var gravatarWrapper: GravatarNotificationWrapper? {
get {
return objc_getAssociatedObject(self, &Defaults.gravatarWrapperKey) as? GravatarNotificationWrapper
}
set {
objc_setAssociatedObject(self, &Defaults.gravatarWrapperKey, newValue as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

/// Downloads the provided Gravatar.
///
/// - Parameters:
/// - gravatar: the user's Gravatar
/// - placeholder: Image to be used as Placeholder
/// - animate: enable/disable fade in animation
/// - failure: Callback block to be invoked when an error occurs while fetching the Gravatar image
///
/// This method uses deprecated types. Please check the deprecation warning in `GravatarRatings`. Also check out the UIImageView extension from the Gravatar iOS SDK as an alternative to download images. See: https://github.com/Automattic/Gravatar-SDK-iOS.
@available(*, deprecated, message: "Usage of the deprecated type: Gravatar.")
public func downloadGravatar(_ gravatar: Gravatar?, placeholder: UIImage, animate: Bool, failure: ((Error?) -> Void)? = nil) {
guard let gravatar = gravatar else {
self.image = placeholder
return
}

// Starting with iOS 10, it seems `initWithCoder` uses a default size
// of 1000x1000, which was messing with our size calculations for gravatars
// on newly created table cells.
// Calling `layoutIfNeeded()` forces UIKit to calculate the actual size.
layoutIfNeeded()

let size = Int(ceil(frame.width * UIScreen.main.scale))
let url = gravatar.urlWithSize(size)

self.downloadImage(from: url,
placeholderImage: placeholder,
success: { image in
guard image != self.image else {
return
}

self.image = image
if animate {
self.fadeInAnimation()
}
}, failure: { error in
failure?(error)
})
}

/// Sets an Image Override in both, AFNetworking's Private Cache + NSURLCache
///
/// - Parameters:
/// - image: new UIImage
/// - rating: rating for the new image.
/// - email: associated email of the new gravatar
/// - Note: You may want to use `updateGravatar(image:, email:)` instead
///
/// *WHY* is this required?. *WHY* life has to be so complicated?, is the universe against us?
/// This has been implemented as a workaround. During Upload, we want any async calls made to the
/// `downloadGravatar` API to return the "Fresh" image.
///
/// Note II:
/// We cannot just clear NSURLCache, since the helper that's supposed to do that, is broken since iOS 8.
/// Ref: Ref: http://blog.airsource.co.uk/2014/10/11/nsurlcache-ios8-broken/
///
/// P.s.:
/// Hope buddah, and the code reviewer, can forgive me for this hack.
///
@available(*, deprecated, message: "Usage of the deprecated type: GravatarRatings.")
@objc public func overrideGravatarImageCache(_ image: UIImage, rating: GravatarRatings, email: String) {
guard let gravatarURL = Gravatar.gravatarUrl(for: email, size: gravatarDefaultSize(), rating: rating) else {
return
}

listenForGravatarChanges(forEmail: email)
overrideImageCache(for: gravatarURL, with: image)
}

/// Updates the gravatar image for the given email, and notifies all gravatar image views
///
/// - Parameters:
/// - image: the new UIImage
/// - email: associated email of the new gravatar
@objc public func updateGravatar(image: UIImage, email: String?) {
self.image = image
guard let email = email else {
return
}
NotificationCenter.default.post(name: .GravatarImageUpdateNotification, object: self, userInfo: [Defaults.emailKey: email, Defaults.imageKey: image])
}

// MARK: - Private Helpers

/// Returns the required gravatar size. If the current view's size is zero, falls back to the default size.
///
private func gravatarDefaultSize() -> Int {
guard bounds.size.equalTo(.zero) == false else {
return Defaults.imageSize
}

let targetSize = max(bounds.width, bounds.height) * UIScreen.main.scale
return Int(targetSize)
}

/// Private helper structure: contains the default Gravatar parameters
///
private struct Defaults {
static let imageSize = 80
static var gravatarWrapperKey = 0x1000
static let emailKey = "email"
static let imageKey = "image"
}
}

public extension NSNotification.Name {
static let GravatarImageUpdateNotification = NSNotification.Name(rawValue: "GravatarImageUpdateNotification")
}
4 changes: 4 additions & 0 deletions Sources/WordPressUI/Extensions/UIImageView+Networking.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
import UIKit

#if SWIFT_PACKAGE
import WordPressUIObjC
#endif

public extension UIImageView {
enum ImageDownloadError: Error {
case noURLSpecifiedInRequest
Expand Down
7 changes: 7 additions & 0 deletions Sources/WordPressUIObjC/Extensions/NSString+Gravatar.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>

@interface NSString (Gravatar)

- (NSString *)sha256Hash;

@end
21 changes: 21 additions & 0 deletions Sources/WordPressUIObjC/Extensions/NSString+Gravatar.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#import "NSString+Gravatar.h"
#import <CommonCrypto/CommonDigest.h>


@implementation NSString (Gravatar)

- (NSString *)sha256Hash
{
const char *cStr = [self UTF8String];
unsigned char result[CC_SHA256_DIGEST_LENGTH];

CC_SHA256(cStr, (CC_LONG)strlen(cStr), result);

NSMutableString *hashString = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH*2];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[hashString appendFormat:@"%02x",result[i]];
}
return hashString;
}

@end
1 change: 1 addition & 0 deletions Sources/WordPressUIObjC/include/NSString+Gravatar.h

0 comments on commit 06b0b76

Please sign in to comment.