Improve Follow pack timeline loading logic in the Universe view
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -1161,6 +1161,9 @@
|
|||||||
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 */; };
|
||||||
|
D72C01312E78C10500AACB67 /* 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 */; };
|
||||||
D72E12782BEED22500F4F781 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12772BEED22400F4F781 /* Array.swift */; };
|
D72E12782BEED22500F4F781 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12772BEED22400F4F781 /* Array.swift */; };
|
||||||
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
|
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
|
||||||
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
|
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
|
||||||
@@ -2607,6 +2610,7 @@
|
|||||||
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>"; };
|
||||||
|
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>"; };
|
||||||
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
|
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
|
||||||
@@ -4301,6 +4305,7 @@
|
|||||||
5C78A7922E3036F800CF177D /* Models */ = {
|
5C78A7922E3036F800CF177D /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D72C01302E78C0FB00AACB67 /* CondensedProfilePicturesViewModel.swift */,
|
||||||
4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */,
|
4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */,
|
||||||
4C363A912825FCF2006E126D /* ProfileUpdate.swift */,
|
4C363A912825FCF2006E126D /* ProfileUpdate.swift */,
|
||||||
);
|
);
|
||||||
@@ -5783,6 +5788,7 @@
|
|||||||
D706C5AF2D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
D706C5AF2D5D31C20027C627 /* AutoSaveIndicatorView.swift in Sources */,
|
||||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
||||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||||
|
D72C01312E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */,
|
||||||
4C1253682A76D2470004F4B8 /* MuteNotify.swift in Sources */,
|
4C1253682A76D2470004F4B8 /* MuteNotify.swift in Sources */,
|
||||||
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */,
|
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */,
|
||||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||||
@@ -6385,6 +6391,7 @@
|
|||||||
82D6FC082CD99F7900C925F4 /* ProfileZapLinkView.swift in Sources */,
|
82D6FC082CD99F7900C925F4 /* ProfileZapLinkView.swift in Sources */,
|
||||||
D71AD8FE2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
D71AD8FE2CEC176A002E2C3C /* AppAccessibilityIdentifiers.swift in Sources */,
|
||||||
82D6FC092CD99F7900C925F4 /* AboutView.swift in Sources */,
|
82D6FC092CD99F7900C925F4 /* AboutView.swift in Sources */,
|
||||||
|
D72C01332E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */,
|
||||||
82D6FC0A2CD99F7900C925F4 /* ProfileName.swift in Sources */,
|
82D6FC0A2CD99F7900C925F4 /* ProfileName.swift in Sources */,
|
||||||
82D6FC0B2CD99F7900C925F4 /* ProfilePictureSelector.swift in Sources */,
|
82D6FC0B2CD99F7900C925F4 /* ProfilePictureSelector.swift in Sources */,
|
||||||
82D6FC0C2CD99F7900C925F4 /* EditMetadataView.swift in Sources */,
|
82D6FC0C2CD99F7900C925F4 /* EditMetadataView.swift in Sources */,
|
||||||
@@ -6940,6 +6947,7 @@
|
|||||||
D703D7502C6709F500A400EA /* NdbTxn.swift in Sources */,
|
D703D7502C6709F500A400EA /* NdbTxn.swift in Sources */,
|
||||||
D703D77E2C670C1100A400EA /* NostrKind.swift in Sources */,
|
D703D77E2C670C1100A400EA /* NostrKind.swift in Sources */,
|
||||||
D73E5F972C6AA7B7007EB227 /* SuggestedHashtagsView.swift in Sources */,
|
D73E5F972C6AA7B7007EB227 /* SuggestedHashtagsView.swift in Sources */,
|
||||||
|
D72C01322E78C10500AACB67 /* CondensedProfilePicturesViewModel.swift in Sources */,
|
||||||
D703D7B22C6710AF00A400EA /* ContentParsing.swift in Sources */,
|
D703D7B22C6710AF00A400EA /* ContentParsing.swift in Sources */,
|
||||||
D703D7522C670A1400A400EA /* Log.swift in Sources */,
|
D703D7522C670A1400A400EA /* Log.swift in Sources */,
|
||||||
D73E5E1B2C6A9672007EB227 /* LikeCounter.swift in Sources */,
|
D73E5E1B2C6A9672007EB227 /* LikeCounter.swift in Sources */,
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ class RelayPool {
|
|||||||
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
||||||
private var last_network_status: NWPath.Status = .unsatisfied
|
private var last_network_status: NWPath.Status = .unsatisfied
|
||||||
|
|
||||||
|
/// The limit of maximum concurrent subscriptions. Any subscriptions beyond this limit will be paused until subscriptions clear
|
||||||
|
/// This is to avoid error states and undefined behaviour related to hitting subscription limits on the relays, by letting those wait instead — with the principle that slower is better than broken.
|
||||||
|
static let MAX_CONCURRENT_SUBSCRIPTION_LIMIT = 10 // This number is only an educated guess at this point.
|
||||||
|
|
||||||
func close() {
|
func close() {
|
||||||
disconnect()
|
disconnect()
|
||||||
relays = []
|
relays = []
|
||||||
@@ -102,10 +106,17 @@ class RelayPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func register_handler(sub_id: String, handler: @escaping (RelayURL, NostrConnectionEvent) -> ()) {
|
func register_handler(sub_id: String, handler: @escaping (RelayURL, NostrConnectionEvent) -> ()) async {
|
||||||
|
while handlers.count > Self.MAX_CONCURRENT_SUBSCRIPTION_LIMIT {
|
||||||
|
Log.debug("%s: Too many subscriptions, waiting for subscription pool to clear", for: .networking, sub_id)
|
||||||
|
try? await Task.sleep(for: .seconds(1))
|
||||||
|
}
|
||||||
|
Log.debug("%s: Subscription pool cleared", for: .networking, sub_id)
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
// don't add duplicate handlers
|
// don't add duplicate handlers
|
||||||
if handler.sub_id == sub_id {
|
if handler.sub_id == sub_id {
|
||||||
|
assertionFailure("Duplicate handlers are not allowed. Proper error handling for this has not been built yet.")
|
||||||
|
Log.error("Duplicate handlers are not allowed. Error handling for this has not been built yet.", for: .networking)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ struct SaveKeysView: View {
|
|||||||
add_rw_relay(self.pool, relay)
|
add_rw_relay(self.pool, relay)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pool.register_handler(sub_id: "signup", handler: handle_event)
|
Task { await self.pool.register_handler(sub_id: "signup", handler: handle_event) }
|
||||||
|
|
||||||
self.loading = true
|
self.loading = true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// CondensedProfilePicturesViewModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2025-09-15.
|
||||||
|
//
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class CondensedProfilePicturesViewModel: ObservableObject {
|
||||||
|
let state: DamusState
|
||||||
|
let pubkeys: [Pubkey]
|
||||||
|
let maxPictures: Int
|
||||||
|
var shownPubkeys: [Pubkey] {
|
||||||
|
return Array(pubkeys.prefix(maxPictures))
|
||||||
|
}
|
||||||
|
var loadingTask: Task<Void, Never>? = nil
|
||||||
|
|
||||||
|
init(state: DamusState, pubkeys: [Pubkey], maxPictures: Int) {
|
||||||
|
self.state = state
|
||||||
|
self.pubkeys = pubkeys
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,26 +8,26 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CondensedProfilePicturesView: View {
|
struct CondensedProfilePicturesView: View {
|
||||||
let state: DamusState
|
let model: CondensedProfilePicturesViewModel
|
||||||
let pubkeys: [Pubkey]
|
|
||||||
let maxPictures: Int
|
|
||||||
|
|
||||||
init(state: DamusState, pubkeys: [Pubkey], maxPictures: Int) {
|
init(state: DamusState, pubkeys: [Pubkey], maxPictures: Int) {
|
||||||
self.state = state
|
self.model = CondensedProfilePicturesViewModel(state: state, pubkeys: pubkeys, maxPictures: maxPictures)
|
||||||
self.pubkeys = pubkeys
|
|
||||||
self.maxPictures = min(maxPictures, pubkeys.count)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some 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..<maxPictures).reversed(), id: \.self) { index in
|
ForEach((0..<model.maxPictures).reversed(), id: \.self) { index in
|
||||||
ProfilePicView(pubkey: pubkeys[index], size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: 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)
|
||||||
.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((maxPictures - 1) * 20))
|
.padding(.trailing, CGFloat((model.maxPictures - 1) * 20))
|
||||||
|
.onAppear {
|
||||||
|
self.model.load()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import Foundation
|
|||||||
/// The data model for the SearchHome view, typically something global-like
|
/// The data model for the SearchHome view, typically something global-like
|
||||||
class SearchHomeModel: ObservableObject {
|
class SearchHomeModel: ObservableObject {
|
||||||
var events: EventHolder
|
var events: EventHolder
|
||||||
|
var followPackEvents: EventHolder
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
var seen_pubkey: Set<Pubkey> = Set()
|
var seen_pubkey: Set<Pubkey> = Set()
|
||||||
|
var follow_pack_seen_pubkey: Set<Pubkey> = Set()
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let base_subid = UUID().description
|
let base_subid = UUID().description
|
||||||
let follow_pack_subid = UUID().description
|
let follow_pack_subid = UUID().description
|
||||||
@@ -25,6 +27,9 @@ class SearchHomeModel: ObservableObject {
|
|||||||
self.events = EventHolder(on_queue: { ev in
|
self.events = EventHolder(on_queue: { ev in
|
||||||
preload_events(state: damus_state, events: [ev])
|
preload_events(state: damus_state, events: [ev])
|
||||||
})
|
})
|
||||||
|
self.followPackEvents = EventHolder(on_queue: { ev in
|
||||||
|
preload_events(state: damus_state, events: [ev])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_base_filter() -> NostrFilter {
|
func get_base_filter() -> NostrFilter {
|
||||||
@@ -40,6 +45,12 @@ class SearchHomeModel: ObservableObject {
|
|||||||
self.objectWillChange.send()
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func reload() async {
|
||||||
|
self.events.reset()
|
||||||
|
await self.load()
|
||||||
|
}
|
||||||
|
|
||||||
func load() async {
|
func load() async {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.loading = true
|
self.loading = true
|
||||||
@@ -51,16 +62,23 @@ 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: [get_base_filter(), follow_list_filter], to: to_relays) {
|
for await noteLender in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [follow_list_filter], to: to_relays) {
|
||||||
|
await noteLender.justUseACopy({ await self.handleFollowPackEvent($0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
for await noteLender in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(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
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
|
||||||
load_profiles(context: "universe", load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@@ -76,6 +94,20 @@ class SearchHomeModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func handleFollowPackEvent(_ ev: NostrEvent) {
|
||||||
|
if ev.known_kind == .follow_list && should_show_event(state: damus_state, ev: ev) && !ev.is_reply() {
|
||||||
|
if !damus_state.settings.multiple_events_per_pubkey && follow_pack_seen_pubkey.contains(ev.pubkey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
follow_pack_seen_pubkey.insert(ev.pubkey)
|
||||||
|
|
||||||
|
if self.followPackEvents.insert(ev) {
|
||||||
|
self.objectWillChange.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func find_profiles_to_fetch<Y>(profiles: Profiles, load: PubkeysToLoad, cache: EventCache, txn: NdbTxn<Y>) -> [Pubkey] {
|
func find_profiles_to_fetch<Y>(profiles: Profiles, load: PubkeysToLoad, cache: EventCache, txn: NdbTxn<Y>) -> [Pubkey] {
|
||||||
@@ -113,28 +145,23 @@ enum PubkeysToLoad {
|
|||||||
case from_keys([Pubkey])
|
case from_keys([Pubkey])
|
||||||
}
|
}
|
||||||
|
|
||||||
func load_profiles<Y>(context: String, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) {
|
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)
|
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn)
|
||||||
|
|
||||||
guard !authors.isEmpty else {
|
guard !authors.isEmpty else {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
Task {
|
return Task {
|
||||||
print("load_profiles[\(context)]: requesting \(authors.count) profiles from relay pool")
|
print("load_profiles[\(context)]: requesting \(authors.count) profiles from relay pool")
|
||||||
let filter = NostrFilter(kinds: [.metadata], authors: authors)
|
let filter = NostrFilter(kinds: [.metadata], authors: authors)
|
||||||
|
|
||||||
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
for await noteLender in damus_state.nostrNetwork.reader.streamNotesUntilEndOfStoredEvents(filters: [filter]) {
|
||||||
let now = UInt64(Date.now.timeIntervalSince1970)
|
let now = UInt64(Date.now.timeIntervalSince1970)
|
||||||
switch item {
|
try noteLender.borrow { event in
|
||||||
case .event(let lender):
|
|
||||||
lender.justUseACopy({ event in
|
|
||||||
if event.known_kind == .metadata {
|
if event.known_kind == .metadata {
|
||||||
damus_state.ndb.write_profile_last_fetched(pubkey: event.pubkey, fetched_at: now)
|
damus_state.ndb.write_profile_last_fetched(pubkey: event.pubkey, fetched_at: now)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
case .eose:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ struct SearchHomeView: View {
|
|||||||
loading: $model.loading,
|
loading: $model.loading,
|
||||||
damus: damus_state,
|
damus: damus_state,
|
||||||
show_friend_icon: true,
|
show_friend_icon: true,
|
||||||
filter:content_filter(FilterState.posts),
|
filter: content_filter(FilterState.posts),
|
||||||
content: {
|
content: {
|
||||||
AnyView(VStack(alignment: .leading) {
|
AnyView(VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
@@ -66,7 +66,7 @@ struct SearchHomeView: View {
|
|||||||
.padding(.top)
|
.padding(.top)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
FollowPackTimelineView<AnyView>(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true,filter:content_filter(FilterState.follow_list)
|
FollowPackTimelineView<AnyView>(events: model.followPackEvents, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: content_filter(FilterState.follow_list)
|
||||||
).padding(.bottom)
|
).padding(.bottom)
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
@@ -83,20 +83,10 @@ struct SearchHomeView: View {
|
|||||||
}.padding(.bottom, 50))
|
}.padding(.bottom, 50))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refreshable {
|
|
||||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
|
||||||
loadingTask?.cancel()
|
|
||||||
loadingTask = Task { await model.load() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var SearchContent: some View {
|
var SearchContent: some View {
|
||||||
SearchResultsView(damus_state: damus_state, search: $search)
|
SearchResultsView(damus_state: damus_state, search: $search)
|
||||||
.refreshable {
|
|
||||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
|
||||||
loadingTask?.cancel()
|
|
||||||
loadingTask = Task { await model.load() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var MainContent: some View {
|
var MainContent: some View {
|
||||||
@@ -136,6 +126,12 @@ struct SearchHomeView: View {
|
|||||||
.onDisappear {
|
.onDisappear {
|
||||||
loadingTask?.cancel()
|
loadingTask?.cancel()
|
||||||
}
|
}
|
||||||
|
.refreshable {
|
||||||
|
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||||
|
loadingTask?.cancel()
|
||||||
|
loadingTask = Task { await model.reload() }
|
||||||
|
try? await loadingTask?.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,4 +95,10 @@ class EventHolder: ObservableObject, ScrollQueue {
|
|||||||
|
|
||||||
self.incoming = []
|
self.incoming = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func reset() {
|
||||||
|
self.incoming = []
|
||||||
|
self.events = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user