diff --git a/DamusNotificationService/NotificationService.swift b/DamusNotificationService/NotificationService.swift index 44317084..0eb395b1 100644 --- a/DamusNotificationService/NotificationService.swift +++ b/DamusNotificationService/NotificationService.swift @@ -67,40 +67,40 @@ class NotificationService: UNNotificationServiceExtension { nip05: profile?.nip05) }() let sender_pubkey = nostr_event.pubkey - - // Don't show notification details that match mute list. - // TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block - if state.mutelist_manager.is_event_muted(nostr_event) { - // We cannot really suppress muted notifications until we have the notification supression entitlement. - // The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details - let content = UNMutableNotificationContent() - content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted") - content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it") - content.sound = UNNotificationSound.default - contentHandler(content) - return - } - - guard should_display_notification(state: state, event: nostr_event, mode: .push) else { - Log.debug("should_display_notification failed", for: .push_notifications) - // We should not display notification for this event. Suppress notification. - // contentHandler(UNNotificationContent()) - // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification - contentHandler(request.content) - return - } - - guard let notification_object = generate_local_notification_object(ndb: state.ndb, from: nostr_event, state: state) else { - Log.debug("generate_local_notification_object failed", for: .push_notifications) - // We could not process this notification. Probably an unsupported nostr event kind. Suppress. - // contentHandler(UNNotificationContent()) - // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification - contentHandler(request.content) - return - } - - + Task { + + // Don't show notification details that match mute list. + // TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block + if await state.mutelist_manager.is_event_muted(nostr_event) { + // We cannot really suppress muted notifications until we have the notification supression entitlement. + // The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details + let content = UNMutableNotificationContent() + content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted") + content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it") + content.sound = UNNotificationSound.default + contentHandler(content) + return + } + + guard await should_display_notification(state: state, event: nostr_event, mode: .push) else { + Log.debug("should_display_notification failed", for: .push_notifications) + // We should not display notification for this event. Suppress notification. + // contentHandler(UNNotificationContent()) + // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification + contentHandler(request.content) + return + } + + guard let notification_object = generate_local_notification_object(ndb: state.ndb, from: nostr_event, state: state) else { + Log.debug("generate_local_notification_object failed", for: .push_notifications) + // We could not process this notification. Probably an unsupported nostr event kind. Suppress. + // contentHandler(UNNotificationContent()) + // TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification + contentHandler(request.content) + return + } + let sender_dn = DisplayName(name: sender_profile.name, display_name: sender_profile.display_name, pubkey: sender_pubkey) guard let (improvedContent, _) = await NotificationFormatter.shared.format_message(displayName: sender_dn.displayName, notify: notification_object, state: state) else { diff --git a/damus/Features/Chat/ReplyQuoteView.swift b/damus/Features/Chat/ReplyQuoteView.swift index 660318cb..d69bd8fd 100644 --- a/damus/Features/Chat/ReplyQuoteView.swift +++ b/damus/Features/Chat/ReplyQuoteView.swift @@ -15,11 +15,17 @@ struct ReplyQuoteView: View { @ObservedObject var thread: ThreadModel let options: EventViewOptions + @State var can_show_event = true + + func update_should_show_event(event: NdbNote) async { + self.can_show_event = await should_show_event(event: event, damus_state: state) + } + func content(event: NdbNote) -> some View { ZStack(alignment: .leading) { VStack(alignment: .leading) { HStack(alignment: .center) { - if should_show_event(event: event, damus_state: state) { + if can_show_event { ProfilePicView(pubkey: event.pubkey, size: 14, highlight: .reply, profiles: state.profiles, disable_animation: false) let blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event, our_pubkey: state.pubkey) NoteContentView(damus_state: state, event: event, blur_images: blur_images, size: .small, options: options) @@ -56,6 +62,9 @@ struct ReplyQuoteView: View { Group { if let event = state.events.lookup(event_id) { self.content(event: event) + .onAppear { + Task { await self.update_should_show_event(event: event) } + } } } } diff --git a/damus/Features/FollowPack/Models/FollowPackModel.swift b/damus/Features/FollowPack/Models/FollowPackModel.swift index 5ead6f5b..f938bd87 100644 --- a/damus/Features/FollowPack/Models/FollowPackModel.swift +++ b/damus/Features/FollowPack/Models/FollowPackModel.swift @@ -51,7 +51,8 @@ class FollowPackModel: ObservableObject { event = ev.toOwned() } guard let event else { return } - if event.is_textlike && should_show_event(state: damus_state, ev: event) && !event.is_reply() + let should_show_event = await should_show_event(state: damus_state, ev: event) + if event.is_textlike && should_show_event && !event.is_reply() { if await self.events.insert(event) { DispatchQueue.main.async { diff --git a/damus/Features/Muting/Models/MutedThreadsManager.swift b/damus/Features/Muting/Models/MutedThreadsManager.swift index e4c93c75..7b463971 100644 --- a/damus/Features/Muting/Models/MutedThreadsManager.swift +++ b/damus/Features/Muting/Models/MutedThreadsManager.swift @@ -23,6 +23,7 @@ func loadOldMutedThreads(pubkey: Pubkey) -> [NoteId] { // We need to still use it since existing users might have their muted threads stored in UserDefaults // So now all it's doing is moving a users muted threads to the new kind:10000 system // It should not be used for any purpose beyond that +@MainActor func migrate_old_muted_threads_to_new_mutelist(keypair: Keypair, damus_state: DamusState) { // Ensure that keypair is fullkeypair guard let fullKeypair = keypair.to_full() else { return } diff --git a/damus/Features/Muting/Models/MutelistManager.swift b/damus/Features/Muting/Models/MutelistManager.swift index 69864a97..c2a748b9 100644 --- a/damus/Features/Muting/Models/MutelistManager.swift +++ b/damus/Features/Muting/Models/MutelistManager.swift @@ -7,6 +7,7 @@ import Foundation +@MainActor class MutelistManager { let user_keypair: Keypair private(set) var event: NostrEvent? = nil @@ -26,7 +27,7 @@ class MutelistManager { var muted_notes_cache: [NoteId: EventMuteStatus] = [:] - init(user_keypair: Keypair) { + nonisolated init(user_keypair: Keypair) { self.user_keypair = user_keypair } diff --git a/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift b/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift index 55a86675..2037b9ba 100644 --- a/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift +++ b/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift @@ -76,6 +76,7 @@ class NIP05DomainEventsModel: ObservableObject { guard let event else { return } await self.add_event(event) case .eose: + DispatchQueue.main.async { self.loading = false } continue } } @@ -86,7 +87,7 @@ class NIP05DomainEventsModel: ObservableObject { return } - guard should_show_event(state: state, ev: ev) else { + guard await should_show_event(state: state, ev: ev) else { return } diff --git a/damus/Features/Notifications/Models/NotificationsManager.swift b/damus/Features/Notifications/Models/NotificationsManager.swift index 35c848fe..b266189b 100644 --- a/damus/Features/Notifications/Models/NotificationsManager.swift +++ b/damus/Features/Notifications/Models/NotificationsManager.swift @@ -12,8 +12,8 @@ import UIKit let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60 -func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) { - guard should_display_notification(state: state, event: ev, mode: .local) else { +func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) async { + guard await should_display_notification(state: state, event: ev, mode: .local) else { // We should not display notification. Exit. return } @@ -25,7 +25,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) create_local_notification(profiles: state.profiles, notify: local_notification) } -func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) -> Bool { +func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) async -> Bool { // Do not show notification if it's coming from a mode different from the one selected by our user guard state.settings.notification_mode == mode else { return false @@ -46,7 +46,7 @@ func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent } // Don't show notifications that match mute list. - if state.mutelist_manager.is_event_muted(ev) { + if await state.mutelist_manager.is_event_muted(ev) { return false } diff --git a/damus/Features/Search/Models/SearchHomeModel.swift b/damus/Features/Search/Models/SearchHomeModel.swift index 0dec9fc2..9530ca81 100644 --- a/damus/Features/Search/Models/SearchHomeModel.swift +++ b/damus/Features/Search/Models/SearchHomeModel.swift @@ -34,6 +34,7 @@ class SearchHomeModel: ObservableObject { return filter } + @MainActor func filter_muted() { events.filter { should_show_event(state: damus_state, ev: $0) } self.objectWillChange.send() diff --git a/damus/Features/Search/Models/SearchModel.swift b/damus/Features/Search/Models/SearchModel.swift index f2a9229c..36577e6a 100644 --- a/damus/Features/Search/Models/SearchModel.swift +++ b/damus/Features/Search/Models/SearchModel.swift @@ -26,6 +26,7 @@ class SearchModel: ObservableObject { }) } + @MainActor func filter_muted() { self.events.filter { should_show_event(state: state, ev: $0) diff --git a/damus/Features/Timeline/Models/ContentFilters.swift b/damus/Features/Timeline/Models/ContentFilters.swift index a950b0db..238f15d2 100644 --- a/damus/Features/Timeline/Models/ContentFilters.swift +++ b/damus/Features/Timeline/Models/ContentFilters.swift @@ -34,6 +34,7 @@ func nsfw_tag_filter(ev: NostrEvent) -> Bool { return ev.referenced_hashtags.first(where: { t in t.hashtag.caseInsensitiveCompare("nsfw") == .orderedSame }) == nil } +@MainActor func get_repost_of_muted_user_filter(damus_state: DamusState) -> ((_ ev: NostrEvent) -> Bool) { return { ev in guard ev.known_kind == .boost else { return true } @@ -65,10 +66,12 @@ struct ContentFilters { } extension ContentFilters { + @MainActor static func default_filters(damus_state: DamusState) -> ContentFilters { return ContentFilters(filters: ContentFilters.defaults(damus_state: damus_state)) } + @MainActor static func defaults(damus_state: DamusState) -> [(NostrEvent) -> Bool] { var filters = Array<(NostrEvent) -> Bool>() if damus_state.settings.hide_nsfw_tagged_content { diff --git a/damus/Features/Timeline/Models/HomeModel.swift b/damus/Features/Timeline/Models/HomeModel.swift index ab0649e4..ed18f3c4 100644 --- a/damus/Features/Timeline/Models/HomeModel.swift +++ b/damus/Features/Timeline/Models/HomeModel.swift @@ -84,7 +84,9 @@ class HomeModel: ContactsDelegate { init() { self.damus_state = DamusState.empty self.setup_debouncer() - filter_events() + DispatchQueue.main.async { + self.filter_events() + } events.on_queue = preloader //self.events = EventHolder(on_queue: preloader) } @@ -353,6 +355,7 @@ class HomeModel: ContactsDelegate { } } + @MainActor func filter_events() { events.filter { ev in !damus_state.mutelist_manager.is_muted(.user(ev.pubkey, nil)) @@ -422,6 +425,7 @@ class HomeModel: ContactsDelegate { } } + @MainActor func handle_like_event(_ ev: NostrEvent) { guard let e = ev.last_refid() else { // no id ref? invalid like event @@ -682,6 +686,7 @@ class HomeModel: ContactsDelegate { case nwc } + @MainActor func handle_mute_list_event(_ ev: NostrEvent) { // we only care about our mutelist guard ev.pubkey == damus_state.pubkey else { @@ -700,6 +705,7 @@ class HomeModel: ContactsDelegate { migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state) } + @MainActor func handle_old_list_event(_ ev: NostrEvent) { // we only care about our lists guard ev.pubkey == damus_state.pubkey else { @@ -731,6 +737,7 @@ class HomeModel: ContactsDelegate { return m[kind] } + @MainActor func handle_notification(ev: NostrEvent) { // don't show notifications from ourselves guard ev.pubkey != damus_state.pubkey, @@ -750,7 +757,7 @@ class HomeModel: ContactsDelegate { } if handle_last_event(ev: ev, timeline: .notifications) { - process_local_notification(state: damus_state, event: ev) + Task { await process_local_notification(state: damus_state, event: ev) } } } @@ -773,6 +780,7 @@ class HomeModel: ContactsDelegate { } + @MainActor func handle_text_event(_ ev: NostrEvent, context: SubscriptionContext) { guard should_show_event(state: damus_state, ev: ev) else { return @@ -808,17 +816,21 @@ class HomeModel: ContactsDelegate { } func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) { - notification_status.new_events = notifs - - guard should_display_notification(state: damus_state, event: ev, mode: .local), - let notification_object = generate_local_notification_object(ndb: self.damus_state.ndb, from: ev, state: damus_state) - else { - return + Task { + notification_status.new_events = notifs + + + guard await should_display_notification(state: damus_state, event: ev, mode: .local), + let notification_object = generate_local_notification_object(ndb: self.damus_state.ndb, from: ev, state: damus_state) + else { + return + } + + create_local_notification(profiles: damus_state.profiles, notify: notification_object) } - - create_local_notification(profiles: damus_state.profiles, notify: notification_object) } + @MainActor func handle_dm(_ ev: NostrEvent) { guard should_show_event(state: damus_state, ev: ev) else { return @@ -1150,6 +1162,7 @@ func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: Pubkey) -> Bool { return ev.referenced_pubkeys.contains(our_pubkey) } +@MainActor func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool { return should_show_event( state: damus_state, @@ -1157,6 +1170,7 @@ func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool { ) } +@MainActor func should_show_event(state: DamusState, ev: NostrEvent) -> Bool { let event_muted = state.mutelist_manager.is_event_muted(ev) if event_muted {