From 6cf6e3c420216b1ce051fc196b02d50a7c82af08 Mon Sep 17 00:00:00 2001 From: jack Date: Sat, 13 Sep 2025 12:09:41 +0200 Subject: [PATCH] Notes: subscribe counter to building + parent block (merge results); hide notes icon unless location is authorized; replace 10s timer with live location updates while sheet open --- bitchat/Services/LocationNotesCounter.swift | 53 +++++++++++++-------- bitchat/Views/ContentView.swift | 17 +++---- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/bitchat/Services/LocationNotesCounter.swift b/bitchat/Services/LocationNotesCounter.swift index 5e3f1285..d4417028 100644 --- a/bitchat/Services/LocationNotesCounter.swift +++ b/bitchat/Services/LocationNotesCounter.swift @@ -9,7 +9,8 @@ final class LocationNotesCounter: ObservableObject { @Published private(set) var count: Int? = 0 @Published private(set) var initialLoadComplete: Bool = false - private var subscriptionID: String? = nil + // Support multiple subscriptions (e.g., building + parent block fallback) + private var subscriptionIDs: Set = [] private var noteIDs = Set() private init() {} @@ -23,28 +24,40 @@ final class LocationNotesCounter: ObservableObject { noteIDs.removeAll() initialLoadComplete = false - let subID = "locnotes-count-\(norm)-\(UUID().uuidString.prefix(6))" - subscriptionID = subID - let filter = NostrFilter.geohashNotes(norm, since: nil, limit: 500) - let relays = GeoRelayDirectory.shared.closestRelays(toGeohash: norm, count: TransportConfig.nostrGeoRelayCount) - NostrRelayManager.shared.subscribe(filter: filter, id: subID, relayUrls: relays, handler: { [weak self] event in - guard let self = self else { return } - guard event.kind == NostrProtocol.EventKind.textNote.rawValue else { return } - guard event.tags.contains(where: { $0.count >= 2 && $0[0].lowercased() == "g" && $0[1].lowercased() == norm }) else { return } - if !self.noteIDs.contains(event.id) { - self.noteIDs.insert(event.id) - self.count = self.noteIDs.count - } - }, onEOSE: { [weak self] in - self?.initialLoadComplete = true - }) + // Subscribe to both building and parent block to capture legacy notes + var targets: [String] = [norm] + if norm.count >= 8 { + let parent7 = String(norm.prefix(7)) + if parent7 != norm { targets.append(parent7) } + } + var pendingEOSE = Set() + for target in targets { + let subID = "locnotes-count-\(target)-\(UUID().uuidString.prefix(6))" + subscriptionIDs.insert(subID) + pendingEOSE.insert(subID) + let filter = NostrFilter.geohashNotes(target, since: nil, limit: 500) + let relays = GeoRelayDirectory.shared.closestRelays(toGeohash: target, count: TransportConfig.nostrGeoRelayCount) + NostrRelayManager.shared.subscribe(filter: filter, id: subID, relayUrls: relays, handler: { [weak self] event in + guard let self = self else { return } + guard event.kind == NostrProtocol.EventKind.textNote.rawValue else { return } + // Ensure matching g-tag for either building or parent block + let matches = event.tags.contains(where: { $0.count >= 2 && $0[0].lowercased() == "g" && targets.contains($0[1].lowercased()) }) + guard matches else { return } + if !self.noteIDs.contains(event.id) { + self.noteIDs.insert(event.id) + self.count = self.noteIDs.count + } + }, onEOSE: { [weak self] in + guard let self = self else { return } + pendingEOSE.remove(subID) + if pendingEOSE.isEmpty { self.initialLoadComplete = true } + }) + } } func cancel() { - if let sub = subscriptionID { - NostrRelayManager.shared.unsubscribe(id: sub) - } - subscriptionID = nil + for sub in subscriptionIDs { NostrRelayManager.shared.unsubscribe(id: sub) } + subscriptionIDs.removeAll() geohash = nil count = 0 noteIDs.removeAll() diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 2fc55f73..faf60f33 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -52,7 +52,7 @@ struct ContentView: View { @State private var expandedMessageIDs: Set = [] @State private var showLocationNotes = false @State private var notesGeohash: String? = nil - @State private var notesRefreshTimer: Timer? = nil + // Timer-based refresh removed; use LocationChannelManager live updates instead // Window sizes for rendering (infinite scroll up) @State private var windowCountPublic: Int = 300 @State private var windowCountPrivate: [String: Int] = [:] @@ -1174,8 +1174,8 @@ struct ContentView: View { } .buttonStyle(.plain) - // Notes icon (mesh only), to the right of #mesh - if case .mesh = locationManager.selectedChannel { + // Notes icon (mesh only and when location is authorized), to the right of #mesh + if case .mesh = locationManager.selectedChannel, locationManager.permissionState == .authorized { Button(action: { // Kick a one-shot refresh and show the sheet immediately. LocationChannelManager.shared.enableLocationChannels() @@ -1277,17 +1277,12 @@ struct ContentView: View { } } .onAppear { - // Ensure we are authorized and start periodic refresh while the sheet is open + // Ensure we are authorized and start live location updates (distance-filtered) LocationChannelManager.shared.enableLocationChannels() - LocationChannelManager.shared.refreshChannels() - notesRefreshTimer?.invalidate() - notesRefreshTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { _ in - LocationChannelManager.shared.refreshChannels() - } + LocationChannelManager.shared.beginLiveRefresh() } .onDisappear { - notesRefreshTimer?.invalidate() - notesRefreshTimer = nil + LocationChannelManager.shared.endLiveRefresh() } } .onAppear { updateNotesCounterSubscription() }