Geohash notifications (#288)

* mention notifications

* notifications

* warning
This commit is contained in:
callebtc
2025-08-23 01:35:46 +02:00
committed by GitHub
parent 65afdfb92e
commit f13f3063c9
6 changed files with 646 additions and 27 deletions
@@ -29,7 +29,8 @@ class NostrGeohashService(
private val privateChatManager: PrivateChatManager,
private val meshDelegateHandler: MeshDelegateHandler,
private val coroutineScope: CoroutineScope,
private val dataManager: com.bitchat.android.ui.DataManager
private val dataManager: com.bitchat.android.ui.DataManager,
private val notificationManager: com.bitchat.android.ui.NotificationManager
) {
companion object {
@@ -51,7 +52,8 @@ class NostrGeohashService(
privateChatManager: PrivateChatManager,
meshDelegateHandler: MeshDelegateHandler,
coroutineScope: CoroutineScope,
dataManager: com.bitchat.android.ui.DataManager
dataManager: com.bitchat.android.ui.DataManager,
notificationManager: com.bitchat.android.ui.NotificationManager
): NostrGeohashService {
return synchronized(this) {
INSTANCE ?: NostrGeohashService(
@@ -61,7 +63,8 @@ class NostrGeohashService(
privateChatManager,
meshDelegateHandler,
coroutineScope,
dataManager
dataManager,
notificationManager
).also { INSTANCE = it }
}
}
@@ -751,6 +754,10 @@ class NostrGeohashService(
Log.d(TAG, "📡 Switched to mesh channel")
// Immediate UI state updates
currentGeohash = null
// Update notification manager with current geohash
notificationManager.setCurrentGeohash(null)
// Clear mesh mention notifications since user is now viewing mesh chat
notificationManager.clearMeshMentionNotifications()
// Note: Don't clear geoNicknames - keep cached for when we return to location channels
stopGeoParticipantsTimer()
state.setGeohashPeople(emptyList())
@@ -760,6 +767,10 @@ class NostrGeohashService(
is com.bitchat.android.geohash.ChannelID.Location -> {
Log.d(TAG, "📍 Switching to geohash channel: ${channel.channel.geohash}")
currentGeohash = channel.channel.geohash
// Update notification manager with current geohash
notificationManager.setCurrentGeohash(channel.channel.geohash)
// Clear notifications for this geohash since user is now viewing it
notificationManager.clearNotificationsForGeohash(channel.channel.geohash)
// Note: Don't clear geoNicknames - they contain cached nicknames for all geohashes
// Load stored messages for this geohash immediately
@@ -1023,6 +1034,9 @@ class NostrGeohashService(
messageManager.addMessage(message)
}
// NOTIFICATION LOGIC: Check for mentions and first messages
checkAndTriggerGeohashNotifications(geohash, senderName, content, message)
Log.d(TAG, "📥 Unified geohash event processed - geohash: $geohash, sender: $senderName, content: ${content.take(50)}")
} catch (e: Exception) {
@@ -1030,6 +1044,104 @@ class NostrGeohashService(
}
}
/**
* Check and trigger geohash notifications for mentions and first messages
*/
private fun checkAndTriggerGeohashNotifications(
geohash: String,
senderName: String,
content: String,
message: BitchatMessage
) {
try {
// Get user's current nickname
val currentNickname = state.getNicknameValue()
if (currentNickname.isNullOrEmpty()) {
return
}
// Check if this message mentions the current user
val isMention = checkForMention(content, currentNickname)
// Check if this is the first message in a subscribed geohash chat
val isFirstMessage = checkIfFirstMessage(geohash, message)
// Only trigger notifications if we have a mention or first message
if (isMention || isFirstMessage) {
Log.d(TAG, "🔔 Triggering geohash notification - geohash: $geohash, mention: $isMention, first: $isFirstMessage")
notificationManager.showGeohashNotification(
geohash = geohash,
senderNickname = senderName,
messageContent = content,
isMention = isMention,
isFirstMessage = isFirstMessage
)
}
} catch (e: Exception) {
Log.e(TAG, "Error checking geohash notifications: ${e.message}")
}
}
/**
* Check if the content mentions the current user with @nickname#hash format
*/
private fun checkForMention(content: String, currentNickname: String): Boolean {
// iOS-style mention pattern: @nickname#1234 or @nickname
val mentionPattern = "@([\\p{L}0-9_]+(?:#[a-fA-F0-9]{4})?)".toRegex()
return mentionPattern.findAll(content).any { match ->
val mentionWithoutAt = match.groupValues[1]
// Split the mention to get base nickname (without #hash suffix)
val baseName = if (mentionWithoutAt.contains("#")) {
mentionWithoutAt.substringBeforeLast("#")
} else {
mentionWithoutAt
}
// Check if the base name matches current user's nickname
baseName.equals(currentNickname, ignoreCase = true)
}
}
/**
* Check if this is the first message in a subscribed geohash chat
*/
private fun checkIfFirstMessage(geohash: String, message: BitchatMessage): Boolean {
// Get the message history for this geohash
val messageHistory = geohashMessageHistory[geohash] ?: return true
// Filter out our own messages (local echoes and messages from our identity)
val otherUserMessages = messageHistory.filter { msg ->
// Check if this is our own message by comparing sender ID with our geohash identity
try {
val myGeoIdentity = NostrIdentityBridge.deriveIdentity(
forGeohash = geohash,
context = application
)
val myIdentityId = "nostr:${myGeoIdentity.publicKeyHex.take(8)}"
msg.senderPeerID != myIdentityId && msg.senderPeerID != message.senderPeerID
} catch (e: Exception) {
// If we can't determine identity, assume it's not our message
msg.senderPeerID != message.senderPeerID
}
}
// This is a first message if there are no other user messages and this isn't from us
val isFromUs = try {
val myGeoIdentity = NostrIdentityBridge.deriveIdentity(
forGeohash = geohash,
context = application
)
val myIdentityId = "nostr:${myGeoIdentity.publicKeyHex.take(8)}"
message.senderPeerID == myIdentityId
} catch (e: Exception) {
false
}
return otherUserMessages.isEmpty() && !isFromUs
}
/**
* Handle geohash DM event (private messages in geohash context) - iOS compatible
*/