Add plus-minus feature to location notes (± 1 grid) (#821)

Expands location notes coverage to include 8 neighboring geohash cells,
creating a 3×3 grid around the user's current building-level location.

Changes:
- Add Geohash.neighbors() method to calculate 8 surrounding cells
- Update LocationNotesManager to subscribe to center + neighbors (9 cells total)
- Add NostrFilter.geohashNotes([String]) overload for multi-cell subscriptions
- Display '± 1' indicator in LocationNotesView header

Coverage:
- Single cell: ~38m × 19m
- 3×3 grid: ~114m × 57m total area
- Better discovery of nearby activity

Behavior:
- Subscribe: Fetches notes from all 9 cells (single efficient subscription)
- Post: Still uses center geohash only (correct privacy-preserving behavior)
- UI: Shows 'geohash ± 1' to indicate expanded coverage

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-10-18 12:44:06 +02:00
committed by GitHub
co-authored by jack
parent 88bafb41cc
commit 7040d9ecb0
4 changed files with 76 additions and 5 deletions
+12 -4
View File
@@ -163,14 +163,22 @@ final class LocationNotesManager: ObservableObject {
subscriptionID = subID
initialLoadComplete = false
// For persistent notes, allow relays to return recent history without an aggressive time cutoff
let filter = NostrFilter.geohashNotes(geohash, since: nil, limit: 200)
// Subscribe to center + 8 neighbors (± 1 grid)
let neighbors = Geohash.neighbors(of: geohash)
let allGeohashes = [geohash] + neighbors
let filter = NostrFilter.geohashNotes(allGeohashes, since: nil, limit: 200)
// Build a set of valid geohashes for tag matching (includes all 9 cells)
let validGeohashes = Set(allGeohashes.map { $0.lowercased() })
dependencies.subscribe(filter, subID, relays, { [weak self] event in
guard let self = self else { return }
guard event.kind == NostrProtocol.EventKind.textNote.rawValue else { return }
// Ensure matching tag
guard event.tags.contains(where: { $0.count >= 2 && $0[0].lowercased() == "g" && $0[1].lowercased() == self.geohash }) else { return }
// Ensure matching tag - accept any of our 9 geohashes
guard event.tags.contains(where: { tag in
tag.count >= 2 && tag[0].lowercased() == "g" && validGeohashes.contains(tag[1].lowercased())
}) else { return }
guard !self.noteIDs.contains(event.id) else { return }
self.noteIDs.insert(event.id)
let nick = event.tags.first(where: { $0.first?.lowercased() == "n" && $0.count >= 2 })?.dropFirst().first