-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This reverts commit 5f04d80.
- Loading branch information
Showing
6 changed files
with
375 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
194
Sources/WordPressUI/Extensions/UIImageView+Gravatar.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#import <Foundation/Foundation.h> | ||
|
||
@interface NSString (Gravatar) | ||
|
||
- (NSString *)sha256Hash; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../Extensions/NSString+Gravatar.h |