commit cfe14fac23c3cd70285af0afef1cb7a0207ca0a1
parent 2046fe5502322b5469ef5fe01baffc4680136146
Author: Terry Yiu <963907+tyiu@users.noreply.github.com>
Date: Sun, 25 Jun 2023 23:35:01 -0400
Deduplicate users in group notifications
Changelog-Fixed: Deduplicate users in notifications
Closes: #1326
Diffstat:
8 files changed, 84 insertions(+), 36 deletions(-)
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -1076,7 +1076,7 @@ func zap_notification_title(_ zap: Zap) -> String {
func zap_notification_body(profiles: Profiles, zap: Zap, locale: Locale = Locale.current) -> String {
let src = zap.request.ev
- let pk = zap.is_anon ? "anon" : src.pubkey
+ let pk = zap.is_anon ? ANON_PUBKEY : src.pubkey
let profile = profiles.lookup(id: pk)
let sats = NSNumber(value: (Double(zap.invoice.amount) / 1000.0))
let formattedSats = format_msats_abbrev(zap.invoice.amount)
diff --git a/damus/Util/DisplayName.swift b/damus/Util/DisplayName.swift
@@ -38,7 +38,7 @@ enum DisplayName {
func parse_display_name(profile: Profile?, pubkey: String) -> DisplayName {
- if pubkey == "anon" {
+ if pubkey == ANON_PUBKEY {
return .one(NSLocalizedString("Anonymous", comment: "Placeholder display name of anonymous user."))
}
diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift
@@ -9,6 +9,7 @@ import Foundation
import secp256k1
let PUBKEY_HRP = "npub"
+let ANON_PUBKEY = "anon"
struct FullKeypair: Equatable {
let pubkey: String
diff --git a/damus/Views/Events/TextEvent.swift b/damus/Views/Events/TextEvent.swift
@@ -132,7 +132,7 @@ struct TextEvent: View {
func ProfileName(is_anon: Bool) -> some View {
let profile = damus.profiles.lookup(id: pubkey)
- let pk = is_anon ? "anon" : pubkey
+ let pk = is_anon ? ANON_PUBKEY : pubkey
return EventProfileName(pubkey: pk, profile: profile, damus: damus, size: .normal)
}
diff --git a/damus/Views/Notifications/EventGroupView.swift b/damus/Views/Notifications/EventGroupView.swift
@@ -78,19 +78,42 @@ func event_author_name(profiles: Profiles, pubkey: String) -> String {
return Profile.displayName(profile: alice_prof, pubkey: pubkey).username.truncate(maxLength: 50)
}
-func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType) -> String {
+func event_group_unique_pubkeys(profiles: Profiles, group: EventGroupType) -> [String] {
+ var seen = Set<String>()
+ var sorted = [String]()
+
if let zapgrp = group.zap_group {
- let zap = zapgrp.zaps[ind]
-
- if zap.is_anon {
- return NSLocalizedString("Anonymous", comment: "Placeholder author name of the anonymous person who zapped an event.")
+ let zaps = zapgrp.zaps
+
+ for i in 0..<zaps.count {
+ let zap = zapgrp.zaps[i]
+ let pubkey: String
+
+ if zap.is_anon {
+ pubkey = ANON_PUBKEY
+ } else {
+ pubkey = zap.request.ev.pubkey
+ }
+
+ if !seen.contains(pubkey) {
+ seen.insert(pubkey)
+ sorted.append(pubkey)
+ }
}
-
- return event_author_name(profiles: profiles, pubkey: zap.request.ev.pubkey)
} else {
- let ev = group.events[ind]
- return event_author_name(profiles: profiles, pubkey: ev.pubkey)
+ let events = group.events
+
+ for i in 0..<events.count {
+ let ev = events[i]
+ let pubkey = ev.pubkey
+ if !seen.contains(pubkey) {
+ seen.insert(pubkey)
+ sorted.append(pubkey)
+ }
+ }
}
+
+ return sorted
}
/**
@@ -130,29 +153,29 @@ func event_group_author_name(profiles: Profiles, ind: Int, group: EventGroupType
"zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
"zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
*/
-func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, nozaps: Bool, locale: Locale? = nil) -> String {
- if group.events.count == 0 {
+func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, nozaps: Bool, pubkeys: [String], locale: Locale? = nil) -> String {
+ if pubkeys.count == 0 {
return "??"
}
let verb = reacting_to_verb(group: group)
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev, group: group, nozaps: nozaps)
- let localization_key = "\(verb)_\(reacting_to)_\(min(group.events.count, 3))"
+ let localization_key = "\(verb)_\(reacting_to)_\(min(pubkeys.count, 3))"
let format = localizedStringFormat(key: localization_key, locale: locale)
- switch group.events.count {
+ switch pubkeys.count {
case 1:
- let display_name = event_group_author_name(profiles: profiles, ind: 0, group: group)
+ let display_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
return String(format: format, locale: locale, display_name)
case 2:
- let alice_name = event_group_author_name(profiles: profiles, ind: 0, group: group)
- let bob_name = event_group_author_name(profiles: profiles, ind: 1, group: group)
+ let alice_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
+ let bob_name = event_author_name(profiles: profiles, pubkey: pubkeys[1])
return String(format: format, locale: locale, alice_name, bob_name)
default:
- let alice_name = event_group_author_name(profiles: profiles, ind: 0, group: group)
- let count = group.events.count - 1
+ let alice_name = event_author_name(profiles: profiles, pubkey: pubkeys[0])
+ let count = pubkeys.count - 1
return String(format: format, locale: locale, count, alice_name)
}
@@ -175,8 +198,8 @@ struct EventGroupView: View {
let event: NostrEvent?
let group: EventGroupType
- var GroupDescription: some View {
- Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, nozaps: state.settings.nozaps))")
+ func GroupDescription(_ pubkeys: [String]) -> some View {
+ Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, nozaps: state.settings.nozaps, pubkeys: pubkeys))")
}
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
@@ -216,12 +239,14 @@ struct EventGroupView: View {
.frame(width: PFP_SIZE + 10)
VStack(alignment: .leading) {
- ProfilePicturesView(state: state, pubkeys: group.events.map { $0.pubkey })
+ let unique_pubkeys = event_group_unique_pubkeys(profiles: state.profiles, group: group)
+
+ ProfilePicturesView(state: state, pubkeys: unique_pubkeys)
if let event {
let thread = ThreadModel(event: event, damus_state: state)
let dest = ThreadView(state: state, thread: thread)
- GroupDescription
+ GroupDescription(unique_pubkeys)
if !state.settings.nozaps || !group.is_note_zap {
NavigationLink(destination: dest) {
VStack(alignment: .leading) {
@@ -234,7 +259,7 @@ struct EventGroupView: View {
.buttonStyle(.plain)
}
} else {
- GroupDescription
+ GroupDescription(unique_pubkeys)
}
}
}
diff --git a/damus/Views/Profile/MaybeAnonPfpView.swift b/damus/Views/Profile/MaybeAnonPfpView.swift
@@ -38,6 +38,6 @@ struct MaybeAnonPfpView: View {
struct MaybeAnonPfpView_Previews: PreviewProvider {
static var previews: some View {
- MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon", size: PFP_SIZE)
+ MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: ANON_PUBKEY, size: PFP_SIZE)
}
}
diff --git a/damus/Views/Zaps/ZapUserView.swift b/damus/Views/Zaps/ZapUserView.swift
@@ -23,6 +23,6 @@ struct ZapUserView: View {
struct ZapUserView_Previews: PreviewProvider {
static var previews: some View {
- ZapUserView(state: test_damus_state(), pubkey: "anon")
+ ZapUserView(state: test_damus_state(), pubkey: ANON_PUBKEY)
}
}
diff --git a/damusTests/EventGroupViewTests.swift b/damusTests/EventGroupViewTests.swift
@@ -18,6 +18,27 @@ final class EventGroupViewTests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
+ func testEventAuthorName() {
+ let damusState = test_damus_state()
+ XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "pk1"), "pk1:pk1")
+ XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "pk2"), "pk2:pk2")
+ XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "anon"), "Anonymous")
+ }
+
+ func testEventGroupUniquePubkeys() {
+ let damusState = test_damus_state()
+
+ let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
+ let repost1 = NostrEvent(id: "", content: encodedPost, pubkey: "pk1", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
+ let repost2 = NostrEvent(id: "", content: encodedPost, pubkey: "pk2", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
+ let repost3 = NostrEvent(id: "", content: encodedPost, pubkey: "pk3", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
+
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: []))), [])
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1]))), ["pk1"])
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2]))), ["pk1", "pk2"])
+ XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), ["pk1", "pk2", "pk3"])
+ }
+
func testReactingToText() throws {
let enUsLocale = Locale(identifier: "en-US")
let damusState = test_damus_state()
@@ -25,18 +46,19 @@ final class EventGroupViewTests: XCTestCase {
let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
let repost1 = NostrEvent(id: "", content: encodedPost, pubkey: "pk1", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
let repost2 = NostrEvent(id: "", content: encodedPost, pubkey: "pk2", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
+ let repost3 = NostrEvent(id: "", content: encodedPost, pubkey: "pk3", kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)
let nozaps = true
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, nozaps: nozaps, locale: enUsLocale), "??")
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, nozaps: nozaps, locale: enUsLocale), "pk1:pk1 reposted a note you were tagged in")
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, nozaps: nozaps, locale: enUsLocale), "pk1:pk1 and pk2:pk2 reposted a note you were tagged in")
- XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_event, nozaps: nozaps, locale: enUsLocale), "pk1:pk1 and 2 others reposted a note you were tagged in")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, nozaps: nozaps, pubkeys: [], locale: enUsLocale), "??")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, nozaps: nozaps, pubkeys: ["pk1"], locale: enUsLocale), "pk1:pk1 reposted a note you were tagged in")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, nozaps: nozaps, pubkeys: ["pk1", "pk2"], locale: enUsLocale), "pk1:pk1 and pk2:pk2 reposted a note you were tagged in")
+ XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_event, nozaps: nozaps, pubkeys: ["pk1", "pk2", "pk3"], locale: enUsLocale), "pk1:pk1 and 2 others reposted a note you were tagged in")
Bundle.main.localizations.map { Locale(identifier: $0) }.forEach {
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, nozaps: nozaps, locale: $0), "??")
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, nozaps: nozaps, locale: $0))
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, nozaps: nozaps, locale: $0))
- XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_event, nozaps: nozaps, locale: $0))
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_event, nozaps: nozaps, pubkeys: [], locale: $0), "??")
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_event, nozaps: nozaps, pubkeys: ["pk1"], locale: $0))
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_event, nozaps: nozaps, pubkeys: ["pk1", "pk2"], locale: $0))
+ XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost3])), ev: test_event, nozaps: nozaps, pubkeys: ["pk1", "pk2", "pk3"], locale: $0))
}
}