[PATCH damus v1 0/4] DRAFT/RFC: Fix word muting

4 views
Skip to first unread message

Daniel D’Aquino

unread,
May 7, 2024, 12:31:44 AMMay 7
to pat...@damus.io, Daniel D’Aquino
Hi Will,

Here is a draft of the fix. Please note that this can be further tested/refined, unless we are tight on time.

What has been done:
- Word muting bug has been fixed
- Mute rules now apply to all timeline views
- Optimized performance of timeline view muting by adding a cache
- Added a super basic word muting automated test
- Lightly tested each one of the changes above. (I did less rigorous testing than usual)


What has NOT been done (and perhaps I should do):
- I did not test mute item expiration
- I did not add edge cases to the automated test, or tests for other mute items and scenarios
- I did not add a changelog entry to commit messages
- I did not check if this implementation conflicts with any other muting implementation in the codebase
- I did not fix that profile view performance issue yet


Please let me know if you would like me to go over all items above, or which items you would like to defer (if any)

Also, please let me know if you have any suggestions or concerns over these changes.

Thank you!
Daniel

Daniel D’Aquino (4):
Mute: Add `user_keypair` to MutelistManager
Apply mute rules to Timeline views by default
Implement cache on MutelistManager
Add basic word muting automated test

.../NotificationExtensionState.swift | 2 +-
damus.xcodeproj/project.pbxproj | 4 ++
damus/ContentView.swift | 2 +-
damus/Models/DamusState.swift | 2 +-
damus/Models/HomeModel.swift | 4 +-
damus/Models/MutelistManager.swift | 63 ++++++++++++++++---
damus/Models/NotificationsManager.swift | 2 +-
damus/TestData.swift | 2 +-
damus/Views/DMChatView.swift | 2 +-
damus/Views/DirectMessagesView.swift | 2 +-
damus/Views/Timeline/InnerTimelineView.swift | 4 +-
damus/Views/TimelineView.swift | 6 +-
damusTests/Mocking/MockDamusState.swift | 2 +-
damusTests/MutingTests.swift | 43 +++++++++++++
14 files changed, 117 insertions(+), 23 deletions(-)
create mode 100644 damusTests/MutingTests.swift


base-commit: bb8dba6df6e2534dfb193402399b31a4fae8052d
--
2.44.0


Daniel D’Aquino

unread,
May 7, 2024, 12:31:49 AMMay 7
to pat...@damus.io, Daniel D’Aquino
The user keypair is necessary to determine whether or not a given event
is muted or not. Previously, the user keypair had to be passed on each
function call as an optional parameter, and word filtering would not
work unless the caller remembered to add the keypair parameter.

All usages of MutelistManager functions indicate that the desired base
keypair is always the same as the DamusState's keypair that owns the
MutelistManager. Therefore, it is simpler and less error prone to simply
pass the keypair to MutelistManager during its initialization.

Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
.../NotificationExtensionState.swift | 2 +-
damus/ContentView.swift | 2 +-
damus/Models/DamusState.swift | 2 +-
damus/Models/HomeModel.swift | 4 ++--
damus/Models/MutelistManager.swift | 15 ++++++++++-----
damus/Models/NotificationsManager.swift | 2 +-
damus/TestData.swift | 2 +-
damus/Views/DMChatView.swift | 2 +-
damus/Views/DirectMessagesView.swift | 2 +-
damusTests/Mocking/MockDamusState.swift | 2 +-
10 files changed, 20 insertions(+), 15 deletions(-)

diff --git a/DamusNotificationService/NotificationExtensionState.swift b/DamusNotificationService/NotificationExtensionState.swift
index d5cb989f..0cfb8e6a 100644
--- a/DamusNotificationService/NotificationExtensionState.swift
+++ b/DamusNotificationService/NotificationExtensionState.swift
@@ -28,7 +28,7 @@ struct NotificationExtensionState: HeadlessDamusState {
self.settings = UserSettingsStore()

self.contacts = Contacts(our_pubkey: keypair.pubkey)
- self.mutelist_manager = MutelistManager()
+ self.mutelist_manager = MutelistManager(user_keypair: keypair)
self.keypair = keypair
self.profiles = Profiles(ndb: ndb)
self.zaps = Zaps(our_pubkey: keypair.pubkey)
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
index b8619102..b3846a57 100644
--- a/damus/ContentView.swift
+++ b/damus/ContentView.swift
@@ -699,7 +699,7 @@ struct ContentView: View {
likes: EventCounter(our_pubkey: pubkey),
boosts: EventCounter(our_pubkey: pubkey),
contacts: Contacts(our_pubkey: pubkey),
- mutelist_manager: MutelistManager(),
+ mutelist_manager: MutelistManager(user_keypair: keypair),
profiles: Profiles(ndb: ndb),
dms: home.dms,
previews: PreviewCache(),
diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift
index ed4cf268..13679e45 100644
--- a/damus/Models/DamusState.swift
+++ b/damus/Models/DamusState.swift
@@ -112,7 +112,7 @@ class DamusState: HeadlessDamusState {
likes: EventCounter(our_pubkey: empty_pub),
boosts: EventCounter(our_pubkey: empty_pub),
contacts: Contacts(our_pubkey: empty_pub),
- mutelist_manager: MutelistManager(),
+ mutelist_manager: MutelistManager(user_keypair: kp),
profiles: Profiles(ndb: .empty),
dms: DirectMessagesModel(our_pubkey: empty_pub),
previews: PreviewCache(),
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
index 82d5bde1..5f040dc6 100644
--- a/damus/Models/HomeModel.swift
+++ b/damus/Models/HomeModel.swift
@@ -1148,8 +1148,8 @@ func should_show_event(event: NostrEvent, damus_state: DamusState) -> Bool {
)
}

-func should_show_event(state: DamusState, ev: NostrEvent, keypair: Keypair? = nil) -> Bool {
- let event_muted = state.mutelist_manager.is_event_muted(ev, keypair: keypair)
+func should_show_event(state: DamusState, ev: NostrEvent) -> Bool {
+ let event_muted = state.mutelist_manager.is_event_muted(ev)
if event_muted {
return false
}
diff --git a/damus/Models/MutelistManager.swift b/damus/Models/MutelistManager.swift
index 90a640fe..88ef8f90 100644
--- a/damus/Models/MutelistManager.swift
+++ b/damus/Models/MutelistManager.swift
@@ -8,12 +8,17 @@
import Foundation

class MutelistManager {
+ let user_keypair: Keypair
private(set) var event: NostrEvent? = nil

var users: Set<MuteItem> = []
var hashtags: Set<MuteItem> = []
var threads: Set<MuteItem> = []
var words: Set<MuteItem> = []
+
+ init(user_keypair: Keypair) {
+ self.user_keypair = user_keypair
+ }

func refresh_sets() {
guard let referenced_mute_items = event?.referenced_mute_items else { return }
@@ -55,8 +60,8 @@ class MutelistManager {
}
}

- func is_event_muted(_ ev: NostrEvent, keypair: Keypair? = nil) -> Bool {
- return event_muted_reason(ev, keypair: keypair) != nil
+ func is_event_muted(_ ev: NostrEvent) -> Bool {
+ return self.event_muted_reason(ev) != nil
}

func set_mutelist(_ ev: NostrEvent) {
@@ -120,9 +125,9 @@ class MutelistManager {
///
/// - Parameter ev: The ``NostrEvent`` that you want to check the muted reason for.
/// - Returns: The ``MuteItem`` that matched the event. Or `nil` if the event is not muted.
- func event_muted_reason(_ ev: NostrEvent, keypair: Keypair? = nil) -> MuteItem? {
+ func event_muted_reason(_ ev: NostrEvent) -> MuteItem? {
// Events from the current user should not be muted.
- guard keypair?.pubkey != ev.pubkey else { return nil }
+ guard self.user_keypair.pubkey != ev.pubkey else { return nil }

// Check if user is muted
let check_user_item = MuteItem.user(ev.pubkey, nil)
@@ -147,7 +152,7 @@ class MutelistManager {
}

// Check if word is muted
- if let keypair, let content: String = ev.maybe_get_content(keypair)?.lowercased() {
+ if let content: String = ev.maybe_get_content(self.user_keypair)?.lowercased() {
for word in words {
if case .word(let string, _) = word {
if content.contains(string.lowercased()) {
diff --git a/damus/Models/NotificationsManager.swift b/damus/Models/NotificationsManager.swift
index d59527d4..8fcaa554 100644
--- a/damus/Models/NotificationsManager.swift
+++ b/damus/Models/NotificationsManager.swift
@@ -37,7 +37,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, keypair: state.keypair) {
+ if state.mutelist_manager.is_event_muted(ev) {
return false
}

diff --git a/damus/TestData.swift b/damus/TestData.swift
index 1de6ac02..d7e9c977 100644
--- a/damus/TestData.swift
+++ b/damus/TestData.swift
@@ -73,7 +73,7 @@ var test_damus_state: DamusState = ({
likes: .init(our_pubkey: our_pubkey),
boosts: .init(our_pubkey: our_pubkey),
contacts: .init(our_pubkey: our_pubkey),
- mutelist_manager: MutelistManager(),
+ mutelist_manager: MutelistManager(user_keypair: test_keypair),
profiles: .init(ndb: ndb),
dms: .init(our_pubkey: our_pubkey),
previews: .init(),
diff --git a/damus/Views/DMChatView.swift b/damus/Views/DMChatView.swift
index 32a39fbc..fcf17362 100644
--- a/damus/Views/DMChatView.swift
+++ b/damus/Views/DMChatView.swift
@@ -20,7 +20,7 @@ struct DMChatView: View, KeyboardReadable {
ScrollViewReader { scroller in
ScrollView {
LazyVStack(alignment: .leading) {
- ForEach(Array(zip(dms.events, dms.events.indices)).filter { should_show_event(state: damus_state, ev: $0.0, keypair: damus_state.keypair)}, id: \.0.id) { (ev, ind) in
+ ForEach(Array(zip(dms.events, dms.events.indices)).filter { should_show_event(state: damus_state, ev: $0.0)}, id: \.0.id) { (ev, ind) in
DMView(event: dms.events[ind], damus_state: damus_state)
.contextMenu{MenuItems(damus_state: damus_state, event: ev, target_pubkey: ev.pubkey, profileModel: ProfileModel(pubkey: ev.pubkey, damus: damus_state))}
}
diff --git a/damus/Views/DirectMessagesView.swift b/damus/Views/DirectMessagesView.swift
index 2f41aead..9f3c8b4b 100644
--- a/damus/Views/DirectMessagesView.swift
+++ b/damus/Views/DirectMessagesView.swift
@@ -55,7 +55,7 @@ struct DirectMessagesView: View {

func MaybeEvent(_ model: DirectMessageModel) -> some View {
Group {
- if let ev = model.events.last(where: { should_show_event(state: damus_state, ev: $0, keypair: damus_state.keypair) }) {
+ if let ev = model.events.last(where: { should_show_event(state: damus_state, ev: $0) }) {
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
.onTapGesture {
self.model.set_active_dm_model(model)
diff --git a/damusTests/Mocking/MockDamusState.swift b/damusTests/Mocking/MockDamusState.swift
index fb02cd62..e554f2fe 100644
--- a/damusTests/Mocking/MockDamusState.swift
+++ b/damusTests/Mocking/MockDamusState.swift
@@ -25,7 +25,7 @@ func generate_test_damus_state(
return profiles
}()

- let mutelist_manager = MutelistManager()
+ let mutelist_manager = MutelistManager(user_keypair: test_keypair)
let damus = DamusState(pool: pool,
keypair: test_keypair,
likes: .init(our_pubkey: our_pubkey),
--
2.44.0


Daniel D’Aquino

unread,
May 7, 2024, 12:31:52 AMMay 7
to pat...@damus.io, Daniel D’Aquino
Mute rules were not applied in all timeline views. This commit adds code
to filter out muted events on timelines. It also provides an opt-out
parameter if there is ever a need to a timeline without mute filters

Testing
--------

PASS

Device: iPhone 13 Mini
iOS: 17.4.1
Damus: This commit
Coverage:
1. Add a muted keyword
2. Go to home view, check that events with that keyword are now not showing up

Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
damus/Views/Timeline/InnerTimelineView.swift | 4 ++--
damus/Views/TimelineView.swift | 6 ++++--
2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/damus/Views/Timeline/InnerTimelineView.swift b/damus/Views/Timeline/InnerTimelineView.swift
index 4212f3dc..0e572f8f 100644
--- a/damus/Views/Timeline/InnerTimelineView.swift
+++ b/damus/Views/Timeline/InnerTimelineView.swift
@@ -13,10 +13,10 @@ struct InnerTimelineView: View {
let state: DamusState
let filter: (NostrEvent) -> Bool

- init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool) {
+ init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) {
self.events = events
self.state = damus
- self.filter = filter
+ self.filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter
}

var event_options: EventViewOptions {
diff --git a/damus/Views/TimelineView.swift b/damus/Views/TimelineView.swift
index 6a88e6fb..f08fcd87 100644
--- a/damus/Views/TimelineView.swift
+++ b/damus/Views/TimelineView.swift
@@ -16,13 +16,15 @@ struct TimelineView<Content: View>: View {
let filter: (NostrEvent) -> Bool
let content: Content?
let debouncer: Debouncer
+ let apply_mute_rules: Bool

- init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, content: (() -> Content)? = nil) {
+ init(events: EventHolder, loading: Binding<Bool>, damus: DamusState, show_friend_icon: Bool, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true, content: (() -> Content)? = nil) {
self.events = events
self._loading = loading
self.damus = damus
self.show_friend_icon = show_friend_icon
self.filter = filter
+ self.apply_mute_rules = apply_mute_rules
self.debouncer = Debouncer(interval: 0.5)
self.content = content?()
}
@@ -42,7 +44,7 @@ struct TimelineView<Content: View>: View {
.id("startblock")
.frame(height: 1)

- InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter)
+ InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter, apply_mute_rules: self.apply_mute_rules)
.redacted(reason: loading ? .placeholder : [])
.shimmer(loading)
.disabled(loading)
--
2.44.0


Daniel D’Aquino

unread,
May 7, 2024, 12:32:02 AMMay 7
to pat...@damus.io, Daniel D’Aquino
To filter muted events on a timeline, we need to check if an event is
muted. However, we need to do so in a very efficient manner, to avoid performance degradation.

This commit improves performance by caching mute statuses for as long as
the mutelist stays the same.

Testing
--------

PASS

Device: iPhone 13 Mini
iOS: 17.4.1
Damus: This commit
Damus baseline: bb8dba6df6e2534dfb193402399b31a4fae8052d
Steps:
1. Downgrade to baseline version, Run SwiftUI profiler on Xcode instruments
2. Scroll down home feed. Try to notice hangs and microhangs on the UI
3. Upgrade to version under test. Start the same profiler test.
4. Check that hangs/microhangs frequency and severity are roughly the same.

Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
damus/Models/MutelistManager.swift | 50 +++++++++++++++++++++++++++---
1 file changed, 45 insertions(+), 5 deletions(-)

diff --git a/damus/Models/MutelistManager.swift b/damus/Models/MutelistManager.swift
index 88ef8f90..1096bdf9 100644
--- a/damus/Models/MutelistManager.swift
+++ b/damus/Models/MutelistManager.swift
@@ -11,10 +11,20 @@ class MutelistManager {
let user_keypair: Keypair
private(set) var event: NostrEvent? = nil

- var users: Set<MuteItem> = []
- var hashtags: Set<MuteItem> = []
- var threads: Set<MuteItem> = []
- var words: Set<MuteItem> = []
+ var users: Set<MuteItem> = [] {
+ didSet { self.reset_cache() }
+ }
+ var hashtags: Set<MuteItem> = [] {
+ didSet { self.reset_cache() }
+ }
+ var threads: Set<MuteItem> = [] {
+ didSet { self.reset_cache() }
+ }
+ var words: Set<MuteItem> = [] {
+ didSet { self.reset_cache() }
+ }
+
+ var muted_notes_cache: [NoteId: EventMuteStatus] = [:]

init(user_keypair: Keypair) {
self.user_keypair = user_keypair
@@ -46,6 +56,10 @@ class MutelistManager {
threads = new_threads
words = new_words
}
+
+ func reset_cache() {
+ self.muted_notes_cache = [:]
+ }

func is_muted(_ item: MuteItem) -> Bool {
switch item {
@@ -119,13 +133,25 @@ class MutelistManager {
threads.remove(item)
}
}
+
+ func event_muted_reason(_ ev: NostrEvent) -> MuteItem? {
+ if let cached_mute_status = self.muted_notes_cache[ev.id] {
+ return cached_mute_status.mute_reason()
+ }
+ if let reason = self.compute_event_muted_reason(ev) {
+ self.muted_notes_cache[ev.id] = .muted(reason: reason)
+ return reason
+ }
+ self.muted_notes_cache[ev.id] = .not_muted
+ return nil
+ }


/// Check if an event is muted given a collection of ``MutedItem``.
///
/// - Parameter ev: The ``NostrEvent`` that you want to check the muted reason for.
/// - Returns: The ``MuteItem`` that matched the event. Or `nil` if the event is not muted.
- func event_muted_reason(_ ev: NostrEvent) -> MuteItem? {
+ func compute_event_muted_reason(_ ev: NostrEvent) -> MuteItem? {
// Events from the current user should not be muted.
guard self.user_keypair.pubkey != ev.pubkey else { return nil }

@@ -164,4 +190,18 @@ class MutelistManager {

return nil
}
+
+ enum EventMuteStatus {
+ case muted(reason: MuteItem)
+ case not_muted
+
+ func mute_reason() -> MuteItem? {
+ switch self {
+ case .muted(reason: let reason):
+ return reason
+ case .not_muted:
+ return nil
+ }
+ }
+ }
}
--
2.44.0


Daniel D’Aquino

unread,
May 7, 2024, 12:32:06 AMMay 7
to pat...@damus.io, Daniel D’Aquino
Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
damus.xcodeproj/project.pbxproj | 4 +++
damusTests/MutingTests.swift | 43 +++++++++++++++++++++++++++++++++
2 files changed, 47 insertions(+)
create mode 100644 damusTests/MutingTests.swift

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
index 68552a0b..1b03f123 100644
--- a/damus.xcodeproj/project.pbxproj
+++ b/damus.xcodeproj/project.pbxproj
@@ -484,6 +484,7 @@
D74AAFD62B155F0C006CF0F4 /* WalletConnect+.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */; };
D74F430A2B23F0BE00425B75 /* DamusPurple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F43092B23F0BE00425B75 /* DamusPurple.swift */; };
D74F430C2B23FB9B00425B75 /* StoreObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F430B2B23FB9B00425B75 /* StoreObserver.swift */; };
+ D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D753CEA92BE9DE04001C3A5D /* MutingTests.swift */; };
D76556D62B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */; };
D76874F32AE3632B00FB0F68 /* ProfileZapLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */; };
D77BFA0B2AE3051200621634 /* ProfileActionSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */; };
@@ -1401,6 +1402,7 @@
D74AAFD52B155F0C006CF0F4 /* WalletConnect+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletConnect+.swift"; sourceTree = "<group>"; };
D74F43092B23F0BE00425B75 /* DamusPurple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurple.swift; sourceTree = "<group>"; };
D74F430B2B23FB9B00425B75 /* StoreObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreObserver.swift; sourceTree = "<group>"; };
+ D753CEA92BE9DE04001C3A5D /* MutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutingTests.swift; sourceTree = "<group>"; };
D76556D52B1E6C08001B0CCC /* DamusPurpleWelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleWelcomeView.swift; sourceTree = "<group>"; };
D76874F22AE3632B00FB0F68 /* ProfileZapLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZapLinkView.swift; sourceTree = "<group>"; };
D77BFA0A2AE3051200621634 /* ProfileActionSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileActionSheetView.swift; sourceTree = "<group>"; };
@@ -2557,6 +2559,7 @@
E06336A92B75832100A88E6B /* ImageMetadataTest.swift */,
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
+ D753CEA92BE9DE04001C3A5D /* MutingTests.swift */,
);
path = damusTests;
sourceTree = "<group>";
@@ -3561,6 +3564,7 @@
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */,
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
+ D753CEAA2BE9DE04001C3A5D /* MutingTests.swift in Sources */,
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */,
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
4C684A552A7E91FE005E6031 /* LongPostTests.swift in Sources */,
diff --git a/damusTests/MutingTests.swift b/damusTests/MutingTests.swift
new file mode 100644
index 00000000..3d01d6af
--- /dev/null
+++ b/damusTests/MutingTests.swift
@@ -0,0 +1,43 @@
+//
+// MutingTests.swift
+// damusTests
+//
+// Created by Daniel D’Aquino on 2024-05-06.
+//
+import Foundation
+
+import XCTest
+@testable import damus
+
+final class MutingTests: XCTestCase {
+ func testWordMuting() {
+ // Setup some test data
+ let test_note = NostrEvent(
+ content: "Nostr is the super app. Because it’s actually an ecosystem of apps, all of which make each other better. People haven’t grasped that yet. They will when it’s more accessible and onboarding is more straightforward and intuitive.",
+ keypair: jack_keypair,
+ createdAt: UInt32(Date().timeIntervalSince1970 - 100)
+ )!
+ let spammy_keypair = generate_new_keypair().to_keypair()
+ let spammy_test_note = NostrEvent(
+ content: "Some spammy airdrop just arrived! Why stack sats when you can get scammed instead with some random coin? Call 1-800-GET-SCAMMED to claim your airdrop today!",
+ keypair: spammy_keypair,
+ createdAt: UInt32(Date().timeIntervalSince1970 - 100)
+ )!
+
+ let mute_item: MuteItem = .word("airdrop", nil)
+ let existing_mutelist = test_damus_state.mutelist_manager.event
+
+ guard
+ let full_keypair = test_damus_state.keypair.to_full(),
+ let mutelist = create_or_update_mutelist(keypair: full_keypair, mprev: existing_mutelist, to_add: mute_item)
+ else {
+ return
+ }
+
+ test_damus_state.mutelist_manager.set_mutelist(mutelist)
+ test_damus_state.postbox.send(mutelist)
+
+ XCTAssert(test_damus_state.mutelist_manager.is_event_muted(spammy_test_note))
+ XCTAssertFalse(test_damus_state.mutelist_manager.is_event_muted(test_note))
+ }
+}
--
2.44.0


William Casarin

unread,
May 7, 2024, 1:38:04 PMMay 7
to Daniel D’Aquino, pat...@damus.io
On Tue, May 07, 2024 at 04:31:53AM GMT, Daniel D’Aquino wrote:
>To filter muted events on a timeline, we need to check if an event is
>muted. However, we need to do so in a very efficient manner, to avoid performance degradation.
>
>This commit improves performance by caching mute statuses for as long as
>the mutelist stays the same.
>
>Testing
>--------
>
>PASS
>
>Device: iPhone 13 Mini
>iOS: 17.4.1
>Damus: This commit
>Damus baseline: bb8dba6df6e2534dfb193402399b31a4fae8052d
>Steps:
>1. Downgrade to baseline version, Run SwiftUI profiler on Xcode instruments
>2. Scroll down home feed. Try to notice hangs and microhangs on the UI
>3. Upgrade to version under test. Start the same profiler test.
>4. Check that hangs/microhangs frequency and severity are roughly the same.
>
>Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
>---

ah this is great, thanks!

Reviewed-by: William Casarin <jb...@jb55.com>

William Casarin

unread,
May 7, 2024, 9:11:43 PMMay 7
to Daniel D’Aquino, pat...@damus.io
On Tue, May 07, 2024 at 04:31:33AM GMT, Daniel D’Aquino wrote:
>Hi Will,
>
>Here is a draft of the fix. Please note that this can be further tested/refined, unless we are tight on time.
>
>What has been done:
>- Word muting bug has been fixed
>- Mute rules now apply to all timeline views
>- Optimized performance of timeline view muting by adding a cache
>- Added a super basic word muting automated test
>- Lightly tested each one of the changes above. (I did less rigorous testing than usual)
>
>
>What has NOT been done (and perhaps I should do):
>- I did not test mute item expiration
>- I did not add edge cases to the automated test, or tests for other mute items and scenarios
>- I did not add a changelog entry to commit messages
>- I did not check if this implementation conflicts with any other muting implementation in the codebase
>- I did not fix that profile view performance issue yet
>
>
>Please let me know if you would like me to go over all items above, or which items you would like to defer (if any)
>
>Also, please let me know if you have any suggestions or concerns over these changes.

This is already a lot better, thanks. I've added it to 1.8 + master

Will

Daniel D'Aquino

unread,
May 8, 2024, 2:25:31 PMMay 8
to William Casarin, sembene_truestar via patches
Thanks!


Reply all
Reply to author
Forward
0 new messages