damus

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

EventGroupView.swift (9562B)


      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? = nil) -> 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         Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, pubkeys: pubkeys))")
    192     }
    193     
    194     func ZapIcon(_ zapgrp: ZapGroup) -> some View {
    195         let fmt = format_msats_abbrev(zapgrp.msat_total)
    196         return VStack(alignment: .center) {
    197             Image("zap.fill")
    198                 .foregroundColor(.orange)
    199             Text(fmt)
    200                 .foregroundColor(Color.orange)
    201         }
    202     }
    203     
    204     var GroupIcon: some View {
    205         Group {
    206             switch group {
    207             case .repost:
    208                 Image("repost")
    209                     .foregroundColor(DamusColors.green)
    210             case .reaction:
    211                 LINEAR_GRADIENT
    212                     .mask(Image("shaka.fill")
    213                         .resizable()
    214                         .aspectRatio(contentMode: .fit)
    215                     )
    216                     .frame(width: 20, height: 20)
    217             case .profile_zap(let zapgrp):
    218                 ZapIcon(zapgrp)
    219             case .zap(let zapgrp):
    220                 ZapIcon(zapgrp)
    221             }
    222         }
    223     }
    224     
    225     var body: some View {
    226         HStack(alignment: .top) {
    227             GroupIcon
    228                 .frame(width: PFP_SIZE + 10)
    229             
    230             VStack(alignment: .leading) {
    231                 let unique_pubkeys = event_group_unique_pubkeys(profiles: state.profiles, group: group)
    232 
    233                 ProfilePicturesView(state: state, pubkeys: unique_pubkeys)
    234                 
    235                 if let event {
    236                     let thread = ThreadModel(event: event, damus_state: state)
    237                     NavigationLink(value: Route.Thread(thread: thread)) {
    238                         VStack(alignment: .leading) {
    239                             GroupDescription(unique_pubkeys)
    240                             EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
    241                                 .padding([.top], 1)
    242                                 .padding([.trailing])
    243                                 .foregroundColor(.gray)
    244                         }
    245                     }
    246                     .buttonStyle(.plain)
    247                 } else {
    248                     GroupDescription(unique_pubkeys)
    249                 }
    250             }
    251         }
    252         .padding([.top], 6)
    253     }
    254 }
    255 
    256 struct EventGroupView_Previews: PreviewProvider {
    257     static var previews: some View {
    258         VStack {
    259             EventGroupView(state: test_damus_state, event: test_note, group: .repost(test_event_group))
    260                 .frame(height: 200)
    261                 .padding()
    262             
    263             EventGroupView(state: test_damus_state, event: test_note, group: .reaction(test_event_group))
    264                 .frame(height: 200)
    265                 .padding()
    266         }
    267     }
    268     
    269 }
    270