eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2#if canImport(UIKit)
3import UIKit
4#elseif canImport(AppKit)
5import AppKit
6#endif
7
8// The initials fallback for a notification's sender image: without one, a
9// communication notification shows the system's gray silhouette — so senders
10// with no profile photo get the SAME monogram the app draws for them (Avatar
11// on iOS, the Mac's circles): first initial, white, over a colour hashed from
12// the name. Keep initial(for:)/paletteIndex(for:) in lockstep with
13// ios/LChat/Views/Avatar.swift so a person's banner face matches their in-app
14// face. Rendered square — the notification pipeline circle-crops it.
15public enum MonogramImage {
16 /// The uppercased first character, "?" for an all-whitespace name —
17 /// Avatar.initial verbatim.
18 static func initial(for name: String) -> String {
19 let s = String(name.trimmingCharacters(in: .whitespaces).prefix(1)).uppercased()
20 return s.isEmpty ? "?" : s
21 }
22
23 /// djb2 over the name's UTF-8, mod the 8-colour palette — Avatar.color verbatim.
24 static func paletteIndex(for name: String) -> Int {
25 var h = 5381
26 for b in name.utf8 { h = (h &* 33) &+ Int(b) }
27 return abs(h) % 8
28 }
29
30 #if canImport(UIKit) || canImport(AppKit)
31 // The concrete twins of Avatar's SwiftUI palette (.blue, .purple, …).
32 #if canImport(UIKit)
33 private static let palette: [UIColor] = [
34 .systemBlue, .systemPurple, .systemPink, .systemOrange,
35 .systemGreen, .systemTeal, .systemIndigo, .systemRed,
36 ]
37 #else
38 private static let palette: [NSColor] = [
39 .systemBlue, .systemPurple, .systemPink, .systemOrange,
40 .systemGreen, .systemTeal, .systemIndigo, .systemRed,
41 ]
42 #endif
43
44 /// PNG bytes of the monogram tile, or nil if rendering fails.
45 public static func pngData(name: String, size: CGFloat = 256) -> Data? {
46 let color = palette[paletteIndex(for: name)]
47 let text = initial(for: name) as NSString
48 let rect = CGRect(x: 0, y: 0, width: size, height: size)
49 #if canImport(UIKit)
50 let attrs: [NSAttributedString.Key: Any] = [
51 .font: UIFont.systemFont(ofSize: size * 0.42, weight: .semibold),
52 .foregroundColor: UIColor.white,
53 ]
54 let img = UIGraphicsImageRenderer(size: rect.size).image { ctx in
55 color.setFill()
56 ctx.fill(rect)
57 let s = text.size(withAttributes: attrs)
58 text.draw(at: CGPoint(x: (size - s.width) / 2, y: (size - s.height) / 2),
59 withAttributes: attrs)
60 }
61 return img.pngData()
62 #else
63 let attrs: [NSAttributedString.Key: Any] = [
64 .font: NSFont.systemFont(ofSize: size * 0.42, weight: .semibold),
65 .foregroundColor: NSColor.white,
66 ]
67 let img = NSImage(size: rect.size)
68 img.lockFocus()
69 color.setFill()
70 rect.fill()
71 let s = text.size(withAttributes: attrs)
72 text.draw(at: CGPoint(x: (size - s.width) / 2, y: (size - s.height) / 2),
73 withAttributes: attrs)
74 img.unlockFocus()
75 guard let tiff = img.tiffRepresentation,
76 let rep = NSBitmapImageRep(data: tiff) else { return nil }
77 return rep.representation(using: .png, properties: [:])
78 #endif
79 }
80 #endif
81}
82