damus

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

NotificationsView.swift (6725B)


      1 //
      2 //  NotificationsView.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-02-21.
      6 //
      7 
      8 import SwiftUI
      9 
     10 class NotificationFilter: ObservableObject, Equatable {
     11     @Published var state: NotificationFilterState
     12     @Published var fine_filter: FriendFilter
     13     
     14     static func == (lhs: NotificationFilter, rhs: NotificationFilter) -> Bool {
     15         return lhs.state == rhs.state && lhs.fine_filter == rhs.fine_filter
     16     }
     17     
     18     init(state: NotificationFilterState = .all, fine_filter: FriendFilter = .all) {
     19         self.state = state
     20         self.fine_filter = fine_filter
     21     }
     22     
     23     func filter(contacts: Contacts, items: [NotificationItem]) -> [NotificationItem] {
     24         
     25         return items.reduce(into: []) { acc, item in
     26             if !self.state.filter(item) {
     27                 return
     28             }
     29             
     30             if let item = item.filter({ self.fine_filter.filter(contacts: contacts, pubkey: $0.pubkey) }) {
     31                 acc.append(item)
     32             }
     33         }
     34     }
     35 }
     36 
     37 enum NotificationFilterState: String {
     38     case all
     39     case zaps
     40     case replies
     41     
     42     func filter(_ item: NotificationItem) -> Bool {
     43         switch self {
     44         case .all:
     45             return true
     46         case .replies:
     47             return item.is_reply != nil
     48         case .zaps:
     49             return item.is_zap != nil
     50         }
     51     }
     52 }
     53 
     54 struct NotificationsView: View {
     55     let state: DamusState
     56     @ObservedObject var notifications: NotificationsModel
     57     @StateObject var filter = NotificationFilter()
     58     @SceneStorage("NotificationsView.filter_state") var filter_state: NotificationFilterState = .all
     59     
     60     @Environment(\.colorScheme) var colorScheme
     61     
     62     var mystery: some View {
     63         let profile_txn = state.profiles.lookup(id: state.pubkey)
     64         let profile = profile_txn?.unsafeUnownedValue
     65         return VStack(spacing: 20) {
     66             Text("Wake up, \(Profile.displayName(profile: profile, pubkey: state.pubkey).displayName.truncate(maxLength: 50))", comment: "Text telling the user to wake up, where the argument is their display name.")
     67             Text("You are dreaming...", comment: "Text telling the user that they are dreaming.")
     68         }
     69         .id("what")
     70     }
     71     
     72     var body: some View {
     73         TabView(selection: $filter_state) {
     74             // This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
     75             mystery
     76             
     77             NotificationTab(
     78                 NotificationFilter(
     79                     state: .all,
     80                     fine_filter: filter.fine_filter
     81                 )
     82             )
     83             .tag(NotificationFilterState.all)
     84             
     85             NotificationTab(
     86                 NotificationFilter(
     87                     state: .zaps,
     88                     fine_filter: filter.fine_filter
     89                 )
     90             )
     91             .tag(NotificationFilterState.zaps)
     92             
     93             NotificationTab(
     94                 NotificationFilter(
     95                     state: .replies,
     96                     fine_filter: filter.fine_filter
     97                 )
     98             )
     99             .tag(NotificationFilterState.replies)
    100         }
    101         .toolbar {
    102             ToolbarItem(placement: .navigationBarTrailing) {
    103                 if would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications) {
    104                     FriendsButton(filter: $filter.fine_filter)
    105                 }
    106             }
    107         }
    108         .onChange(of: filter.fine_filter) { val in
    109             state.settings.friend_filter = val
    110         }
    111         .onChange(of: filter_state) { val in
    112             filter.state = val
    113         }
    114         .onAppear {
    115             self.filter.fine_filter = state.settings.friend_filter
    116             filter.state = filter_state
    117         }
    118         .safeAreaInset(edge: .top, spacing: 0) {
    119             VStack(spacing: 0) {
    120                 CustomPicker(selection: $filter_state, content: {
    121                     Text("All", comment: "Label for filter for all notifications.")
    122                         .tag(NotificationFilterState.all)
    123                     
    124                     Text("Zaps", comment: "Label for filter for zap notifications.")
    125                         .tag(NotificationFilterState.zaps)
    126                     
    127                     Text("Mentions", comment: "Label for filter for seeing mention notifications (replies, etc).")
    128                         .tag(NotificationFilterState.replies)
    129                     
    130                 })
    131                 Divider()
    132                     .frame(height: 1)
    133             }
    134             .background(colorScheme == .dark ? Color.black : Color.white)
    135         }
    136     }
    137     
    138     func NotificationTab(_ filter: NotificationFilter) -> some View {
    139         ScrollViewReader { scroller in
    140             ScrollView {
    141                 LazyVStack(alignment: .leading) {
    142                     Color.white.opacity(0)
    143                         .id("startblock")
    144                         .frame(height: 5)
    145                     let notifs = Array(zip(1..., filter.filter(contacts: state.contacts, items: notifications.notifications)))
    146                     ForEach(notifs, id: \.0) { zip in
    147                         NotificationItemView(state: state, item: zip.1)
    148                     }
    149                 }
    150                 .background(GeometryReader { proxy -> Color in
    151                     DispatchQueue.main.async {
    152                         handle_scroll_queue(proxy, queue: self.notifications)
    153                     }
    154                     return Color.clear
    155                 })
    156             }
    157             .coordinateSpace(name: "scroll")
    158             .onReceive(handle_notify(.scroll_to_top)) { notif in
    159                 let _ = notifications.flush(state)
    160                 self.notifications.should_queue = false
    161                 scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
    162             }
    163         }
    164         .onAppear {
    165             let _ = notifications.flush(state)
    166         }
    167     }
    168 }
    169 
    170 struct NotificationsView_Previews: PreviewProvider {
    171     static var previews: some View {
    172         NotificationsView(state: test_damus_state, notifications: NotificationsModel(), filter: NotificationFilter())
    173     }
    174 }
    175 
    176 func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
    177     for item in items {
    178         // this is only valid depending on which tab we're looking at
    179         if !state.filter(item) {
    180             continue
    181         }
    182         
    183         if item.would_filter({ ev in FriendFilter.friends.filter(contacts: contacts, pubkey: ev.pubkey) }) {
    184             return true
    185         }
    186     }
    187     
    188     return false
    189 }
    190