damus

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

commit dbf8c932ae886946146c396cb5beee8473fe41ce
parent 8aac880bb5914e0e0164927ddb9989a0ad384f5b
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 24 May 2022 15:29:28 -0700

fetch following contacts if we are missing any

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Adamus/Models/FollowingModel.swift | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Models/HomeModel.swift | 42+++++++++++++++++++++++++-----------------
Mdamus/Models/ProfileModel.swift | 11+++--------
Mdamus/Util/InsertSort.swift | 20++++++++++++++++++++
Mdamus/Views/FollowingView.swift | 14+++++++++++---
Mdamus/Views/ProfileView.swift | 4+++-
7 files changed, 136 insertions(+), 29 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C477C9D282C3A4800033AA3 /* TipCounter.swift */; }; 4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; }; 4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9115283D855D0052CD1C /* EventsModel.swift */; }; + 4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9117283D88E40052CD1C /* FollowingModel.swift */; }; 4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C63334F283D40E500B1C9C3 /* HomeModel.swift */; }; 4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C633351283D419F00B1C9C3 /* SignalModel.swift */; }; 4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; }; @@ -156,6 +157,7 @@ 4C477C9D282C3A4800033AA3 /* TipCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipCounter.swift; sourceTree = "<group>"; }; 4C5F9113283D694D0052CD1C /* FollowTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowTarget.swift; sourceTree = "<group>"; }; 4C5F9115283D855D0052CD1C /* EventsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsModel.swift; sourceTree = "<group>"; }; + 4C5F9117283D88E40052CD1C /* FollowingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingModel.swift; sourceTree = "<group>"; }; 4C63334F283D40E500B1C9C3 /* HomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModel.swift; sourceTree = "<group>"; }; 4C633351283D419F00B1C9C3 /* SignalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalModel.swift; sourceTree = "<group>"; }; 4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; }; @@ -250,6 +252,7 @@ 4C633351283D419F00B1C9C3 /* SignalModel.swift */, 4C5F9113283D694D0052CD1C /* FollowTarget.swift */, 4C5F9115283D855D0052CD1C /* EventsModel.swift */, + 4C5F9117283D88E40052CD1C /* FollowingModel.swift */, ); path = Models; sourceTree = "<group>"; @@ -603,6 +606,7 @@ 4C0A3F95280F6C78000448DE /* ReplyQuoteView.swift in Sources */, 4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */, 4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */, + 4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */, 4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */, 4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */, 4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */, diff --git a/damus/Models/FollowingModel.swift b/damus/Models/FollowingModel.swift @@ -0,0 +1,70 @@ +// +// FollowingModel.swift +// damus +// +// Created by William Casarin on 2022-05-24. +// + +import Foundation + +class FollowingModel: ObservableObject { + let damus_state: DamusState + var needs_sub: Bool = true + + var has_contact: Set<String> = Set() + let contacts: [String] + + let sub_id: String = UUID().description + + init(damus_state: DamusState, contacts: [String]) { + self.damus_state = damus_state + self.contacts = contacts + } + + func get_filter() -> NostrFilter { + var f = NostrFilter.filter_kinds([0]) + f.authors = self.contacts.reduce(into: Array<String>()) { acc, pk in + // don't fetch profiles we already have + if damus_state.profiles.lookup(id: pk) != nil { + return + } + acc.append(pk) + } + return f + } + + func subscribe() { + let filter = get_filter() + if (filter.authors?.count ?? 0) == 0 { + needs_sub = false + return + } + let filters = [filter] + print_filters(relay_id: "following", filters: [filters]) + self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event) + } + + func unsubscribe() { + if !needs_sub { + return + } + print("unsubscribing from following \(sub_id)") + self.damus_state.pool.unsubscribe(sub_id: sub_id) + } + + func handle_event(relay_id: String, ev: NostrConnectionEvent) { + switch ev { + case .ws_event: + break + case .nostr_event(let nev): + switch nev { + case .event(_, let ev): + if ev.kind == 0 { + process_metadata_event(profiles: damus_state.profiles, ev: ev) + } + case .notice(let msg): + print("followingmodel notice: \(msg)") + } + } + } +} diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift @@ -69,8 +69,7 @@ class HomeModel: ObservableObject { } func handle_contact_event(sub_id: String, relay_id: String, ev: NostrEvent) { - load_our_contacts(contacts: self.damus_state.contacts, our_pubkey: self.damus_state.pubkey, ev: ev) - add_contact_if_friend(contacts: self.damus_state.contacts, ev: ev) + process_contact_event(contacts: damus_state.contacts, pubkey: damus_state.pubkey, ev: ev) if sub_id == init_subid { pool.send(.unsubscribe(init_subid), to: [relay_id]) @@ -246,21 +245,7 @@ class HomeModel: ObservableObject { } func handle_metadata_event(_ ev: NostrEvent) { - guard let profile: Profile = decode_data(Data(ev.content.utf8)) else { - return - } - - if let mprof = damus_state.profiles.lookup_with_timestamp(id: ev.pubkey) { - if mprof.timestamp > ev.created_at { - // skip if we already have an newer profile - return - } - } - - let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at) - damus_state.profiles.add(id: ev.pubkey, profile: tprof) - - notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile)) + process_metadata_event(profiles: damus_state.profiles, ev: ev) } func get_last_event_of_kind(relay_id: String, kind: Int) -> NostrEvent? { @@ -392,3 +377,26 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) { } print("-----") } + +func process_metadata_event(profiles: Profiles, ev: NostrEvent) { + guard let profile: Profile = decode_data(Data(ev.content.utf8)) else { + return + } + + if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) { + if mprof.timestamp > ev.created_at { + // skip if we already have an newer profile + return + } + } + + let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at) + profiles.add(id: ev.pubkey, profile: tprof) + + notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile)) +} + +func process_contact_event(contacts: Contacts, pubkey: String, ev: NostrEvent) { + load_our_contacts(contacts: contacts, our_pubkey: pubkey, ev: ev) + add_contact_if_friend(contacts: contacts, ev: ev) +} diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift @@ -39,19 +39,14 @@ class ProfileModel: ObservableObject { var profile_filter = NostrFilter.filter_kinds([ NostrKind.text.rawValue, NostrKind.boost.rawValue, + NostrKind.metadata.rawValue, + NostrKind.contacts.rawValue, NostrKind.like.rawValue ]) profile_filter.authors = [pubkey] - - var contact_pks = (contacts?.referenced_pubkeys.map { $0.ref_id }) ?? [] - contact_pks.append(pubkey) - - var contacts_filter = NostrFilter.filter_kinds([0,3]) - contacts_filter.authors = contact_pks - profile_filter.limit = 1000 - let filters = [profile_filter, contacts_filter] + let filters = [profile_filter] print("subscribing to profile \(pubkey) with sub_id \(sub_id)") print_filters(relay_id: "profile", filters: [filters]) diff --git a/damus/Util/InsertSort.swift b/damus/Util/InsertSort.swift @@ -18,6 +18,26 @@ func insert_uniq<T: Equatable>(xs: inout [T], new_x: T) -> Bool { return true } +func insert_uniq_by_pubkey(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool { + var i: Int = 0 + + for event in events { + // don't insert duplicate events + if new_ev.pubkey == event.pubkey { + return false + } + + if cmp(new_ev, event) { + events.insert(new_ev, at: i) + return true + } + i += 1 + } + + events.append(new_ev) + return true +} + func insert_uniq_sorted_event(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool { var i: Int = 0 diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift @@ -36,17 +36,25 @@ struct FollowUserView: View { } struct FollowingView: View { - let contact: NostrEvent let damus_state: DamusState + @StateObject var following: FollowingModel + + var body: some View { ScrollView { LazyVStack(alignment: .leading) { - ForEach(contact.referenced_pubkeys) { pk in - FollowUserView(target: .pubkey(pk.ref_id), damus_state: damus_state) + ForEach(following.contacts, id: \.self) { pk in + FollowUserView(target: .pubkey(pk), damus_state: damus_state) } } } + .onAppear { + following.subscribe() + } + .onDisappear { + following.unsubscribe() + } } } diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -79,7 +79,9 @@ struct ProfileView: View { if let contact = profile.contacts { Divider() - NavigationLink(destination: FollowingView(contact: contact, damus_state: damus_state)) { + let contacts = contact.referenced_pubkeys.map { $0.ref_id } + let following_model = FollowingModel(damus_state: damus_state, contacts: contacts) + NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model)) { HStack { Text("\(profile.following)") Text("Following")