damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }