damus

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

NotificationsView.swift (6667B)


      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     @Binding var subtitle: String?
     60     
     61     @Environment(\.colorScheme) var colorScheme
     62     
     63     var body: some View {
     64         TabView(selection: $filter_state) {
     65             NotificationTab(
     66                 NotificationFilter(
     67                     state: .all,
     68                     fine_filter: filter.fine_filter
     69                 )
     70             )
     71             .tag(NotificationFilterState.all)
     72             
     73             NotificationTab(
     74                 NotificationFilter(
     75                     state: .zaps,
     76                     fine_filter: filter.fine_filter
     77                 )
     78             )
     79             .tag(NotificationFilterState.zaps)
     80             
     81             NotificationTab(
     82                 NotificationFilter(
     83                     state: .replies,
     84                     fine_filter: filter.fine_filter
     85                 )
     86             )
     87             .tag(NotificationFilterState.replies)
     88         }
     89         .toolbar {
     90             ToolbarItem(placement: .navigationBarTrailing) {
     91                 Button(
     92                     action: { state.nav.push(route: Route.NotificationSettings(settings: state.settings)) },
     93                     label: {
     94                         Image("settings")
     95                             .foregroundColor(.gray)
     96                     }
     97                 )
     98             }
     99             ToolbarItem(placement: .navigationBarTrailing) {
    100                 if would_filter_non_friends_from_notifications(contacts: state.contacts, state: filter_state, items: self.notifications.notifications) {
    101                     FriendsButton(filter: $filter.fine_filter)
    102                 }
    103             }
    104         }
    105         .onChange(of: filter.fine_filter) { val in
    106             state.settings.friend_filter = val
    107             self.subtitle = filter.fine_filter.description()
    108         }
    109         .onChange(of: filter_state) { val in
    110             filter.state = val
    111         }
    112         .onAppear {
    113             self.filter.fine_filter = state.settings.friend_filter
    114             self.subtitle = filter.fine_filter.description()
    115             filter.state = filter_state
    116         }
    117         .safeAreaInset(edge: .top, spacing: 0) {
    118             VStack(spacing: 0) {
    119                 CustomPicker(tabs: [
    120                     (NSLocalizedString("All", comment: "Label for filter for all notifications."), NotificationFilterState.all),
    121                     (NSLocalizedString("Zaps", comment: "Label for filter for zap notifications."), NotificationFilterState.zaps),
    122                     (NSLocalizedString("Mentions", comment: "Label for filter for seeing mention notifications (replies, etc)."), NotificationFilterState.replies),
    123                 ], selection: $filter_state)
    124                 Divider()
    125                     .frame(height: 1)
    126             }
    127             .background(colorScheme == .dark ? Color.black : Color.white)
    128         }
    129     }
    130     
    131     func NotificationTab(_ filter: NotificationFilter) -> some View {
    132         ScrollViewReader { scroller in
    133             ScrollView {
    134                 let notifs = Array(zip(1..., filter.filter(contacts: state.contacts, items: notifications.notifications)))
    135                 if notifs.isEmpty {
    136                     EmptyTimelineView()
    137                 } else {
    138                     LazyVStack(alignment: .leading) {
    139                         Color.white.opacity(0)
    140                             .id("startblock")
    141                             .frame(height: 5)
    142                         ForEach(notifs, id: \.0) { zip in
    143                             NotificationItemView(state: state, item: zip.1)
    144                         }
    145                     }
    146                     .background(GeometryReader { proxy -> Color in
    147                         DispatchQueue.main.async {
    148                             handle_scroll_queue(proxy, queue: self.notifications)
    149                         }
    150                         return Color.clear
    151                     })
    152                 }
    153             }
    154             .coordinateSpace(name: "scroll")
    155             .onReceive(handle_notify(.scroll_to_top)) { notif in
    156                 let _ = notifications.flush(state)
    157                 self.notifications.should_queue = false
    158                 scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
    159             }
    160         }
    161         .onAppear {
    162             let _ = notifications.flush(state)
    163         }
    164     }
    165 }
    166 
    167 struct NotificationsView_Previews: PreviewProvider {
    168     static var previews: some View {
    169         NotificationsView(state: test_damus_state, notifications: NotificationsModel(), filter: NotificationFilter(), subtitle: .constant(nil))
    170     }
    171 }
    172 
    173 func would_filter_non_friends_from_notifications(contacts: Contacts, state: NotificationFilterState, items: [NotificationItem]) -> Bool {
    174     for item in items {
    175         // this is only valid depending on which tab we're looking at
    176         if !state.filter(item) {
    177             continue
    178         }
    179         
    180         if item.would_filter({ ev in FriendFilter.friends_of_friends.filter(contacts: contacts, pubkey: ev.pubkey) }) {
    181             return true
    182         }
    183     }
    184     
    185     return false
    186 }
    187