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