diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index c4607085f..3984ce475 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; 7BDCFC092421894900641C39 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; 7BDCFC0B2421EB7600641C39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; + 7BF3FF002505B8E400609570 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF3FEFF2505B8E400609570 /* PlaceholderIcon.swift */; }; A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; }; A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; }; A123C14916F902EE000AE905 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A163E8AA16F3F6A90094D68B /* Security.framework */; }; @@ -1247,6 +1248,7 @@ 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* LokiPushNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LokiPushNotificationService.entitlements; sourceTree = ""; }; 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtensionContext.swift; sourceTree = ""; }; + 7BF3FEFF2505B8E400609570 /* PlaceholderIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderIcon.swift; sourceTree = ""; }; 8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = ""; }; 8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = ""; }; 948239851C08032C842937CC /* Pods-SignalMessaging.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.test.xcconfig"; sourceTree = ""; }; @@ -2601,6 +2603,7 @@ B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */, 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */, 241C6310231F5C4400B4198E /* UIColor+Helper.swift */, + 7BF3FEFF2505B8E400609570 /* PlaceholderIcon.swift */, ); path = "Jazz Icon"; sourceTree = ""; @@ -3700,6 +3703,7 @@ 241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */, 34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */, 459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */, + 7BF3FF002505B8E400609570 /* PlaceholderIcon.swift in Sources */, 340872D622397E6800CB25B0 /* AttachmentCaptionToolbar.swift in Sources */, 34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */, 4C948FF72146EB4800349F0D /* BlockListCache.swift in Sources */, diff --git a/SignalMessaging/Loki/Jazz Icon/Identicon+ObjC.swift b/SignalMessaging/Loki/Jazz Icon/Identicon+ObjC.swift index d24bf7bb8..b9afee170 100644 --- a/SignalMessaging/Loki/Jazz Icon/Identicon+ObjC.swift +++ b/SignalMessaging/Loki/Jazz Icon/Identicon+ObjC.swift @@ -10,4 +10,13 @@ public final class Identicon : NSObject { let image = renderer.image { iconLayer.render(in: $0.cgContext) } return image } + + @objc public static func generatePlaceholderIcon(seed: String, text: String, size: CGFloat) -> UIImage { + let icon = PlaceholderIcon(seed: seed) + let iconLayer = icon.generateLayer(ofSize: size, with: text.substring(to: 1)) + let rect = CGRect(origin: CGPoint.zero, size: iconLayer.frame.size) + let renderer = UIGraphicsImageRenderer(size: rect.size) + let image = renderer.image { iconLayer.render(in: $0.cgContext) } + return image + } } diff --git a/SignalMessaging/Loki/Jazz Icon/PlaceholderIcon.swift b/SignalMessaging/Loki/Jazz Icon/PlaceholderIcon.swift new file mode 100644 index 000000000..cb19c6ead --- /dev/null +++ b/SignalMessaging/Loki/Jazz Icon/PlaceholderIcon.swift @@ -0,0 +1,67 @@ + +import CryptoSwift + +public class PlaceholderIcon { + private let seed: Int + // Colour palette + private var colours: [UIColor] = [ + 0x5ff8b0, + 0x26cdb9, + 0xf3c615, + 0xfcac5a + ].map { UIColor(rgb: $0) } + + init(seed: Int, colours: [UIColor]? = nil) { + self.seed = seed + if let colours = colours { + self.colours = colours + } + } + + convenience init(seed: String, colours: [UIColor]? = nil) { + // Ensure we have a correct hash + var hash = seed + if !hash.matches("^[0-9A-Fa-f]+$") || hash.count < 12 { hash = seed.sha512() } + + guard let number = Int(hash.substring(to: 12), radix: 16) else { + owsFailDebug("[PlaceholderIcon] Failed to generate number from seed string: \(seed)") + self.init(seed: 0, colours: colours) + return + } + + self.init(seed: number, colours: colours) + } + + public func generateLayer(ofSize diameter: CGFloat, with text: String) -> CALayer { + let colour = self.colours[seed % self.colours.count].cgColor + let base = getTextLayer(with: diameter, colour: colour, text: text) + base.masksToBounds = true + return base + } + + private func getTextLayer(with diameter: CGFloat, colour: CGColor? = nil, text: String) -> CALayer { + let font = UIFont.boldSystemFont(ofSize: diameter/2) + let height = NSString(string: text).boundingRect(with: CGSize(width: diameter, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil).height + let frame = CGRect(x: 0, y: (diameter - height)/2, width: diameter, height: height) + + let layer = CATextLayer() + layer.frame = frame + layer.foregroundColor = UIColor.white.cgColor + layer.contentsScale = UIScreen.main.scale + + let fontName = font.fontName; + let fontRef = CGFont(fontName as CFString); + layer.font = fontRef; + layer.fontSize = font.pointSize; + layer.alignmentMode = .center + + layer.string = text + + let base = CALayer() + base.frame = CGRect(x: 0, y: 0, width: diameter, height: diameter) + base.backgroundColor = colour + base.addSublayer(layer) + + return base + } +} diff --git a/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift b/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift index 5f825a902..29487b76f 100644 --- a/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift +++ b/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift @@ -46,8 +46,9 @@ public final class ProfilePictureView : UIView { @objc public func update() { AssertIsOnMainThread() func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage? { + OWSLogger.debug("[Ryan] \(hexEncodedPublicKey)") guard !hexEncodedPublicKey.isEmpty else { return nil } - return OWSProfileManager.shared().profileAvatar(forRecipientId: hexEncodedPublicKey) ?? Identicon.generateIcon(string: hexEncodedPublicKey, size: size) + return OWSProfileManager.shared().profileAvatar(forRecipientId: hexEncodedPublicKey) ?? Identicon.generatePlaceholderIcon(seed: hexEncodedPublicKey, text: OWSProfileManager.shared().profileNameForRecipient(withID: hexEncodedPublicKey) ?? hexEncodedPublicKey, size: size) } let size: CGFloat if let additionalHexEncodedPublicKey = additionalHexEncodedPublicKey, !isRSSFeed, openGroupProfilePicture == nil {