ProfilePicView.swift (4731B)
1 // 2 // ProfilePicView.swift 3 // damus 4 // 5 // Created by William Casarin on 2022-04-16. 6 // 7 8 import SwiftUI 9 import Kingfisher 10 11 let PFP_SIZE: CGFloat = 52.0 12 13 func id_to_color(_ id: String) -> Color { 14 return hex_to_rgb(id) 15 } 16 17 func highlight_color(_ h: Highlight) -> Color { 18 switch h { 19 case .main: return Color.red 20 case .reply: return Color.black 21 case .none: return Color.black 22 case .custom(let c, _): return c 23 } 24 } 25 26 func pfp_line_width(_ h: Highlight) -> CGFloat { 27 switch h { 28 case .reply: return 0 29 case .none: return 0 30 case .main: return 3 31 case .custom(_, let lw): return CGFloat(lw) 32 } 33 } 34 35 struct InnerProfilePicView: View { 36 37 let url: URL? 38 let fallbackUrl: URL? 39 let pubkey: String 40 let size: CGFloat 41 let highlight: Highlight 42 43 var PlaceholderColor: Color { 44 return id_to_color(pubkey) 45 } 46 47 var Placeholder: some View { 48 PlaceholderColor 49 .frame(width: size, height: size) 50 .clipShape(Circle()) 51 .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) 52 .padding(2) 53 } 54 55 var body: some View { 56 ZStack { 57 Color(uiColor: .systemBackground) 58 59 KFAnimatedImage(url) 60 .imageContext(.pfp) 61 .onFailure(fallbackUrl: fallbackUrl, cacheKey: url?.absoluteString) 62 .cancelOnDisappear(true) 63 .configure { view in 64 view.framePreloadCount = 3 65 } 66 .placeholder { _ in 67 Placeholder 68 } 69 .scaledToFill() 70 } 71 .frame(width: size, height: size) 72 .clipShape(Circle()) 73 .overlay(Circle().stroke(highlight_color(highlight), lineWidth: pfp_line_width(highlight))) 74 } 75 } 76 77 struct ProfilePicView: View { 78 let pubkey: String 79 let size: CGFloat 80 let highlight: Highlight 81 let profiles: Profiles 82 83 @State var picture: String? 84 85 init (pubkey: String, size: CGFloat, highlight: Highlight, profiles: Profiles, picture: String? = nil) { 86 self.pubkey = pubkey 87 self.profiles = profiles 88 self.size = size 89 self.highlight = highlight 90 self._picture = State(initialValue: picture) 91 } 92 93 var body: some View { 94 InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: pubkey, profiles: profiles), fallbackUrl: URL(string: robohash(pubkey)), pubkey: pubkey, size: size, highlight: highlight) 95 .onReceive(handle_notify(.profile_updated)) { notif in 96 let updated = notif.object as! ProfileUpdate 97 98 guard updated.pubkey == self.pubkey else { 99 return 100 } 101 102 if let pic = updated.profile.picture { 103 self.picture = pic 104 } 105 } 106 } 107 } 108 109 func get_profile_url(picture: String?, pubkey: String, profiles: Profiles) -> URL { 110 let pic = picture ?? profiles.lookup(id: pubkey)?.picture ?? robohash(pubkey) 111 if let url = URL(string: pic) { 112 return url 113 } 114 return URL(string: robohash(pubkey))! 115 } 116 117 func make_preview_profiles(_ pubkey: String) -> Profiles { 118 let profiles = Profiles() 119 let picture = "http://cdn.jb55.com/img/red-me.jpg" 120 let profile = Profile(name: "jb55", display_name: "William Casarin", about: "It's me", picture: picture, banner: "", website: "https://jb55.com", lud06: nil, lud16: nil, nip05: "jb55.com") 121 let ts_profile = TimestampedProfile(profile: profile, timestamp: 0) 122 profiles.add(id: pubkey, profile: ts_profile) 123 return profiles 124 } 125 126 struct ProfilePicView_Previews: PreviewProvider { 127 static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846" 128 129 static var previews: some View { 130 ProfilePicView( 131 pubkey: pubkey, 132 size: 100, 133 highlight: .none, 134 profiles: make_preview_profiles(pubkey)) 135 } 136 } 137 138 func hex_to_rgb(_ hex: String) -> Color { 139 guard hex.count >= 6 else { 140 return Color.white 141 } 142 143 let arr = Array(hex.utf8) 144 var rgb: [UInt8] = [] 145 var i: Int = arr.count - 12 146 147 while i < arr.count { 148 let cs1 = arr[i] 149 let cs2 = arr[i+1] 150 151 guard let c1 = char_to_hex(cs1) else { 152 return Color.black 153 } 154 155 guard let c2 = char_to_hex(cs2) else { 156 return Color.black 157 } 158 159 rgb.append((c1 << 4) | c2) 160 i += 2 161 } 162 163 return Color.init( 164 .sRGB, 165 red: Double(rgb[0]) / 255, 166 green: Double(rgb[1]) / 255, 167 blue: Double(rgb[2]) / 255, 168 opacity: 1 169 ) 170 }