damus

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

EventGroupView.swift (9588B)


      1 //
      2 //  RepostGroupView.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-02-21.
      6 //
      7 
      8 import SwiftUI
      9 
     10 
     11 enum EventGroupType {
     12     case repost(EventGroup)
     13     case reaction(EventGroup)
     14     case zap(ZapGroup)
     15     case profile_zap(ZapGroup)
     16     
     17     var is_note_zap: Bool {
     18         switch self {
     19         case .repost: return false
     20         case .reaction: return false
     21         case .zap: return true
     22         case .profile_zap: return false
     23         }
     24     }
     25     
     26     var zap_group: ZapGroup? {
     27         switch self {
     28         case .profile_zap(let grp):
     29             return grp
     30         case .zap(let grp):
     31             return grp
     32         case .reaction:
     33             return nil
     34         case .repost:
     35             return nil
     36         }
     37     }
     38     
     39     var events: [NostrEvent] {
     40         switch self {
     41         case .repost(let grp):
     42             return grp.events
     43         case .reaction(let grp):
     44             return grp.events
     45         case .zap(let zapgrp):
     46             return zapgrp.zap_requests()
     47         case .profile_zap(let zapgrp):
     48             return zapgrp.zap_requests()
     49         }
     50     }
     51 }
     52 
     53 enum ReactingTo {
     54     case your_note
     55     case tagged_in
     56     case your_profile
     57 }
     58 
     59 func determine_reacting_to(our_pubkey: Pubkey, ev: NostrEvent?) -> ReactingTo {
     60     guard let ev else {
     61         return .your_profile
     62     }
     63     
     64     if ev.pubkey == our_pubkey {
     65         return .your_note
     66     }
     67     
     68     return .tagged_in
     69 }
     70 
     71 func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [Pubkey] {
     72     var seen = Set<Pubkey>()
     73     var sorted = [Pubkey]()
     74 
     75     if let zapgrp = group.zap_group {
     76         let zaps = zapgrp.zaps
     77 
     78         for i in 0..<zaps.count {
     79             let zap = zapgrp.zaps[i]
     80             let pubkey: Pubkey
     81 
     82             if zap.is_anon {
     83                 pubkey = ANON_PUBKEY
     84             } else {
     85                 pubkey = zap.request.ev.pubkey
     86             }
     87 
     88             if !seen.contains(pubkey) {
     89                 seen.insert(pubkey)
     90                 sorted.append(pubkey)
     91             }
     92         }
     93     } else {
     94         let events = group.events
     95 
     96         for i in 0..<events.count {
     97             let ev = events[i]
     98             let pubkey = ev.pubkey
     99             if !seen.contains(pubkey) {
    100                 seen.insert(pubkey)
    101                 sorted.append(pubkey)
    102             }
    103         }
    104     }
    105 
    106     return sorted
    107 }
    108 
    109 /**
    110  Returns a notification string describing user actions in response to an event group type.
    111 
    112  The localization keys read by this function are the following (although some keys may not actually be used in practice):
    113 
    114  "??" - returned when there are no events associated with the specified event group type.
    115 
    116  "reacted_tagged_in_1" - returned when 1 reaction occurred to a post that the current user was tagged in
    117  "reacted_tagged_in_2" - returned when 2 reactions occurred to a post that the current user was tagged in
    118  "reacted_tagged_in_3" - returned when 3 or more reactions occurred to a post that the current user was tagged in
    119  "reacted_your_note_1" - returned when 1 reaction occurred to the current user's post
    120  "reacted_your_note_2" - returned when 2 reactions occurred to the current user's post
    121  "reacted_your_note_3" - returned when 3 or more reactions occurred to the current user's post
    122  "reacted_your_profile_1" - returned when 1 reaction occurred to the current user's profile
    123  "reacted_your_profile_2" - returned when 2 reactions occurred to the current user's profile
    124  "reacted_your_profile_3" - returned when 3 or more reactions occurred to the current user's profile
    125 
    126  "reposted_tagged_in_1" - returned when 1 repost occurred to a post that the current user was tagged in
    127  "reposted_tagged_in_2" - returned when 2 reposts occurred to a post that the current user was tagged in
    128  "reposted_tagged_in_3" - returned when 3 or more reposts occurred to a post that the current user was tagged in
    129  "reposted_your_note_1" - returned when 1 repost occurred to the current user's post
    130  "reposted_your_note_2" - returned when 2 reposts occurred to the current user's post
    131  "reposted_your_note_3" - returned when 3 or more reposts occurred to the current user's post
    132  "reposted_your_profile_1" - returned when 1 repost occurred to the current user's profile
    133  "reposted_your_profile_2" - returned when 2 reposts occurred to the current user's profile
    134  "reposted_your_profile_3" - returned when 3 or more reposts occurred to the current user's profile
    135 
    136  "zapped_tagged_in_1" - returned when 1 zap occurred to a post that the current user was tagged in
    137  "zapped_tagged_in_2" - returned when 2 zaps occurred to a post that the current user was tagged in
    138  "zapped_tagged_in_3" - returned when 3 or more zaps occurred to a post that the current user was tagged in
    139  "zapped_your_note_1" - returned when 1 zap occurred to the current user's post
    140  "zapped_your_note_2" - returned when 2 zaps occurred to the current user's post
    141  "zapped_your_note_3" - returned when 3 or more zaps occurred to the current user's post
    142  "zapped_your_profile_1" - returned when 1 zap occurred to the current user's profile
    143  "zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
    144  "zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
    145  */
    146 func reacting_to_text(profiles: Profiles, our_pubkey: Pubkey, group: EventGroupType, ev: NostrEvent?, pubkeys: [Pubkey], locale: Locale = Locale.current) -> String {
    147     if group.events.count == 0 {
    148         return "??"
    149     }
    150 
    151     let verb = reacting_to_verb(group: group)
    152     let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
    153     let localization_key = "\(verb)_\(reacting_to)_\(min(pubkeys.count, 3))"
    154     let format = localizedStringFormat(key: localization_key, locale: locale)
    155 
    156     switch pubkeys.count {
    157     case 1:
    158         let display_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
    159 
    160         return String(format: format, locale: locale, display_name)
    161     case 2:
    162         let alice_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
    163         let bob_name = event_author_name(profiles: profiles, pubkey: pubkeys[1])
    164 
    165         return String(format: format, locale: locale, alice_name, bob_name)
    166     default:
    167         let alice_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
    168         let count = pubkeys.count - 1
    169 
    170         return String(format: format, locale: locale, count, alice_name)
    171     }
    172 }
    173 
    174 func reacting_to_verb(group: EventGroupType) -> String {
    175     switch group {
    176     case .reaction:
    177         return "reacted"
    178     case .repost:
    179         return "reposted"
    180     case .zap, .profile_zap:
    181         return "zapped"
    182     }
    183 }
    184 
    185 struct EventGroupView: View {
    186     let state: DamusState
    187     let event: NostrEvent?
    188     let group: EventGroupType
    189 
    190     func GroupDescription(_ pubkeys: [Pubkey]) -> some View {
    191         let text = reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, pubkeys: pubkeys)
    192         return Text(text)
    193     }
    194     
    195     func ZapIcon(_ zapgrp: ZapGroup) -> some View {
    196         let fmt = format_msats_abbrev(zapgrp.msat_total)
    197         return VStack(alignment: .center) {
    198             Image("zap.fill")
    199                 .foregroundColor(.orange)
    200             Text(fmt)
    201                 .foregroundColor(Color.orange)
    202         }
    203     }
    204     
    205     var GroupIcon: some View {
    206         Group {
    207             switch group {
    208             case .repost:
    209                 Image("repost")
    210                     .foregroundColor(DamusColors.green)
    211             case .reaction:
    212                 LINEAR_GRADIENT
    213                     .mask(Image("shaka.fill")
    214                         .resizable()
    215                         .aspectRatio(contentMode: .fit)
    216                     )
    217                     .frame(width: 20, height: 20)
    218             case .profile_zap(let zapgrp):
    219                 ZapIcon(zapgrp)
    220             case .zap(let zapgrp):
    221                 ZapIcon(zapgrp)
    222             }
    223         }
    224     }
    225     
    226     var body: some View {
    227         HStack(alignment: .top) {
    228             GroupIcon
    229                 .frame(width: PFP_SIZE + 10)
    230             
    231             VStack(alignment: .leading) {
    232                 let unique_pubkeys = event_group_unique_pubkeys(profiles: state.profiles, group: group)
    233 
    234                 ProfilePicturesView(state: state, pubkeys: unique_pubkeys)
    235                 
    236                 if let event {
    237                     let thread = ThreadModel(event: event, damus_state: state)
    238                     NavigationLink(value: Route.Thread(thread: thread)) {
    239                         VStack(alignment: .leading) {
    240                             GroupDescription(unique_pubkeys)
    241                             EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
    242                                 .padding([.top], 1)
    243                                 .padding([.trailing])
    244                                 .foregroundColor(.gray)
    245                         }
    246                     }
    247                     .buttonStyle(.plain)
    248                 } else {
    249                     GroupDescription(unique_pubkeys)
    250                 }
    251             }
    252         }
    253         .padding([.top], 6)
    254     }
    255 }
    256 
    257 struct EventGroupView_Previews: PreviewProvider {
    258     static var previews: some View {
    259         VStack {
    260             EventGroupView(state: test_damus_state, event: test_note, group: .repost(test_event_group))
    261                 .frame(height: 200)
    262                 .padding()
    263             
    264             EventGroupView(state: test_damus_state, event: test_note, group: .reaction(test_event_group))
    265                 .frame(height: 200)
    266                 .padding()
    267         }
    268     }
    269     
    270 }
    271