Improve streaming interfaces and profile loading logic
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -1161,6 +1161,12 @@
|
|||||||
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
||||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
||||||
|
D72B6FA22E7DFB450050CD1D /* ProfilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72B6FA12E7DFB3F0050CD1D /* ProfilesManager.swift */; };
|
||||||
|
D72B6FA32E7DFB450050CD1D /* ProfilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72B6FA12E7DFB3F0050CD1D /* ProfilesManager.swift */; };
|
||||||
|
D72B6FA42E7DFB450050CD1D /* ProfilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72B6FA12E7DFB3F0050CD1D /* ProfilesManager.swift */; };
|
||||||
|
D72B6FA62E7E06AD0050CD1D /* ProfileObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72B6FA52E7E06A40050CD1D /* ProfileObserver.swift */; };
|
||||||
|
D72B6FA72E7E06AD0050CD1D /* ProfileObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72B6FA52E7E06A40050CD1D /* ProfileObserver.swift */; };
|
||||||
|
D72B6FA92E7E06AD0050CD1D /* ProfileObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72B6FA52E7E06A40050CD1D /* ProfileObserver.swift */; };
|
||||||
D72C01312E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */; };
|
D72C01312E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */; };
|
||||||
D72C01322E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */; };
|
D72C01322E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */; };
|
||||||
D72C01332E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */; };
|
D72C01332E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */; };
|
||||||
@@ -2614,6 +2620,8 @@
|
|||||||
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
||||||
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
||||||
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
||||||
|
D72B6FA12E7DFB3F0050CD1D /* ProfilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilesManager.swift; sourceTree = "<group>"; };
|
||||||
|
D72B6FA52E7E06A40050CD1D /* ProfileObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileObserver.swift; sourceTree = "<group>"; };
|
||||||
D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CondensedProfilePicturesViewModel.swift; sourceTree = "<group>"; };
|
D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CondensedProfilePicturesViewModel.swift; sourceTree = "<group>"; };
|
||||||
D72E12772BEED22400F4F781 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
D72E12772BEED22400F4F781 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
||||||
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilterTests.swift; sourceTree = "<group>"; };
|
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilterTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -3105,6 +3113,7 @@
|
|||||||
4C75EFAB28049CC80006080F /* Nostr */ = {
|
4C75EFAB28049CC80006080F /* Nostr */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D72B6FA52E7E06A40050CD1D /* ProfileObserver.swift */,
|
||||||
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
|
4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */,
|
||||||
50A60D132A28BEEE00186190 /* RelayLog.swift */,
|
50A60D132A28BEEE00186190 /* RelayLog.swift */,
|
||||||
4C75EFA527FF87A20006080F /* Nostr.swift */,
|
4C75EFA527FF87A20006080F /* Nostr.swift */,
|
||||||
@@ -4907,6 +4916,7 @@
|
|||||||
D73BDB122D71212600D69970 /* NostrNetworkManager */ = {
|
D73BDB122D71212600D69970 /* NostrNetworkManager */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D72B6FA12E7DFB3F0050CD1D /* ProfilesManager.swift */,
|
||||||
D733F9E02D92C1AA00317B11 /* SubscriptionManager.swift */,
|
D733F9E02D92C1AA00317B11 /* SubscriptionManager.swift */,
|
||||||
D73BDB172D71310C00D69970 /* UserRelayListErrors.swift */,
|
D73BDB172D71310C00D69970 /* UserRelayListErrors.swift */,
|
||||||
D73BDB132D71215F00D69970 /* UserRelayListManager.swift */,
|
D73BDB132D71215F00D69970 /* UserRelayListManager.swift */,
|
||||||
@@ -5712,6 +5722,7 @@
|
|||||||
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */,
|
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */,
|
||||||
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */,
|
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */,
|
||||||
F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */,
|
F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */,
|
||||||
|
D72B6FA62E7E06AD0050CD1D /* ProfileObserver.swift in Sources */,
|
||||||
4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */,
|
4C011B5F2BD0A56A002F2F9B /* ChatroomThreadView.swift in Sources */,
|
||||||
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
|
||||||
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
|
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
|
||||||
@@ -5764,6 +5775,7 @@
|
|||||||
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
||||||
5C0567582C8FBC560073F23A /* NDBSearchView.swift in Sources */,
|
5C0567582C8FBC560073F23A /* NDBSearchView.swift in Sources */,
|
||||||
D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */,
|
D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */,
|
||||||
|
D72B6FA32E7DFB450050CD1D /* ProfilesManager.swift in Sources */,
|
||||||
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
|
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
|
||||||
D71528002E0A3D6900C893D6 /* InterestList.swift in Sources */,
|
D71528002E0A3D6900C893D6 /* InterestList.swift in Sources */,
|
||||||
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
|
4C1A9A2729DDE31900516EAC /* TranslationSettingsView.swift in Sources */,
|
||||||
@@ -6064,6 +6076,7 @@
|
|||||||
82D6FAC12CD99F7900C925F4 /* AsciiCharacter.swift in Sources */,
|
82D6FAC12CD99F7900C925F4 /* AsciiCharacter.swift in Sources */,
|
||||||
82D6FAC22CD99F7900C925F4 /* NdbTagElem.swift in Sources */,
|
82D6FAC22CD99F7900C925F4 /* NdbTagElem.swift in Sources */,
|
||||||
82D6FAC32CD99F7900C925F4 /* Ndb.swift in Sources */,
|
82D6FAC32CD99F7900C925F4 /* Ndb.swift in Sources */,
|
||||||
|
D72B6FA92E7E06AD0050CD1D /* ProfileObserver.swift in Sources */,
|
||||||
82D6FAC42CD99F7900C925F4 /* NdbTagsIterator.swift in Sources */,
|
82D6FAC42CD99F7900C925F4 /* NdbTagsIterator.swift in Sources */,
|
||||||
82D6FAC52CD99F7900C925F4 /* NdbTxn.swift in Sources */,
|
82D6FAC52CD99F7900C925F4 /* NdbTxn.swift in Sources */,
|
||||||
82D6FAC72CD99F7900C925F4 /* midl.c in Sources */,
|
82D6FAC72CD99F7900C925F4 /* midl.c in Sources */,
|
||||||
@@ -6124,6 +6137,7 @@
|
|||||||
82D6FB052CD99F7900C925F4 /* MusicController.swift in Sources */,
|
82D6FB052CD99F7900C925F4 /* MusicController.swift in Sources */,
|
||||||
82D6FB062CD99F7900C925F4 /* UserStatusView.swift in Sources */,
|
82D6FB062CD99F7900C925F4 /* UserStatusView.swift in Sources */,
|
||||||
82D6FB072CD99F7900C925F4 /* UserStatus.swift in Sources */,
|
82D6FB072CD99F7900C925F4 /* UserStatus.swift in Sources */,
|
||||||
|
D72B6FA22E7DFB450050CD1D /* ProfilesManager.swift in Sources */,
|
||||||
5CB017262D42C5C400A9ED05 /* TransactionsView.swift in Sources */,
|
5CB017262D42C5C400A9ED05 /* TransactionsView.swift in Sources */,
|
||||||
82D6FB082CD99F7900C925F4 /* UserStatusSheet.swift in Sources */,
|
82D6FB082CD99F7900C925F4 /* UserStatusSheet.swift in Sources */,
|
||||||
82D6FB092CD99F7900C925F4 /* SearchHeaderView.swift in Sources */,
|
82D6FB092CD99F7900C925F4 /* SearchHeaderView.swift in Sources */,
|
||||||
@@ -6551,6 +6565,7 @@
|
|||||||
D73E5E242C6A97F4007EB227 /* FollowedNotify.swift in Sources */,
|
D73E5E242C6A97F4007EB227 /* FollowedNotify.swift in Sources */,
|
||||||
D73E5E252C6A97F4007EB227 /* FollowNotify.swift in Sources */,
|
D73E5E252C6A97F4007EB227 /* FollowNotify.swift in Sources */,
|
||||||
D73E5E262C6A97F4007EB227 /* LikedNotify.swift in Sources */,
|
D73E5E262C6A97F4007EB227 /* LikedNotify.swift in Sources */,
|
||||||
|
D72B6FA42E7DFB450050CD1D /* ProfilesManager.swift in Sources */,
|
||||||
D73E5E272C6A97F4007EB227 /* LocalNotificationNotify.swift in Sources */,
|
D73E5E272C6A97F4007EB227 /* LocalNotificationNotify.swift in Sources */,
|
||||||
D73E5F8B2C6AA6A2007EB227 /* UserStatusSheet.swift in Sources */,
|
D73E5F8B2C6AA6A2007EB227 /* UserStatusSheet.swift in Sources */,
|
||||||
D73E5E282C6A97F4007EB227 /* LoginNotify.swift in Sources */,
|
D73E5E282C6A97F4007EB227 /* LoginNotify.swift in Sources */,
|
||||||
@@ -6664,6 +6679,7 @@
|
|||||||
D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */,
|
D73E5E8A2C6A97F4007EB227 /* PurpleStoreKitManager.swift in Sources */,
|
||||||
D733F9E72D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
|
D733F9E72D92C76100317B11 /* UnownedNdbNote.swift in Sources */,
|
||||||
D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */,
|
D73E5E8E2C6A97F4007EB227 /* ImageResizer.swift in Sources */,
|
||||||
|
D72B6FA72E7E06AD0050CD1D /* ProfileObserver.swift in Sources */,
|
||||||
D78F080E2D7F78EF00FC6C75 /* Request.swift in Sources */,
|
D78F080E2D7F78EF00FC6C75 /* Request.swift in Sources */,
|
||||||
D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */,
|
D73E5E8F2C6A97F4007EB227 /* PhotoCaptureProcessor.swift in Sources */,
|
||||||
D773BC602C6D538500349F0A /* CommentItem.swift in Sources */,
|
D773BC602C6D538500349F0A /* CommentItem.swift in Sources */,
|
||||||
|
|||||||
@@ -819,7 +819,7 @@ struct TopbarSideMenuButton: View {
|
|||||||
Button {
|
Button {
|
||||||
isSideBarOpened.toggle()
|
isSideBarOpened.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: damus_state.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
.opacity(isSideBarOpened ? 0 : 1)
|
.opacity(isSideBarOpened ? 0 : 1)
|
||||||
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||||
.accessibilityHidden(true) // Knowing there is a profile picture here leads to no actionable outcome to VoiceOver users, so it is best not to show it
|
.accessibilityHidden(true) // Knowing there is a profile picture here leads to no actionable outcome to VoiceOver users, so it is best not to show it
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class NostrNetworkManager {
|
|||||||
let postbox: PostBox
|
let postbox: PostBox
|
||||||
/// Handles subscriptions and functions to read or consume data from the Nostr network
|
/// Handles subscriptions and functions to read or consume data from the Nostr network
|
||||||
let reader: SubscriptionManager
|
let reader: SubscriptionManager
|
||||||
|
let profilesManager: ProfilesManager
|
||||||
|
|
||||||
init(delegate: Delegate) {
|
init(delegate: Delegate) {
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
@@ -43,6 +44,7 @@ class NostrNetworkManager {
|
|||||||
self.reader = reader
|
self.reader = reader
|
||||||
self.userRelayList = userRelayList
|
self.userRelayList = userRelayList
|
||||||
self.postbox = PostBox(pool: pool)
|
self.postbox = PostBox(pool: pool)
|
||||||
|
self.profilesManager = ProfilesManager(subscriptionManager: reader, ndb: delegate.ndb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Control functions
|
// MARK: - Control functions
|
||||||
@@ -51,6 +53,7 @@ class NostrNetworkManager {
|
|||||||
func connect() {
|
func connect() {
|
||||||
self.userRelayList.connect()
|
self.userRelayList.connect()
|
||||||
self.pool.open = true
|
self.pool.open = true
|
||||||
|
Task { await self.profilesManager.load() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect() {
|
func disconnect() {
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// ProfilesManager.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-09-19.
|
||||||
|
//
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension NostrNetworkManager {
|
||||||
|
/// Efficiently manages getting profile metadata from the network and NostrDB without too many relay subscriptions
|
||||||
|
///
|
||||||
|
/// This is necessary because relays have a limit on how many subscriptions can be sent to relays at one given time.
|
||||||
|
actor ProfilesManager {
|
||||||
|
private var profileListenerTask: Task<Void, any Error>? = nil
|
||||||
|
private var subscriptionSwitcherTask: Task<Void, any Error>? = nil
|
||||||
|
private var subscriptionNeedsUpdate: Bool = false
|
||||||
|
private let subscriptionManager: SubscriptionManager
|
||||||
|
private let ndb: Ndb
|
||||||
|
private var streams: [Pubkey: [UUID: ProfileStreamInfo]]
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Initialization and deinitialization
|
||||||
|
|
||||||
|
init(subscriptionManager: SubscriptionManager, ndb: Ndb) {
|
||||||
|
self.subscriptionManager = subscriptionManager
|
||||||
|
self.ndb = ndb
|
||||||
|
self.streams = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.subscriptionSwitcherTask?.cancel()
|
||||||
|
self.profileListenerTask?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Task management
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
self.restartProfileListenerTask()
|
||||||
|
self.subscriptionSwitcherTask?.cancel()
|
||||||
|
self.subscriptionSwitcherTask = Task {
|
||||||
|
while true {
|
||||||
|
try await Task.sleep(for: .seconds(1))
|
||||||
|
try Task.checkCancellation()
|
||||||
|
if subscriptionNeedsUpdate {
|
||||||
|
self.restartProfileListenerTask()
|
||||||
|
subscriptionNeedsUpdate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
self.subscriptionSwitcherTask?.cancel()
|
||||||
|
self.profileListenerTask?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restartProfileListenerTask() {
|
||||||
|
self.profileListenerTask?.cancel()
|
||||||
|
self.profileListenerTask = Task {
|
||||||
|
try await self.listenToProfileChanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Listening and publishing of profile changes
|
||||||
|
|
||||||
|
private func listenToProfileChanges() async throws {
|
||||||
|
let pubkeys = Array(streams.keys)
|
||||||
|
guard pubkeys.count > 0 else { return }
|
||||||
|
let profileFilter = NostrFilter(kinds: [.metadata], authors: pubkeys)
|
||||||
|
for await ndbLender in self.subscriptionManager.streamIndefinitely(filters: [profileFilter], streamMode: .ndbFirst) {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
try? ndbLender.borrow { ev in
|
||||||
|
publishProfileUpdates(metadataEvent: ev)
|
||||||
|
}
|
||||||
|
try Task.checkCancellation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func publishProfileUpdates(metadataEvent: borrowing UnownedNdbNote) {
|
||||||
|
let now = UInt64(Date.now.timeIntervalSince1970)
|
||||||
|
ndb.write_profile_last_fetched(pubkey: metadataEvent.pubkey, fetched_at: now)
|
||||||
|
|
||||||
|
if let relevantStreams = streams[metadataEvent.pubkey] {
|
||||||
|
// If we have the user metadata event in ndb, then we should have the profile record as well.
|
||||||
|
guard let profile = ndb.lookup_profile(metadataEvent.pubkey) else { return }
|
||||||
|
for relevantStream in relevantStreams.values {
|
||||||
|
relevantStream.continuation.yield(profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Streaming interface
|
||||||
|
|
||||||
|
func streamProfile(pubkey: Pubkey) -> AsyncStream<ProfileStreamItem> {
|
||||||
|
return AsyncStream<ProfileStreamItem> { continuation in
|
||||||
|
let stream = ProfileStreamInfo(continuation: continuation)
|
||||||
|
self.add(pubkey: pubkey, stream: stream)
|
||||||
|
|
||||||
|
continuation.onTermination = { @Sendable _ in
|
||||||
|
Task { await self.removeStream(pubkey: pubkey, id: stream.id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Stream management
|
||||||
|
|
||||||
|
private func add(pubkey: Pubkey, stream: ProfileStreamInfo) {
|
||||||
|
if self.streams[pubkey] == nil {
|
||||||
|
self.streams[pubkey] = [:]
|
||||||
|
self.subscriptionNeedsUpdate = true
|
||||||
|
}
|
||||||
|
self.streams[pubkey]?[stream.id] = stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeStream(pubkey: Pubkey, id: UUID) {
|
||||||
|
self.streams[pubkey]?[id] = nil
|
||||||
|
if self.streams[pubkey]?.keys.count == 0 {
|
||||||
|
// We don't need to subscribe to this profile anymore
|
||||||
|
self.streams[pubkey] = nil
|
||||||
|
self.subscriptionNeedsUpdate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Helper types
|
||||||
|
|
||||||
|
typealias ProfileStreamItem = NdbTxn<ProfileRecord?>
|
||||||
|
|
||||||
|
struct ProfileStreamInfo {
|
||||||
|
let id: UUID = UUID()
|
||||||
|
let continuation: AsyncStream<ProfileStreamItem>.Continuation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,11 +30,11 @@ extension NostrNetworkManager {
|
|||||||
// MARK: - Subscribing and Streaming data from Nostr
|
// MARK: - Subscribing and Streaming data from Nostr
|
||||||
|
|
||||||
/// Streams notes until the EOSE signal
|
/// Streams notes until the EOSE signal
|
||||||
func streamNotesUntilEndOfStoredEvents(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, timeout: Duration? = nil, id: UUID? = nil) -> AsyncStream<NdbNoteLender> {
|
func streamExistingEvents(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, timeout: Duration? = nil, streamMode: StreamMode? = nil, id: UUID? = nil) -> AsyncStream<NdbNoteLender> {
|
||||||
let timeout = timeout ?? .seconds(10)
|
let timeout = timeout ?? .seconds(10)
|
||||||
return AsyncStream<NdbNoteLender> { continuation in
|
return AsyncStream<NdbNoteLender> { continuation in
|
||||||
let streamingTask = Task {
|
let streamingTask = Task {
|
||||||
outerLoop: for await item in self.subscribe(filters: filters, to: desiredRelays, timeout: timeout, id: id) {
|
outerLoop: for await item in self.advancedStream(filters: filters, to: desiredRelays, timeout: timeout, streamMode: streamMode, id: id) {
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
switch item {
|
switch item {
|
||||||
case .event(let lender):
|
case .event(let lender):
|
||||||
@@ -58,34 +58,55 @@ extension NostrNetworkManager {
|
|||||||
/// Subscribes to data from user's relays, for a maximum period of time — after which the stream will end.
|
/// Subscribes to data from user's relays, for a maximum period of time — after which the stream will end.
|
||||||
///
|
///
|
||||||
/// This is useful when waiting for some specific data from Nostr, but not indefinitely.
|
/// This is useful when waiting for some specific data from Nostr, but not indefinitely.
|
||||||
func subscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, timeout: Duration, id: UUID? = nil) -> AsyncStream<StreamItem> {
|
func timedStream(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, timeout: Duration, streamMode: StreamMode? = nil, id: UUID? = nil) -> AsyncStream<NdbNoteLender> {
|
||||||
return AsyncStream<StreamItem> { continuation in
|
return AsyncStream<NdbNoteLender> { continuation in
|
||||||
let streamingTask = Task {
|
let streamingTask = Task {
|
||||||
for await item in self.subscribe(filters: filters, to: desiredRelays, id: id) {
|
for await item in self.advancedStream(filters: filters, to: desiredRelays, timeout: timeout, streamMode: streamMode, id: id) {
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
continuation.yield(item)
|
switch item {
|
||||||
|
case .event(lender: let lender):
|
||||||
|
continuation.yield(lender)
|
||||||
|
case .eose: break
|
||||||
|
case .ndbEose: break
|
||||||
|
case .networkEose: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continuation.finish()
|
||||||
|
}
|
||||||
|
continuation.onTermination = { @Sendable _ in
|
||||||
|
streamingTask.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribes to notes indefinitely
|
||||||
|
///
|
||||||
|
/// This is useful when simply streaming all events indefinitely
|
||||||
|
func streamIndefinitely(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, streamMode: StreamMode? = nil, id: UUID? = nil) -> AsyncStream<NdbNoteLender> {
|
||||||
|
return AsyncStream<NdbNoteLender> { continuation in
|
||||||
|
let streamingTask = Task {
|
||||||
|
for await item in self.advancedStream(filters: filters, to: desiredRelays, streamMode: streamMode, id: id) {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
switch item {
|
||||||
|
case .event(lender: let lender):
|
||||||
|
continuation.yield(lender)
|
||||||
|
case .eose:
|
||||||
|
break
|
||||||
|
case .ndbEose:
|
||||||
|
break
|
||||||
|
case .networkEose:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let timeoutTask = Task {
|
|
||||||
try await Task.sleep(for: timeout)
|
|
||||||
continuation.finish() // End the stream due to timeout.
|
|
||||||
}
|
}
|
||||||
continuation.onTermination = { @Sendable _ in
|
continuation.onTermination = { @Sendable _ in
|
||||||
timeoutTask.cancel()
|
|
||||||
streamingTask.cancel()
|
streamingTask.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribes to data from the user's relays
|
/// Subscribes to data from the user's relays
|
||||||
///
|
func advancedStream(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, timeout: Duration? = nil, streamMode: StreamMode? = nil, id: UUID? = nil) -> AsyncStream<StreamItem> {
|
||||||
/// ## Implementation notes
|
|
||||||
///
|
|
||||||
/// - When we migrate to the local relay model, we should modify this function to stream directly from NostrDB
|
|
||||||
///
|
|
||||||
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
|
||||||
/// - Returns: An async stream of nostr data
|
|
||||||
func subscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, id: UUID? = nil) -> AsyncStream<StreamItem> {
|
|
||||||
return AsyncStream<StreamItem> { continuation in
|
return AsyncStream<StreamItem> { continuation in
|
||||||
let subscriptionId = id ?? UUID()
|
let subscriptionId = id ?? UUID()
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
@@ -104,7 +125,7 @@ extension NostrNetworkManager {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
Log.info("%s: Streaming.", for: .subscription_manager, subscriptionId.uuidString)
|
Log.info("%s: Streaming.", for: .subscription_manager, subscriptionId.uuidString)
|
||||||
for await item in self.sessionSubscribe(filters: filters, to: desiredRelays, id: id) {
|
for await item in self.sessionSubscribe(filters: filters, to: desiredRelays, streamMode: streamMode, id: id) {
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
continuation.yield(item)
|
continuation.yield(item)
|
||||||
}
|
}
|
||||||
@@ -117,9 +138,16 @@ extension NostrNetworkManager {
|
|||||||
}
|
}
|
||||||
Log.info("%s: Terminated.", for: .subscription_manager, subscriptionId.uuidString)
|
Log.info("%s: Terminated.", for: .subscription_manager, subscriptionId.uuidString)
|
||||||
}
|
}
|
||||||
|
let timeoutTask = Task {
|
||||||
|
if let timeout {
|
||||||
|
try await Task.sleep(for: timeout)
|
||||||
|
continuation.finish() // End the stream due to timeout.
|
||||||
|
}
|
||||||
|
}
|
||||||
continuation.onTermination = { @Sendable _ in
|
continuation.onTermination = { @Sendable _ in
|
||||||
Log.info("%s: Cancelled.", for: .subscription_manager, subscriptionId.uuidString)
|
Log.info("%s: Cancelled.", for: .subscription_manager, subscriptionId.uuidString)
|
||||||
multiSessionStreamingTask.cancel()
|
multiSessionStreamingTask.cancel()
|
||||||
|
timeoutTask.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,8 +162,9 @@ extension NostrNetworkManager {
|
|||||||
///
|
///
|
||||||
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
||||||
/// - Returns: An async stream of nostr data
|
/// - Returns: An async stream of nostr data
|
||||||
private func sessionSubscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, id: UUID? = nil) -> AsyncStream<StreamItem> {
|
private func sessionSubscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil, streamMode: StreamMode? = nil, id: UUID? = nil) -> AsyncStream<StreamItem> {
|
||||||
let id = id ?? UUID()
|
let id = id ?? UUID()
|
||||||
|
let streamMode = streamMode ?? defaultStreamMode()
|
||||||
return AsyncStream<StreamItem> { continuation in
|
return AsyncStream<StreamItem> { continuation in
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
Log.debug("Session subscription %s: Started", for: .subscription_manager, id.uuidString)
|
Log.debug("Session subscription %s: Started", for: .subscription_manager, id.uuidString)
|
||||||
@@ -147,10 +176,10 @@ extension NostrNetworkManager {
|
|||||||
let connectedToNetwork = self.pool.network_monitor.currentPath.status == .satisfied
|
let connectedToNetwork = self.pool.network_monitor.currentPath.status == .satisfied
|
||||||
// In normal mode: Issuing EOSE requires EOSE from both NDB and the network, since they are all considered separate relays
|
// In normal mode: Issuing EOSE requires EOSE from both NDB and the network, since they are all considered separate relays
|
||||||
// In experimental local relay model mode: Issuing EOSE requires only EOSE from NDB, since that is the only relay that "matters"
|
// In experimental local relay model mode: Issuing EOSE requires only EOSE from NDB, since that is the only relay that "matters"
|
||||||
let canIssueEOSE = self.experimentalLocalRelayModelSupport ?
|
let canIssueEOSE = switch streamMode {
|
||||||
(ndbEOSEIssued)
|
case .ndbFirst: (ndbEOSEIssued)
|
||||||
:
|
case .ndbAndNetworkParallel: (ndbEOSEIssued && (networkEOSEIssued || !connectedToNetwork))
|
||||||
(ndbEOSEIssued && (networkEOSEIssued || !connectedToNetwork))
|
}
|
||||||
|
|
||||||
if canIssueEOSE {
|
if canIssueEOSE {
|
||||||
Log.debug("Session subscription %s: Issued EOSE for session. Elapsed: %.2f seconds", for: .subscription_manager, id.uuidString, CFAbsoluteTimeGetCurrent() - startTime)
|
Log.debug("Session subscription %s: Issued EOSE for session. Elapsed: %.2f seconds", for: .subscription_manager, id.uuidString, CFAbsoluteTimeGetCurrent() - startTime)
|
||||||
@@ -197,8 +226,10 @@ extension NostrNetworkManager {
|
|||||||
if EXTRA_VERBOSE_LOGGING {
|
if EXTRA_VERBOSE_LOGGING {
|
||||||
Log.debug("Session subscription %s: Received kind %d event with id %s from the network", for: .subscription_manager, id.uuidString, event.kind, event.id.hex())
|
Log.debug("Session subscription %s: Received kind %d event with id %s from the network", for: .subscription_manager, id.uuidString, event.kind, event.id.hex())
|
||||||
}
|
}
|
||||||
if !self.experimentalLocalRelayModelSupport {
|
switch streamMode {
|
||||||
// In normal mode (non-experimental), we stream from ndb but also directly from the network
|
case .ndbFirst:
|
||||||
|
break // NO-OP
|
||||||
|
case .ndbAndNetworkParallel:
|
||||||
continuation.yield(.event(lender: NdbNoteLender(ownedNdbNote: event)))
|
continuation.yield(.event(lender: NdbNoteLender(ownedNdbNote: event)))
|
||||||
}
|
}
|
||||||
case .eose:
|
case .eose:
|
||||||
@@ -229,6 +260,12 @@ extension NostrNetworkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Utility functions
|
||||||
|
|
||||||
|
private func defaultStreamMode() -> StreamMode {
|
||||||
|
self.experimentalLocalRelayModelSupport ? .ndbFirst : .ndbAndNetworkParallel
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Finding specific data from Nostr
|
// MARK: - Finding specific data from Nostr
|
||||||
|
|
||||||
/// Finds a non-replaceable event based on a note ID
|
/// Finds a non-replaceable event based on a note ID
|
||||||
@@ -255,7 +292,7 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
func query(filters: [NostrFilter], to: [RelayURL]? = nil, timeout: Duration? = nil) async -> [NostrEvent] {
|
func query(filters: [NostrFilter], to: [RelayURL]? = nil, timeout: Duration? = nil) async -> [NostrEvent] {
|
||||||
var events: [NostrEvent] = []
|
var events: [NostrEvent] = []
|
||||||
for await noteLender in self.streamNotesUntilEndOfStoredEvents(filters: filters, to: to, timeout: timeout) {
|
for await noteLender in self.streamExistingEvents(filters: filters, to: to, timeout: timeout) {
|
||||||
noteLender.justUseACopy({ events.append($0) })
|
noteLender.justUseACopy({ events.append($0) })
|
||||||
}
|
}
|
||||||
return events
|
return events
|
||||||
@@ -270,7 +307,7 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
let filter = NostrFilter(kinds: nostrKinds, authors: [naddr.author])
|
let filter = NostrFilter(kinds: nostrKinds, authors: [naddr.author])
|
||||||
|
|
||||||
for await noteLender in self.streamNotesUntilEndOfStoredEvents(filters: [filter], to: targetRelays, timeout: timeout) {
|
for await noteLender in self.streamExistingEvents(filters: [filter], to: targetRelays, timeout: timeout) {
|
||||||
// TODO: This can be refactored to borrow the note instead of copying it. But we need to implement `referenced_params` on `UnownedNdbNote` to do so
|
// TODO: This can be refactored to borrow the note instead of copying it. But we need to implement `referenced_params` on `UnownedNdbNote` to do so
|
||||||
guard let event = noteLender.justGetACopy() else { continue }
|
guard let event = noteLender.justGetACopy() else { continue }
|
||||||
if event.referenced_params.first?.param.string() == naddr.identifier {
|
if event.referenced_params.first?.param.string() == naddr.identifier {
|
||||||
@@ -307,7 +344,7 @@ extension NostrNetworkManager {
|
|||||||
var has_event = false
|
var has_event = false
|
||||||
guard let filter else { return nil }
|
guard let filter else { return nil }
|
||||||
|
|
||||||
for await noteLender in self.streamNotesUntilEndOfStoredEvents(filters: [filter], to: find_from) {
|
for await noteLender in self.streamExistingEvents(filters: [filter], to: find_from) {
|
||||||
let foundEvent: FoundEvent? = try? noteLender.borrow({ event in
|
let foundEvent: FoundEvent? = try? noteLender.borrow({ event in
|
||||||
switch query {
|
switch query {
|
||||||
case .profile:
|
case .profile:
|
||||||
@@ -363,7 +400,7 @@ extension NostrNetworkManager {
|
|||||||
enum StreamItem {
|
enum StreamItem {
|
||||||
/// An event which can be borrowed from NostrDB
|
/// An event which can be borrowed from NostrDB
|
||||||
case event(lender: NdbNoteLender)
|
case event(lender: NdbNoteLender)
|
||||||
/// The canonical "end of stored events". See implementations of `subscribe` to see when this event is fired in relation to other EOSEs
|
/// The canonical generic "end of stored events", which depends on the stream mode. See `StreamMode` to see when this event is fired in relation to other EOSEs
|
||||||
case eose
|
case eose
|
||||||
/// "End of stored events" from NostrDB.
|
/// "End of stored events" from NostrDB.
|
||||||
case ndbEose
|
case ndbEose
|
||||||
@@ -386,4 +423,12 @@ extension NostrNetworkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The mode of streaming
|
||||||
|
enum StreamMode {
|
||||||
|
/// Returns notes exclusively through NostrDB, treating it as the only channel for information in the pipeline. Generic EOSE is fired when EOSE is received from NostrDB
|
||||||
|
case ndbFirst
|
||||||
|
/// Returns notes from both NostrDB and the network, in parallel, treating it with similar importance against the network relays. Generic EOSE is fired when EOSE is received from both the network and NostrDB
|
||||||
|
case ndbAndNetworkParallel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,21 +133,15 @@ extension NostrNetworkManager {
|
|||||||
|
|
||||||
func listenAndHandleRelayUpdates() async {
|
func listenAndHandleRelayUpdates() async {
|
||||||
let filter = NostrFilter(kinds: [.relay_list], authors: [delegate.keypair.pubkey])
|
let filter = NostrFilter(kinds: [.relay_list], authors: [delegate.keypair.pubkey])
|
||||||
for await item in self.reader.subscribe(filters: [filter]) {
|
for await noteLender in self.reader.streamIndefinitely(filters: [filter]) {
|
||||||
switch item {
|
|
||||||
case .event(let lender): // Signature validity already ensured at this point
|
|
||||||
let currentRelayListCreationDate = self.getUserCurrentRelayListCreationDate()
|
let currentRelayListCreationDate = self.getUserCurrentRelayListCreationDate()
|
||||||
try? lender.borrow({ note in
|
try? noteLender.borrow({ note in
|
||||||
guard note.pubkey == self.delegate.keypair.pubkey else { return } // Ensure this new list was ours
|
guard note.pubkey == self.delegate.keypair.pubkey else { return } // Ensure this new list was ours
|
||||||
guard note.createdAt > (currentRelayListCreationDate ?? 0) else { return } // Ensure this is a newer list
|
guard note.createdAt > (currentRelayListCreationDate ?? 0) else { return } // Ensure this is a newer list
|
||||||
guard let relayList = try? NIP65.RelayList(event: note) else { return } // Ensure it is a valid NIP-65 list
|
guard let relayList = try? NIP65.RelayList(event: note) else { return } // Ensure it is a valid NIP-65 list
|
||||||
|
|
||||||
try? self.set(userRelayList: relayList) // Set the validated list
|
try? self.set(userRelayList: relayList) // Set the validated list
|
||||||
})
|
})
|
||||||
case .eose: continue
|
|
||||||
case .ndbEose: continue
|
|
||||||
case .networkEose: continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// ProfileObserver.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-09-19.
|
||||||
|
//
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class ProfileObserver: ObservableObject {
|
||||||
|
private let pubkey: Pubkey
|
||||||
|
private var observerTask: Task<Void, any Error>? = nil
|
||||||
|
private let damusState: DamusState
|
||||||
|
|
||||||
|
init(pubkey: Pubkey, damusState: DamusState) {
|
||||||
|
self.pubkey = pubkey
|
||||||
|
self.damusState = damusState
|
||||||
|
self.watchProfileChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func watchProfileChanges() {
|
||||||
|
observerTask?.cancel()
|
||||||
|
observerTask = Task {
|
||||||
|
for await _ in await damusState.nostrNetwork.profilesManager.streamProfile(pubkey: self.pubkey) {
|
||||||
|
try Task.checkCancellation()
|
||||||
|
DispatchQueue.main.async { self.objectWillChange.send() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
observerTask?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import Foundation
|
|||||||
import LinkPresentation
|
import LinkPresentation
|
||||||
import EmojiPicker
|
import EmojiPicker
|
||||||
|
|
||||||
class DamusState: HeadlessDamusState {
|
class DamusState: HeadlessDamusState, ObservableObject {
|
||||||
let keypair: Keypair
|
let keypair: Keypair
|
||||||
let likes: EventCounter
|
let likes: EventCounter
|
||||||
let boosts: EventCounter
|
let boosts: EventCounter
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct Reposted: View {
|
|||||||
|
|
||||||
// Show profile picture of the reposter only if the reposter is not the author of the reposted note.
|
// Show profile picture of the reposter only if the reposter is not the author of the reposted note.
|
||||||
if pubkey != target.pubkey {
|
if pubkey != target.pubkey {
|
||||||
ProfilePicView(pubkey: pubkey, size: eventview_pfp_size(.small), highlight: .none, profiles: damus.profiles, disable_animation: damus.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: eventview_pfp_size(.small), highlight: .none, profiles: damus.profiles, disable_animation: damus.settings.disable_animation, damusState: damus)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
show_profile_action_sheet_if_enabled(damus_state: damus, pubkey: pubkey)
|
show_profile_action_sheet_if_enabled(damus_state: damus, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ struct ChatEventView: View {
|
|||||||
|
|
||||||
var profile_picture_view: some View {
|
var profile_picture_view: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ProfilePicView(pubkey: event.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation)
|
ProfilePicView(pubkey: event.pubkey, size: 32, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation, damusState: damus_state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: event.pubkey)
|
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: event.pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,18 +115,8 @@ class ThreadModel: ObservableObject {
|
|||||||
self.listener?.cancel()
|
self.listener?.cancel()
|
||||||
self.listener = Task {
|
self.listener = Task {
|
||||||
Log.info("subscribing to thread %s ", for: .render, original_event.id.hex())
|
Log.info("subscribing to thread %s ", for: .render, original_event.id.hex())
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: base_filters + meta_filters) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: base_filters + meta_filters) {
|
||||||
switch item {
|
event.justUseACopy({ handle_event(ev: $0) })
|
||||||
case .event(let lender):
|
|
||||||
lender.justUseACopy({ handle_event(ev: $0) })
|
|
||||||
case .eose:
|
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
load_profiles(context: "thread", load: .from_events(Array(event_map.events)), damus_state: damus_state, txn: txn)
|
|
||||||
case .ndbEose:
|
|
||||||
break
|
|
||||||
case .networkEose:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct ReplyQuoteView: View {
|
|||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
if can_show_event {
|
if can_show_event {
|
||||||
ProfilePicView(pubkey: event.pubkey, size: 14, highlight: .reply, profiles: state.profiles, disable_animation: false)
|
ProfilePicView(pubkey: event.pubkey, size: 14, highlight: .reply, profiles: state.profiles, disable_animation: false, damusState: state)
|
||||||
let blur_images = should_blur_images(settings: state.settings, contacts: state.contacts, ev: event, our_pubkey: state.pubkey)
|
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)
|
NoteContentView(damus_state: state, event: event, blur_images: blur_images, size: .small, options: options)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ struct DMChatView: View, KeyboardReadable {
|
|||||||
var Header: some View {
|
var Header: some View {
|
||||||
return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
|
return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
|
|
||||||
ProfileName(pubkey: pubkey, damus: damus_state)
|
ProfileName(pubkey: pubkey, damus: damus_state)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ struct EventProfile: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .center, spacing: 10) {
|
HStack(alignment: .center, spacing: 10) {
|
||||||
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation, show_zappability: true)
|
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation, show_zappability: true, damusState: damus_state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: pubkey)
|
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class EventsModel: ObservableObject {
|
|||||||
loadingTask?.cancel()
|
loadingTask?.cancel()
|
||||||
loadingTask = Task {
|
loadingTask = Task {
|
||||||
DispatchQueue.main.async { self.loading = true }
|
DispatchQueue.main.async { self.loading = true }
|
||||||
outerLoop: for await item in state.nostrNetwork.reader.subscribe(filters: [get_filter()]) {
|
outerLoop: for await item in state.nostrNetwork.reader.advancedStream(filters: [get_filter()]) {
|
||||||
switch item {
|
switch item {
|
||||||
case .event(let lender):
|
case .event(let lender):
|
||||||
Task {
|
Task {
|
||||||
@@ -91,8 +91,6 @@ class EventsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async { self.loading = false }
|
DispatchQueue.main.async { self.loading = false }
|
||||||
guard let txn = NdbTxn(ndb: self.state.ndb) else { return }
|
|
||||||
load_profiles(context: "events_model", load: .from_events(events.all_events), damus_state: state, txn: txn)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,10 +43,8 @@ class FollowPackModel: ObservableObject {
|
|||||||
filter.authors = follow_pack_users
|
filter.authors = follow_pack_users
|
||||||
filter.limit = 500
|
filter.limit = 500
|
||||||
|
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter], to: to_relays) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: [filter], to: to_relays) {
|
||||||
switch item {
|
await event.justUseACopy({ event in
|
||||||
case .event(lender: let lender):
|
|
||||||
await lender.justUseACopy({ event in
|
|
||||||
let should_show_event = await should_show_event(state: damus_state, ev: event)
|
let should_show_event = await should_show_event(state: damus_state, ev: event)
|
||||||
if event.is_textlike && should_show_event && !event.is_reply()
|
if event.is_textlike && should_show_event && !event.is_reply()
|
||||||
{
|
{
|
||||||
@@ -57,13 +55,6 @@ class FollowPackModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
case .eose:
|
|
||||||
continue
|
|
||||||
case .ndbEose:
|
|
||||||
continue
|
|
||||||
case .networkEose:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ struct FollowPackPreviewBody: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true, damusState: state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ struct FollowPackView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
ProfilePicView(pubkey: event.event.pubkey, size: 25, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true, damusState: state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: event.event.pubkey))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,18 +38,8 @@ class FollowersModel: ObservableObject {
|
|||||||
let filters = [filter]
|
let filters = [filter]
|
||||||
self.listener?.cancel()
|
self.listener?.cancel()
|
||||||
self.listener = Task {
|
self.listener = Task {
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: filters) {
|
for await lender in damus_state.nostrNetwork.reader.streamIndefinitely(filters: filters) {
|
||||||
switch item {
|
|
||||||
case .event(let lender):
|
|
||||||
lender.justUseACopy({ self.handle_event(ev: $0) })
|
lender.justUseACopy({ self.handle_event(ev: $0) })
|
||||||
case .eose:
|
|
||||||
guard let txn = NdbTxn(ndb: self.damus_state.ndb) else { return }
|
|
||||||
load_profiles(txn: txn)
|
|
||||||
case .ndbEose:
|
|
||||||
continue
|
|
||||||
case .networkEose:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,31 +61,6 @@ class FollowersModel: ObservableObject {
|
|||||||
has_contact.insert(ev.pubkey)
|
has_contact.insert(ev.pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_profiles<Y>(txn: NdbTxn<Y>) {
|
|
||||||
let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? [], txn: txn)
|
|
||||||
if authors.isEmpty {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let filter = NostrFilter(kinds: [.metadata],
|
|
||||||
authors: authors)
|
|
||||||
|
|
||||||
self.profilesListener?.cancel()
|
|
||||||
self.profilesListener = Task {
|
|
||||||
for await item in await damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
|
||||||
switch item {
|
|
||||||
case .event(let lender):
|
|
||||||
lender.justUseACopy({ self.handle_event(ev: $0) })
|
|
||||||
case .eose: break
|
|
||||||
case .ndbEose:
|
|
||||||
continue
|
|
||||||
case .networkEose:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_event(ev: NostrEvent) {
|
func handle_event(ev: NostrEvent) {
|
||||||
if ev.known_kind == .contacts {
|
if ev.known_kind == .contacts {
|
||||||
Task { await handle_contact_event(ev) }
|
Task { await handle_contact_event(ev) }
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class FollowingModel {
|
|||||||
let filters = [filter]
|
let filters = [filter]
|
||||||
self.listener?.cancel()
|
self.listener?.cancel()
|
||||||
self.listener = Task {
|
self.listener = Task {
|
||||||
for await item in self.damus_state.nostrNetwork.reader.subscribe(filters: filters) {
|
for await item in self.damus_state.nostrNetwork.reader.advancedStream(filters: filters) {
|
||||||
// don't need to do anything here really
|
// don't need to do anything here really
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,13 +64,11 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
filter.authors = Array(authors)
|
filter.authors = Array(authors)
|
||||||
|
|
||||||
|
|
||||||
for await item in state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
for await item in state.nostrNetwork.reader.advancedStream(filters: [filter]) {
|
||||||
switch item {
|
switch item {
|
||||||
case .event(let lender):
|
case .event(let lender):
|
||||||
await lender.justUseACopy({ await self.add_event($0) })
|
await lender.justUseACopy({ await self.add_event($0) })
|
||||||
case .eose:
|
case .eose:
|
||||||
guard let txn = NdbTxn(ndb: state.ndb) else { return }
|
|
||||||
load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn)
|
|
||||||
DispatchQueue.main.async { self.loading = false }
|
DispatchQueue.main.async { self.loading = false }
|
||||||
continue
|
continue
|
||||||
case .ndbEose:
|
case .ndbEose:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ struct ProfilePicturesView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
ForEach(pubkeys.prefix(8), id: \.self) { pubkey in
|
ForEach(pubkeys.prefix(8), id: \.self) { pubkey in
|
||||||
ProfilePicView(pubkey: pubkey, size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, damusState: state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
authors: [Constants.ONBOARDING_FOLLOW_PACK_CURATOR_PUBKEY]
|
authors: [Constants.ONBOARDING_FOLLOW_PACK_CURATOR_PUBKEY]
|
||||||
)
|
)
|
||||||
|
|
||||||
for await lender in self.damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [filter]) {
|
for await lender in self.damus_state.nostrNetwork.reader.streamExistingEvents(filters: [filter]) {
|
||||||
// Check for cancellation on each iteration
|
// Check for cancellation on each iteration
|
||||||
guard !Task.isCancelled else { break }
|
guard !Task.isCancelled else { break }
|
||||||
|
|
||||||
@@ -212,6 +212,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finds all profiles mentioned in the follow packs, and loads the profile data from the network
|
/// Finds all profiles mentioned in the follow packs, and loads the profile data from the network
|
||||||
|
// TODO LOCAL_RELAY_PROFILE: Remove this
|
||||||
private func loadProfiles(for packs: [FollowPackEvent]) async {
|
private func loadProfiles(for packs: [FollowPackEvent]) async {
|
||||||
var allPubkeys: [Pubkey] = []
|
var allPubkeys: [Pubkey] = []
|
||||||
|
|
||||||
@@ -223,7 +224,7 @@ class SuggestedUsersViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let profileFilter = NostrFilter(kinds: [.metadata], authors: allPubkeys)
|
let profileFilter = NostrFilter(kinds: [.metadata], authors: allPubkeys)
|
||||||
for await _ in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [profileFilter]) {
|
for await _ in damus_state.nostrNetwork.reader.streamExistingEvents(filters: [profileFilter]) {
|
||||||
// NO-OP. We just need NostrDB to ingest these for them to be available elsewhere, no need to analyze the data
|
// NO-OP. We just need NostrDB to ingest these for them to be available elsewhere, no need to analyze the data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ struct PostView: View {
|
|||||||
HStack(alignment: .top, spacing: 0) {
|
HStack(alignment: .top, spacing: 0) {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if let prompt_view {
|
if let prompt_view {
|
||||||
|
|||||||
@@ -21,22 +21,4 @@ class CondensedProfilePicturesViewModel: ObservableObject {
|
|||||||
self.pubkeys = pubkeys
|
self.pubkeys = pubkeys
|
||||||
self.maxPictures = min(maxPictures, pubkeys.count)
|
self.maxPictures = min(maxPictures, pubkeys.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func load() {
|
|
||||||
loadingTask?.cancel()
|
|
||||||
loadingTask = Task { try? await loadingTask() }
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadingTask() async throws {
|
|
||||||
let filter = NostrFilter(kinds: [.metadata], authors: shownPubkeys)
|
|
||||||
let _ = await state.nostrNetwork.reader.query(filters: [filter])
|
|
||||||
for await _ in state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [filter]) {
|
|
||||||
// NO-OP, we just need it to be loaded into NostrDB.
|
|
||||||
try Task.checkCancellation()
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
// Cause the view to re-render with the newly loaded profiles
|
|
||||||
self.objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,34 +76,21 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
|
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
|
||||||
text_filter.authors = [pubkey]
|
text_filter.authors = [pubkey]
|
||||||
text_filter.limit = 500
|
text_filter.limit = 500
|
||||||
for await item in damus.nostrNetwork.reader.subscribe(filters: [text_filter]) {
|
|
||||||
switch item {
|
|
||||||
case .event(let lender):
|
|
||||||
lender.justUseACopy({ handleNostrEvent($0) })
|
|
||||||
case .eose: break
|
|
||||||
case .ndbEose: break
|
|
||||||
case .networkEose: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
guard let txn = NdbTxn(ndb: damus.ndb) else { return }
|
|
||||||
load_profiles(context: "profile", load: .from_events(events.events), damus_state: damus, txn: txn)
|
|
||||||
await bumpUpProgress()
|
await bumpUpProgress()
|
||||||
|
for await event in damus.nostrNetwork.reader.streamIndefinitely(filters: [text_filter]) {
|
||||||
|
event.justUseACopy({ handleNostrEvent($0) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
profileListener?.cancel()
|
profileListener?.cancel()
|
||||||
profileListener = Task {
|
profileListener = Task {
|
||||||
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
|
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
|
||||||
var relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey])
|
var relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey])
|
||||||
profile_filter.authors = [pubkey]
|
profile_filter.authors = [pubkey]
|
||||||
for await item in damus.nostrNetwork.reader.subscribe(filters: [profile_filter, relay_list_filter]) {
|
|
||||||
switch item {
|
|
||||||
case .event(let lender):
|
|
||||||
lender.justUseACopy({ handleNostrEvent($0) })
|
|
||||||
case .eose: break
|
|
||||||
case .ndbEose: break
|
|
||||||
case .networkEose: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await bumpUpProgress()
|
await bumpUpProgress()
|
||||||
|
for await event in damus.nostrNetwork.reader.streamIndefinitely(filters: [profile_filter, relay_list_filter]) {
|
||||||
|
event.justUseACopy({ handleNostrEvent($0) })
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
conversationListener?.cancel()
|
conversationListener?.cancel()
|
||||||
conversationListener = Task {
|
conversationListener = Task {
|
||||||
@@ -127,10 +114,8 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey])
|
let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey])
|
||||||
let conversations_filter_us = NostrFilter(kinds: conversation_kinds, pubkeys: [pubkey], limit: limit, authors: [damus.pubkey])
|
let conversations_filter_us = NostrFilter(kinds: conversation_kinds, pubkeys: [pubkey], limit: limit, authors: [damus.pubkey])
|
||||||
print("subscribing to conversation events from and to profile \(pubkey)")
|
print("subscribing to conversation events from and to profile \(pubkey)")
|
||||||
for await item in self.damus.nostrNetwork.reader.subscribe(filters: [conversations_filter_them, conversations_filter_us]) {
|
for await noteLender in self.damus.nostrNetwork.reader.streamIndefinitely(filters: [conversations_filter_them, conversations_filter_us]) {
|
||||||
switch item {
|
try? noteLender.borrow { ev in
|
||||||
case .event(let lender):
|
|
||||||
try? lender.borrow { ev in
|
|
||||||
if !seen_event.contains(ev.id) {
|
if !seen_event.contains(ev.id) {
|
||||||
let event = ev.toOwned()
|
let event = ev.toOwned()
|
||||||
Task { await self.add_event(event) }
|
Task { await self.add_event(event) }
|
||||||
@@ -140,13 +125,6 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
conversation_events.insert(ev.id)
|
conversation_events.insert(ev.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .eose:
|
|
||||||
continue
|
|
||||||
case .ndbEose:
|
|
||||||
continue
|
|
||||||
case .networkEose:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,22 +190,13 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
profile_filter.authors = [pubkey]
|
profile_filter.authors = [pubkey]
|
||||||
self.findRelaysListener?.cancel()
|
self.findRelaysListener?.cancel()
|
||||||
self.findRelaysListener = Task {
|
self.findRelaysListener = Task {
|
||||||
for await item in await damus.nostrNetwork.reader.subscribe(filters: [profile_filter]) {
|
for await noteLender in damus.nostrNetwork.reader.streamIndefinitely(filters: [profile_filter]) {
|
||||||
switch item {
|
try? noteLender.borrow { event in
|
||||||
case .event(let lender):
|
|
||||||
try? lender.borrow { event in
|
|
||||||
if case .contacts = event.known_kind {
|
if case .contacts = event.known_kind {
|
||||||
// TODO: Is this correct?
|
// TODO: Is this correct?
|
||||||
self.legacy_relay_list = decode_json_relays(event.content)
|
self.legacy_relay_list = decode_json_relays(event.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .eose:
|
|
||||||
break
|
|
||||||
case .ndbEose:
|
|
||||||
break
|
|
||||||
case .networkEose:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,12 @@ struct CondensedProfilePicturesView: View {
|
|||||||
// Using ZStack to make profile pictures floating and stacked on top of each other.
|
// Using ZStack to make profile pictures floating and stacked on top of each other.
|
||||||
ZStack {
|
ZStack {
|
||||||
ForEach((0..<model.maxPictures).reversed(), id: \.self) { index in
|
ForEach((0..<model.maxPictures).reversed(), id: \.self) { index in
|
||||||
ProfilePicView(pubkey: model.pubkeys[index], size: 32.0, highlight: .none, profiles: model.state.profiles, disable_animation: model.state.settings.disable_animation)
|
ProfilePicView(pubkey: model.pubkeys[index], size: 32.0, highlight: .none, profiles: model.state.profiles, disable_animation: model.state.settings.disable_animation, damusState: model.state)
|
||||||
.offset(x: CGFloat(index) * 20)
|
.offset(x: CGFloat(index) * 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Padding is needed so that other components drawn adjacent to this view don't get drawn on top.
|
// Padding is needed so that other components drawn adjacent to this view don't get drawn on top.
|
||||||
.padding(.trailing, CGFloat((model.maxPictures - 1) * 20))
|
.padding(.trailing, CGFloat((model.maxPictures - 1) * 20))
|
||||||
.onAppear {
|
|
||||||
self.model.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ struct MaybeAnonPfpView: View {
|
|||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
} else {
|
} else {
|
||||||
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true)
|
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation, show_zappability: true, damusState: state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
show_profile_action_sheet_if_enabled(damus_state: state, pubkey: pubkey)
|
show_profile_action_sheet_if_enabled(damus_state: state, pubkey: pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ struct ProfileActionSheetView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .center) {
|
VStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
if let url = self.profile_data()?.profile?.website_url {
|
if let url = self.profile_data()?.profile?.website_url {
|
||||||
WebsiteLink(url: url, style: .accent)
|
WebsiteLink(url: url, style: .accent)
|
||||||
.padding(.top, -15)
|
.padding(.top, -15)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ struct ProfileName: View {
|
|||||||
@State var donation: Int?
|
@State var donation: Int?
|
||||||
@State var purple_account: DamusPurple.Account?
|
@State var purple_account: DamusPurple.Account?
|
||||||
@State var nip05_domain_favicon: FaviconURL?
|
@State var nip05_domain_favicon: FaviconURL?
|
||||||
|
@StateObject var profileObserver: ProfileObserver
|
||||||
|
|
||||||
init(pubkey: Pubkey, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true, supporterBadgeStyle: SupporterBadge.Style = .compact) {
|
init(pubkey: Pubkey, prefix: String = "", damus: DamusState, show_nip5_domain: Bool = true, supporterBadgeStyle: SupporterBadge.Style = .compact) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
@@ -53,6 +54,7 @@ struct ProfileName: View {
|
|||||||
self.show_nip5_domain = show_nip5_domain
|
self.show_nip5_domain = show_nip5_domain
|
||||||
self.supporterBadgeStyle = supporterBadgeStyle
|
self.supporterBadgeStyle = supporterBadgeStyle
|
||||||
self.purple_account = nil
|
self.purple_account = nil
|
||||||
|
self._profileObserver = StateObject.init(wrappedValue: ProfileObserver(pubkey: pubkey, damusState: damus))
|
||||||
}
|
}
|
||||||
|
|
||||||
var friend_type: FriendType? {
|
var friend_type: FriendType? {
|
||||||
|
|||||||
@@ -75,8 +75,10 @@ struct ProfilePicView: View {
|
|||||||
let privacy_sensitive: Bool
|
let privacy_sensitive: Bool
|
||||||
|
|
||||||
@State var picture: String?
|
@State var picture: String?
|
||||||
|
@StateObject private var profileObserver: ProfileObserver
|
||||||
|
@EnvironmentObject var damusState: DamusState
|
||||||
|
|
||||||
init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil, show_zappability: Bool? = nil, privacy_sensitive: Bool = false) {
|
init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil, show_zappability: Bool? = nil, privacy_sensitive: Bool = false, damusState: DamusState) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.profiles = profiles
|
self.profiles = profiles
|
||||||
self.size = size
|
self.size = size
|
||||||
@@ -85,6 +87,7 @@ struct ProfilePicView: View {
|
|||||||
self.disable_animation = disable_animation
|
self.disable_animation = disable_animation
|
||||||
self.zappability_indicator = show_zappability ?? false
|
self.zappability_indicator = show_zappability ?? false
|
||||||
self.privacy_sensitive = privacy_sensitive
|
self.privacy_sensitive = privacy_sensitive
|
||||||
|
self._profileObserver = StateObject.init(wrappedValue: ProfileObserver(pubkey: pubkey, damusState: damusState))
|
||||||
}
|
}
|
||||||
|
|
||||||
var privacy_sensitive_pubkey: Pubkey {
|
var privacy_sensitive_pubkey: Pubkey {
|
||||||
@@ -163,7 +166,8 @@ struct ProfilePicView_Previews: PreviewProvider {
|
|||||||
size: 100,
|
size: 100,
|
||||||
highlight: .none,
|
highlight: .none,
|
||||||
profiles: make_preview_profiles(pubkey),
|
profiles: make_preview_profiles(pubkey),
|
||||||
disable_animation: false
|
disable_animation: false,
|
||||||
|
damusState: test_damus_state
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ struct ProfileView: View {
|
|||||||
let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey)
|
let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey)
|
||||||
|
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
.padding(.top, -(pfp_size / 2.0))
|
.padding(.top, -(pfp_size / 2.0))
|
||||||
.offset(y: pfpOffset())
|
.offset(y: pfpOffset())
|
||||||
.scaleEffect(pfpScale())
|
.scaleEffect(pfpScale())
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ struct DamusPurpleAccountView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
ProfilePicView(pubkey: account.pubkey, size: pfp_size, highlight: .custom(Color.black.opacity(0.4), 1.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: account.pubkey, size: pfp_size, highlight: .custom(Color.black.opacity(0.4), 1.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
.background(Color.black.opacity(0.4).clipShape(Circle()))
|
.background(Color.black.opacity(0.4).clipShape(Circle()))
|
||||||
.shadow(color: .black, radius: 10, x: 0.0, y: 5)
|
.shadow(color: .black, radius: 10, x: 0.0, y: 5)
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct RelayAdminDetail: View {
|
|||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.foregroundColor(DamusColors.mediumGrey)
|
.foregroundColor(DamusColors.mediumGrey)
|
||||||
if let pubkey = nip11?.pubkey {
|
if let pubkey = nip11?.pubkey {
|
||||||
ProfilePicView(pubkey: pubkey, size: 40, highlight: .custom(.gray.opacity(0.5), 1), profiles: state.profiles, disable_animation: state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 40, highlight: .custom(.gray.opacity(0.5), 1), profiles: state.profiles, disable_animation: state.settings.disable_animation, damusState: state)
|
||||||
.padding(.bottom, 5)
|
.padding(.bottom, 5)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||||
|
|||||||
@@ -62,20 +62,14 @@ class SearchHomeModel: ObservableObject {
|
|||||||
var follow_list_filter = NostrFilter(kinds: [.follow_list])
|
var follow_list_filter = NostrFilter(kinds: [.follow_list])
|
||||||
follow_list_filter.until = UInt32(Date.now.timeIntervalSince1970)
|
follow_list_filter.until = UInt32(Date.now.timeIntervalSince1970)
|
||||||
|
|
||||||
for await noteLender in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [follow_list_filter], to: to_relays) {
|
for await noteLender in damus_state.nostrNetwork.reader.streamExistingEvents(filters: [follow_list_filter], to: to_relays) {
|
||||||
await noteLender.justUseACopy({ await self.handleFollowPackEvent($0) })
|
await noteLender.justUseACopy({ await self.handleFollowPackEvent($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
for await noteLender in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [get_base_filter()], to: to_relays) {
|
for await noteLender in damus_state.nostrNetwork.reader.streamExistingEvents(filters: [get_base_filter()], to: to_relays) {
|
||||||
await noteLender.justUseACopy({ await self.handleEvent($0) })
|
await noteLender.justUseACopy({ await self.handleEvent($0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
let allEvents = events.all_events + followPackEvents.all_events
|
|
||||||
let task = load_profiles(context: "universe", load: .from_events(allEvents), damus_state: damus_state, txn: txn)
|
|
||||||
|
|
||||||
try? await task?.value
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loading = false
|
self.loading = false
|
||||||
}
|
}
|
||||||
@@ -144,28 +138,3 @@ enum PubkeysToLoad {
|
|||||||
case from_events([NostrEvent])
|
case from_events([NostrEvent])
|
||||||
case from_keys([Pubkey])
|
case from_keys([Pubkey])
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_profiles<Y>(context: String, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) -> Task<Void, any Error>? {
|
|
||||||
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn)
|
|
||||||
|
|
||||||
guard !authors.isEmpty else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task {
|
|
||||||
print("load_profiles[\(context)]: requesting \(authors.count) profiles from relay pool")
|
|
||||||
let filter = NostrFilter(kinds: [.metadata], authors: authors)
|
|
||||||
|
|
||||||
for await noteLender in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [filter]) {
|
|
||||||
let now = UInt64(Date.now.timeIntervalSince1970)
|
|
||||||
try noteLender.borrow { event in
|
|
||||||
if event.known_kind == .metadata {
|
|
||||||
damus_state.ndb.write_profile_last_fetched(pubkey: event.pubkey, fetched_at: now)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("load_profiles[\(context)]: done loading \(authors.count) profiles from relay pool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,7 @@ class SearchModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let txn = NdbTxn(ndb: state.ndb) else { return }
|
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn)
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loading = false
|
self.loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ struct UserStatusSheet: View {
|
|||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
ProfilePicView(pubkey: keypair.pubkey, size: 120.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: keypair.pubkey, size: 120.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
|||||||
@@ -454,37 +454,21 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
let id = UUID()
|
let id = UUID()
|
||||||
Log.info("Initial filter task started with ID %s", for: .homeModel, id.uuidString)
|
Log.info("Initial filter task started with ID %s", for: .homeModel, id.uuidString)
|
||||||
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: [filter]) {
|
||||||
switch item {
|
await event.justUseACopy({ await process_event(ev: $0, context: .initialContactList) })
|
||||||
case .event(let lender):
|
|
||||||
await lender.justUseACopy({ await process_event(ev: $0, context: .initialContactList) })
|
|
||||||
continue
|
|
||||||
case .eose:
|
|
||||||
if !done_init {
|
if !done_init {
|
||||||
done_init = true
|
done_init = true
|
||||||
Log.info("Initial filter task %s: Done initialization; Elapsed time: %.2f seconds", for: .homeModel, id.uuidString, CFAbsoluteTimeGetCurrent() - startTime)
|
Log.info("Initial filter task %s: Done initialization; Elapsed time: %.2f seconds", for: .homeModel, id.uuidString, CFAbsoluteTimeGetCurrent() - startTime)
|
||||||
send_home_filters()
|
send_home_filters()
|
||||||
}
|
}
|
||||||
break
|
|
||||||
case .ndbEose:
|
|
||||||
break
|
|
||||||
case .networkEose:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
let relayListFilter = NostrFilter(kinds: [.relay_list], limit: 1, authors: [damus_state.pubkey])
|
let relayListFilter = NostrFilter(kinds: [.relay_list], limit: 1, authors: [damus_state.pubkey])
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [relayListFilter]) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: [relayListFilter]) {
|
||||||
switch item {
|
await event.justUseACopy({ await process_event(ev: $0, context: .initialRelayList) })
|
||||||
case .event(let lender):
|
|
||||||
await lender.justUseACopy({ await process_event(ev: $0, context: .initialRelayList) })
|
|
||||||
case .eose: break
|
|
||||||
case .ndbEose: break
|
|
||||||
case .networkEose: break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -543,41 +527,25 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
|
|
||||||
self.contactsHandlerTask?.cancel()
|
self.contactsHandlerTask?.cancel()
|
||||||
self.contactsHandlerTask = Task {
|
self.contactsHandlerTask = Task {
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: contacts_filters) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: contacts_filters) {
|
||||||
switch item {
|
await event.justUseACopy({ await process_event(ev: $0, context: .contacts) })
|
||||||
case .event(let lender):
|
|
||||||
await lender.justUseACopy({ await process_event(ev: $0, context: .contacts) })
|
|
||||||
case .eose: continue
|
|
||||||
case .ndbEose: continue
|
|
||||||
case .networkEose: continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.notificationsHandlerTask?.cancel()
|
self.notificationsHandlerTask?.cancel()
|
||||||
self.notificationsHandlerTask = Task {
|
self.notificationsHandlerTask = Task {
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: notifications_filters) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: notifications_filters) {
|
||||||
switch item {
|
await event.justUseACopy({ await process_event(ev: $0, context: .notifications) })
|
||||||
case .event(let lender):
|
|
||||||
await lender.justUseACopy({ await process_event(ev: $0, context: .notifications) })
|
|
||||||
case .eose:
|
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
load_profiles(context: "notifications", load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state, txn: txn)
|
|
||||||
case .ndbEose: break
|
|
||||||
case .networkEose: break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.dmsHandlerTask?.cancel()
|
self.dmsHandlerTask?.cancel()
|
||||||
self.dmsHandlerTask = Task {
|
self.dmsHandlerTask = Task {
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: dms_filters) {
|
for await item in damus_state.nostrNetwork.reader.advancedStream(filters: dms_filters) {
|
||||||
switch item {
|
switch item {
|
||||||
case .event(let lender):
|
case .event(let lender):
|
||||||
await lender.justUseACopy({ await process_event(ev: $0, context: .dms) })
|
await lender.justUseACopy({ await process_event(ev: $0, context: .dms) })
|
||||||
case .eose:
|
case .eose:
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
var dms = dms.dms.flatMap { $0.events }
|
var dms = dms.dms.flatMap { $0.events }
|
||||||
dms.append(contentsOf: incoming_dms)
|
dms.append(contentsOf: incoming_dms)
|
||||||
load_profiles(context: "dms", load: .from_events(dms), damus_state: damus_state, txn: txn)
|
|
||||||
case .ndbEose: break
|
case .ndbEose: break
|
||||||
case .networkEose: break
|
case .networkEose: break
|
||||||
}
|
}
|
||||||
@@ -591,14 +559,8 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
var filter = NostrFilter(kinds: [.nwc_response])
|
var filter = NostrFilter(kinds: [.nwc_response])
|
||||||
filter.authors = [nwc.pubkey]
|
filter.authors = [nwc.pubkey]
|
||||||
filter.limit = 0
|
filter.limit = 0
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter], to: [nwc.relay]) {
|
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: [filter], to: [nwc.relay]) {
|
||||||
switch item {
|
await event.justUseACopy({ await process_event(ev: $0, context: .nwc) })
|
||||||
case .event(let lender):
|
|
||||||
await lender.justUseACopy({ await process_event(ev: $0, context: .nwc) })
|
|
||||||
case .eose: continue
|
|
||||||
case .ndbEose: continue
|
|
||||||
case .networkEose: continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,7 +615,7 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loading = true
|
self.loading = true
|
||||||
}
|
}
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: home_filters, id: id) {
|
for await item in damus_state.nostrNetwork.reader.advancedStream(filters: home_filters, id: id) {
|
||||||
switch item {
|
switch item {
|
||||||
case .event(let lender):
|
case .event(let lender):
|
||||||
let currentTime = CFAbsoluteTimeGetCurrent()
|
let currentTime = CFAbsoluteTimeGetCurrent()
|
||||||
@@ -664,20 +626,15 @@ class HomeModel: ContactsDelegate, ObservableObject {
|
|||||||
let eoseTime = CFAbsoluteTimeGetCurrent()
|
let eoseTime = CFAbsoluteTimeGetCurrent()
|
||||||
Log.info("Home handler task %s: Received general EOSE after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
Log.info("Home handler task %s: Received general EOSE after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
||||||
|
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
load_profiles(context: "home", load: .from_events(events.events), damus_state: damus_state, txn: txn)
|
|
||||||
|
|
||||||
let finishTime = CFAbsoluteTimeGetCurrent()
|
let finishTime = CFAbsoluteTimeGetCurrent()
|
||||||
Log.info("Home handler task %s: Completed initial loading task after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
Log.info("Home handler task %s: Completed initial loading task after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
||||||
case .ndbEose:
|
case .ndbEose:
|
||||||
let eoseTime = CFAbsoluteTimeGetCurrent()
|
let eoseTime = CFAbsoluteTimeGetCurrent()
|
||||||
Log.info("Home handler task %s: Received NDB EOSE after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
Log.info("Home handler task %s: Received NDB EOSE after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
||||||
|
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loading = false
|
self.loading = false
|
||||||
}
|
}
|
||||||
load_profiles(context: "home", load: .from_events(events.events), damus_state: damus_state, txn: txn)
|
|
||||||
|
|
||||||
let finishTime = CFAbsoluteTimeGetCurrent()
|
let finishTime = CFAbsoluteTimeGetCurrent()
|
||||||
Log.info("Home handler task %s: Completed initial NDB loading task after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
Log.info("Home handler task %s: Completed initial NDB loading task after %.2f seconds", for: .homeModel, id.uuidString, eoseTime - startTime)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ struct SideMenuView: View {
|
|||||||
return VStack(alignment: .leading) {
|
return VStack(alignment: .leading) {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
|
|
||||||
ProfilePicView(pubkey: damus_state.pubkey, size: 50, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: damus_state.pubkey, size: 50, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|||||||
@@ -182,10 +182,8 @@ class WalletModel: ObservableObject {
|
|||||||
]
|
]
|
||||||
|
|
||||||
nostrNetwork.send(event: requestEvent, to: [currentNwcUrl.relay], skipEphemeralRelays: false)
|
nostrNetwork.send(event: requestEvent, to: [currentNwcUrl.relay], skipEphemeralRelays: false)
|
||||||
for await item in nostrNetwork.reader.subscribe(filters: responseFilters, to: [currentNwcUrl.relay], timeout: timeout) {
|
for await event in nostrNetwork.reader.timedStream(filters: responseFilters, to: [currentNwcUrl.relay], timeout: timeout) {
|
||||||
switch item {
|
guard let responseEvent = try? event.getCopy() else { throw .internalError }
|
||||||
case .event(let lender):
|
|
||||||
guard let responseEvent = try? lender.getCopy() else { throw .internalError }
|
|
||||||
|
|
||||||
let fullWalletResponse: WalletConnect.FullWalletResponse
|
let fullWalletResponse: WalletConnect.FullWalletResponse
|
||||||
do { fullWalletResponse = try WalletConnect.FullWalletResponse(from: responseEvent, nwc: currentNwcUrl) }
|
do { fullWalletResponse = try WalletConnect.FullWalletResponse(from: responseEvent, nwc: currentNwcUrl) }
|
||||||
@@ -196,13 +194,6 @@ class WalletModel: ObservableObject {
|
|||||||
|
|
||||||
guard let result = fullWalletResponse.response.result else { throw .walletEmptyResponse }
|
guard let result = fullWalletResponse.response.result else { throw .walletEmptyResponse }
|
||||||
return result
|
return result
|
||||||
case .eose:
|
|
||||||
continue
|
|
||||||
case .ndbEose:
|
|
||||||
continue
|
|
||||||
case .networkEose:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
do { try Task.checkCancellation() } catch { throw .cancelled }
|
do { try Task.checkCancellation() } catch { throw .cancelled }
|
||||||
throw .responseTimeout
|
throw .responseTimeout
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ struct TransactionView: View {
|
|||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
ZStack {
|
ZStack {
|
||||||
ProfilePicView(pubkey: pubkey ?? ANON_PUBKEY, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, privacy_sensitive: true)
|
ProfilePicView(pubkey: pubkey ?? ANON_PUBKEY, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, privacy_sensitive: true, damusState: damus_state)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if let pubkey {
|
if let pubkey {
|
||||||
damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
|
||||||
|
|||||||
@@ -33,21 +33,8 @@ class ZapsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
zapCommsListener?.cancel()
|
zapCommsListener?.cancel()
|
||||||
zapCommsListener = Task {
|
zapCommsListener = Task {
|
||||||
for await item in state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
for await event in state.nostrNetwork.reader.streamIndefinitely(filters: [filter]) {
|
||||||
switch item {
|
await event.justUseACopy({ await self.handle_event(ev: $0) })
|
||||||
case .event(let lender):
|
|
||||||
await lender.justUseACopy({ event in
|
|
||||||
await self.handle_event(ev: event)
|
|
||||||
})
|
|
||||||
case .eose:
|
|
||||||
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
|
|
||||||
guard let txn = NdbTxn(ndb: state.ndb) else { return }
|
|
||||||
load_profiles(context: "zaps_model", load: .from_events(events), damus_state: state, txn: txn)
|
|
||||||
case .ndbEose:
|
|
||||||
break
|
|
||||||
case .networkEose:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ struct QRCodeView: View {
|
|||||||
let profile_txn = damus_state.profiles.lookup(id: pubkey, txn_name: "qrview-profile")
|
let profile_txn = damus_state.profiles.lookup(id: pubkey, txn_name: "qrview-profile")
|
||||||
let profile = profile_txn?.unsafeUnownedValue
|
let profile = profile_txn?.unsafeUnownedValue
|
||||||
|
|
||||||
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
if let display_name = profile?.display_name {
|
if let display_name = profile?.display_name {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ struct UserView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
|
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, damusState: damus_state)
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
ProfileName(pubkey: pubkey, damus: damus_state, show_nip5_domain: false)
|
ProfileName(pubkey: pubkey, damus: damus_state, show_nip5_domain: false)
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ enum NdbNoteLender: Sendable {
|
|||||||
return try self.getCopy()
|
return try self.getCopy()
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
assertionFailure("Unexpected error while fetching a copy of an NdbNote: \(error.localizedDescription)")
|
// assertionFailure("Unexpected error while fetching a copy of an NdbNote: \(error.localizedDescription)")
|
||||||
Log.error("Unexpected error while fetching a copy of an NdbNote: %s", for: .ndb, error.localizedDescription)
|
Log.error("Unexpected error while fetching a copy of an NdbNote: %s", for: .ndb, error.localizedDescription)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user