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:
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