mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 05:05:20 +00:00
plus minus location notes
This commit is contained in:
@@ -115,4 +115,36 @@ object Geohash {
|
||||
lonMax = maxOf(lonInterval.first, lonInterval.second)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 8 neighboring geohash cells at the same precision as the input.
|
||||
* Neighbors include N, NE, E, SE, S, SW, W, NW, even when crossing parent cell boundaries.
|
||||
*/
|
||||
fun neighborsSamePrecision(geohash: String): Set<String> {
|
||||
if (geohash.isEmpty()) return emptySet()
|
||||
val p = geohash.length
|
||||
val b = decodeToBounds(geohash)
|
||||
val dLat = b.latMax - b.latMin
|
||||
val dLon = b.lonMax - b.lonMin
|
||||
|
||||
fun wrapLon(lon: Double): Double {
|
||||
var x = lon
|
||||
while (x > 180.0) x -= 360.0
|
||||
while (x < -180.0) x += 360.0
|
||||
return x
|
||||
}
|
||||
|
||||
val neighbors = mutableSetOf<String>()
|
||||
for (dy in -1..1) {
|
||||
for (dx in -1..1) {
|
||||
if (dx == 0 && dy == 0) continue // skip center
|
||||
val centerLat = (b.latMin + b.latMax) / 2 + dy * dLat
|
||||
val rawLonCenter = (b.lonMin + b.lonMax) / 2 + dx * dLon
|
||||
val centerLon = wrapLon(rawLonCenter)
|
||||
val enc = encode(centerLat.coerceIn(-90.0, 90.0), centerLon, p)
|
||||
if (enc.isNotEmpty() && enc != geohash) neighbors.add(enc)
|
||||
}
|
||||
}
|
||||
return neighbors
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,9 @@ class LocationNotesManager private constructor() {
|
||||
val errorMessage: LiveData<String?> = _errorMessage
|
||||
|
||||
// Private state
|
||||
private var subscriptionID: String? = null
|
||||
private var subscriptionIDs: MutableMap<String, String> = mutableMapOf()
|
||||
private val noteIDs = mutableSetOf<String>() // For deduplication
|
||||
private var subscribedGeohashes: Set<String> = emptySet()
|
||||
|
||||
// Dependencies (injected via setters for flexibility)
|
||||
private var relayLookup: (() -> NostrRelayManager)? = null
|
||||
@@ -143,8 +144,14 @@ class LocationNotesManager private constructor() {
|
||||
noteIDs.clear()
|
||||
_geohash.value = normalized
|
||||
|
||||
// Start new subscription
|
||||
subscribe()
|
||||
// Compute target geohashes: center + neighbors (±1)
|
||||
val neighbors = try {
|
||||
com.bitchat.android.geohash.Geohash.neighborsSamePrecision(normalized)
|
||||
} catch (_: Exception) { emptySet() }
|
||||
subscribedGeohashes = (neighbors + normalized).toSet()
|
||||
|
||||
// Start new subscriptions for all cells
|
||||
subscribeAll()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,12 +175,17 @@ class LocationNotesManager private constructor() {
|
||||
|
||||
Log.d(TAG, "Refreshing notes for geohash: $currentGeohash")
|
||||
|
||||
// Cancel and restart subscription
|
||||
// Cancel and restart subscriptions for current ±1 set
|
||||
cancel()
|
||||
_notes.value = emptyList()
|
||||
noteIDs.clear()
|
||||
_initialLoadComplete.value = false
|
||||
subscribe()
|
||||
// Rebuild subscribedGeohashes and resubscribe
|
||||
val neighbors = try {
|
||||
com.bitchat.android.geohash.Geohash.neighborsSamePrecision(currentGeohash)
|
||||
} catch (_: Exception) { emptySet() }
|
||||
subscribedGeohashes = (neighbors + currentGeohash).toSet()
|
||||
subscribeAll()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,7 +287,7 @@ class LocationNotesManager private constructor() {
|
||||
/**
|
||||
* Subscribe to location notes for current geohash
|
||||
*/
|
||||
private fun subscribe() {
|
||||
private fun subscribeAll() {
|
||||
val currentGeohash = _geohash.value
|
||||
if (currentGeohash == null) {
|
||||
Log.w(TAG, "Cannot subscribe - no geohash set")
|
||||
@@ -292,18 +304,21 @@ class LocationNotesManager private constructor() {
|
||||
|
||||
_state.value = State.LOADING
|
||||
|
||||
val filter = NostrFilter.geohashNotes(
|
||||
geohash = currentGeohash,
|
||||
since = null, // Get all notes (relays will limit)
|
||||
limit = 200
|
||||
)
|
||||
|
||||
val subId = "location-notes-$currentGeohash"
|
||||
|
||||
Log.d(TAG, "📡 Subscribing to location notes: $subId")
|
||||
|
||||
subscriptionID = subscribe(filter, subId) { event ->
|
||||
handleEvent(event)
|
||||
// Subscribe for each geohash in the ±1 set
|
||||
subscribedGeohashes.forEach { gh ->
|
||||
val filter = NostrFilter.geohashNotes(
|
||||
geohash = gh,
|
||||
since = null,
|
||||
limit = 200
|
||||
)
|
||||
val subId = "location-notes-$gh"
|
||||
Log.d(TAG, "📡 Subscribing to location notes: $subId")
|
||||
try {
|
||||
val id = subscribe(filter, subId) { event -> handleEvent(event) }
|
||||
subscriptionIDs[gh] = id
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to subscribe for $gh: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// Mark initial load complete after brief delay to allow relay responses
|
||||
@@ -336,8 +351,8 @@ class LocationNotesManager private constructor() {
|
||||
|
||||
// Check if matches current geohash
|
||||
val eventGeohash = geohashTag[1]
|
||||
if (eventGeohash != _geohash.value) {
|
||||
Log.v(TAG, "Ignoring event for different geohash: $eventGeohash")
|
||||
if (!subscribedGeohashes.contains(eventGeohash)) {
|
||||
Log.v(TAG, "Ignoring event for non-subscribed geohash: $eventGeohash")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -406,12 +421,16 @@ class LocationNotesManager private constructor() {
|
||||
* Cancel subscription and clear state
|
||||
*/
|
||||
fun cancel() {
|
||||
subscriptionID?.let { subId ->
|
||||
Log.d(TAG, "🚫 Canceling subscription: $subId")
|
||||
unsubscribeFunc?.invoke(subId)
|
||||
subscriptionID = null
|
||||
if (subscriptionIDs.isNotEmpty()) {
|
||||
subscriptionIDs.values.forEach { subId ->
|
||||
try {
|
||||
Log.d(TAG, "🚫 Canceling subscription: $subId")
|
||||
unsubscribeFunc?.invoke(subId)
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
subscriptionIDs.clear()
|
||||
}
|
||||
|
||||
subscribedGeohashes = emptySet()
|
||||
_state.value = State.IDLE
|
||||
}
|
||||
|
||||
|
||||
@@ -215,9 +215,9 @@ private fun LocationNotesHeader(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// iOS format: "#geohash • X notes"
|
||||
// "#geohash ± 1 • X notes" to indicate neighboring cells are included
|
||||
Text(
|
||||
text = "#$geohash • $count ${if (count == 1) "note" else "notes"}",
|
||||
text = "#$geohash ± 1 • $count ${if (count == 1) "note" else "notes"}",
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = 18.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
|
||||
Reference in New Issue
Block a user