damus

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

commit 2b2d124495ca6f9e4673ce8ab7bbe6f0987627d0
parent 2a2af056eb53419671b11eed2d65fcbe268a589c
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 11 Apr 2023 12:00:39 -0700

Configurable notification dots

Changelog-Added: Make notification dots configurable

Diffstat:
Mdamus/ContentView.swift | 2+-
Mdamus/Models/HomeModel.swift | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mdamus/Models/UserSettingsStore.swift | 7+++++++
Mdamus/Views/MainTabView.swift | 21+++++++++++++++------
Mdamus/Views/Notifications/NotificationsView.swift | 17+++++++++--------
Mdamus/Views/Settings/NotificationSettingsView.swift | 25++++++++++++++++++++++++-
6 files changed, 109 insertions(+), 37 deletions(-)

diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -291,7 +291,7 @@ struct ContentView: View { } .navigationViewStyle(.stack) - TabBar(new_events: $home.new_events, selected: $selected_timeline, isSidebarVisible: $isSideBarOpened, action: switch_timeline) + TabBar(new_events: $home.new_events, selected: $selected_timeline, isSidebarVisible: $isSideBarOpened, settings: damus.settings, action: switch_timeline) .padding([.bottom], 8) .background(Color(uiColor: .systemBackground).ignoresSafeArea()) } diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift @@ -8,26 +8,19 @@ import Foundation import UIKit -struct NewEventsBits { - let bits: Int - - init() { - bits = 0 - } - - init (prev: NewEventsBits, setting: Timeline) { - self.bits = prev.bits | timeline_bit(setting) - } - - init (prev: NewEventsBits, unsetting: Timeline) { - self.bits = prev.bits & ~timeline_bit(unsetting) - } - - func is_set(_ timeline: Timeline) -> Bool { - let notification_bit = timeline_bit(timeline) - return (bits & notification_bit) == notification_bit - } - +struct NewEventsBits: OptionSet { + let rawValue: Int + + static let home = NewEventsBits(rawValue: 1 << 0) + static let zaps = NewEventsBits(rawValue: 1 << 1) + static let mentions = NewEventsBits(rawValue: 1 << 2) + static let reposts = NewEventsBits(rawValue: 1 << 3) + static let likes = NewEventsBits(rawValue: 1 << 4) + static let search = NewEventsBits(rawValue: 1 << 5) + static let dms = NewEventsBits(rawValue: 1 << 6) + + static let all = NewEventsBits(rawValue: 0xFFFFFFFF) + static let notifications: NewEventsBits = [.zaps, .likes, .reposts, .mentions] } class HomeModel: ObservableObject { @@ -901,6 +894,45 @@ func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, o return new_events } +func determine_event_notifications(_ ev: NostrEvent) -> NewEventsBits { + guard let kind = ev.known_kind else { + return [] + } + + if kind == .zap { + return [.zaps] + } + + if kind == .boost { + return [.reposts] + } + + if kind == .text { + return [.mentions] + } + + if kind == .like { + return [.likes] + } + + return [] +} + +func timeline_to_notification_bits(_ timeline: Timeline, ev: NostrEvent?) -> NewEventsBits { + switch timeline { + case .home: + return [.home] + case .notifications: + if let ev { + return determine_event_notifications(ev) + } + return [.notifications] + case .search: + return [.search] + case .dms: + return [.dms] + } +} /// A helper to determine if we need to notify the user of new events func handle_last_events(new_events: NewEventsBits, ev: NostrEvent, timeline: Timeline, shouldNotify: Bool = true) -> NewEventsBits? { @@ -909,7 +941,7 @@ func handle_last_events(new_events: NewEventsBits, ev: NostrEvent, timeline: Tim if last_ev == nil || last_ev!.created_at < ev.created_at { save_last_event(ev, timeline: timeline) if shouldNotify { - return NewEventsBits(prev: new_events, setting: timeline) + return new_events.union(timeline_to_notification_bits(timeline, ev: ev)) } } diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift @@ -176,6 +176,12 @@ class UserSettingsStore: ObservableObject { } } + @Published var notification_indicators: Int { + didSet { + UserDefaults.standard.set(notification_indicators, forKey: "notification_indicators") + } + } + @Published var truncate_mention_text: Bool { didSet { UserDefaults.standard.set(truncate_mention_text, forKey: "truncate_mention_text") @@ -280,6 +286,7 @@ class UserSettingsStore: ObservableObject { repost_notification = UserDefaults.standard.object(forKey: "repost_notification") as? Bool ?? true like_notification = UserDefaults.standard.object(forKey: "like_notification") as? Bool ?? true dm_notification = UserDefaults.standard.object(forKey: "dm_notification") as? Bool ?? true + notification_indicators = UserDefaults.standard.object(forKey: "notification_indicators") as? Int ?? NewEventsBits.all.rawValue notification_only_from_following = UserDefaults.standard.object(forKey: "notification_only_from_following") as? Bool ?? false translate_dms = UserDefaults.standard.object(forKey: "translate_dms") as? Bool ?? false truncate_timeline_text = UserDefaults.standard.object(forKey: "truncate_timeline_text") as? Bool ?? false diff --git a/damus/Views/MainTabView.swift b/damus/Views/MainTabView.swift @@ -27,6 +27,12 @@ func timeline_bit(_ timeline: Timeline) -> Int { } } +func show_indicator(timeline: Timeline, current: NewEventsBits, indicator_setting: Int) -> Bool { + if timeline == .notifications { + return (current.rawValue & indicator_setting & NewEventsBits.notifications.rawValue) > 0 + } + return (current.rawValue & indicator_setting) == timeline_to_notification_bits(timeline, ev: nil).rawValue +} struct TabButton: View { let timeline: Timeline @@ -35,13 +41,14 @@ struct TabButton: View { @Binding var new_events: NewEventsBits @Binding var isSidebarVisible: Bool + let settings: UserSettingsStore let action: (Timeline) -> () var body: some View { ZStack(alignment: .center) { Tab - if new_events.is_set(timeline) { + if show_indicator(timeline: timeline, current: new_events, indicator_setting: settings.notification_indicators) { Circle() .size(CGSize(width: 8, height: 8)) .frame(width: 10, height: 10, alignment: .topTrailing) @@ -55,7 +62,8 @@ struct TabButton: View { var Tab: some View { Button(action: { action(timeline) - new_events = NewEventsBits(prev: new_events, unsetting: timeline) + let bits = timeline_to_notification_bits(timeline, ev: nil) + new_events = NewEventsBits(rawValue: new_events.rawValue & ~bits.rawValue) isSidebarVisible = false }) { Label("", systemImage: selected == timeline ? "\(img).fill" : img) @@ -72,16 +80,17 @@ struct TabBar: View { @Binding var selected: Timeline? @Binding var isSidebarVisible: Bool + let settings: UserSettingsStore let action: (Timeline) -> () var body: some View { VStack { Divider() HStack { - TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("1") - TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("2") - TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("3") - TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, action: action).keyboardShortcut("4") + TabButton(timeline: .home, img: "house", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, settings: settings, action: action).keyboardShortcut("1") + TabButton(timeline: .dms, img: "bubble.left.and.bubble.right", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, settings: settings, action: action).keyboardShortcut("2") + TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, settings: settings, action: action).keyboardShortcut("3") + TabButton(timeline: .notifications, img: "bell", selected: $selected, new_events: $new_events, isSidebarVisible: $isSidebarVisible, settings: settings, action: action).keyboardShortcut("4") } } } diff --git a/damus/Views/Notifications/NotificationsView.swift b/damus/Views/Notifications/NotificationsView.swift @@ -12,6 +12,10 @@ enum NotificationFilterState: String { case zaps case replies + func is_other( item: NotificationItem) -> Bool { + return item.is_zap == nil && item.is_reply == nil + } + func filter(_ item: NotificationItem) -> Bool { switch self { case .all: @@ -27,16 +31,10 @@ enum NotificationFilterState: String { struct NotificationsView: View { let state: DamusState @ObservedObject var notifications: NotificationsModel - @State var filter_state: NotificationFilterState + @State var filter_state: NotificationFilterState = .all @Environment(\.colorScheme) var colorScheme - init(state: DamusState, notifications: NotificationsModel) { - self.state = state - self._notifications = ObservedObject(initialValue: notifications) - self._filter_state = State(initialValue: load_notification_filter_state(pubkey: state.pubkey)) - } - var body: some View { TabView(selection: $filter_state) { NotificationTab(NotificationFilterState.all) @@ -54,6 +52,9 @@ struct NotificationsView: View { .onChange(of: filter_state) { val in save_notification_filter_state(pubkey: state.pubkey, state: val) } + .onAppear { + self.filter_state = load_notification_filter_state(pubkey: state.pubkey) + } .safeAreaInset(edge: .top, spacing: 0) { VStack(spacing: 0) { CustomPicker(selection: $filter_state, content: { @@ -107,7 +108,7 @@ struct NotificationsView: View { struct NotificationsView_Previews: PreviewProvider { static var previews: some View { - NotificationsView(state: test_damus_state(), notifications: NotificationsModel()) + NotificationsView(state: test_damus_state(), notifications: NotificationsModel(), filter_state: NotificationFilterState.all) } } diff --git a/damus/Views/Settings/NotificationSettingsView.swift b/damus/Views/Settings/NotificationSettingsView.swift @@ -11,7 +11,19 @@ struct NotificationSettingsView: View { @ObservedObject var settings: UserSettingsStore @Environment(\.dismiss) var dismiss - + + func indicator_binding(_ val: NewEventsBits) -> Binding<Bool> { + return Binding.init(get: { + (settings.notification_indicators & val.rawValue) > 0 + }, set: { v in + if v { + settings.notification_indicators |= val.rawValue + } else { + settings.notification_indicators &= ~val.rawValue + } + }) + } + var body: some View { Form { Section(header: Text(NSLocalizedString("Local Notifications", comment: "Section header for damus local notifications user configuration"))) { @@ -31,6 +43,17 @@ struct NotificationSettingsView: View { Toggle(NSLocalizedString("Show only from users you follow", comment: "Setting to Show notifications only associated to users your follow"), isOn: $settings.notification_only_from_following) .toggleStyle(.switch) } + + Section(header: Text(NSLocalizedString("Notification Dots", comment: "Section header for notification indicator dot settings"))) { + Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: indicator_binding(.zaps)) + .toggleStyle(.switch) + Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: indicator_binding(.mentions)) + .toggleStyle(.switch) + Toggle(NSLocalizedString("Reposts", comment: "Setting to enable Repost Local Notification"), isOn: indicator_binding(.reposts)) + .toggleStyle(.switch) + Toggle(NSLocalizedString("Likes", comment: "Setting to enable Like Local Notification"), isOn: indicator_binding(.likes)) + .toggleStyle(.switch) + } } .navigationTitle("Notifications") .onReceive(handle_notify(.switched_timeline)) { _ in