This commit is contained in:
callebtc
2025-08-28 17:31:52 +02:00
parent f665935166
commit 53ccefb880
3 changed files with 106 additions and 2 deletions
@@ -1302,11 +1302,29 @@ class NostrGeohashService(
Log.d(TAG, "🔔 Triggering geohash notification - geohash: $geohash, mention: $isMention, first: $isFirstMessage")
withContext(Dispatchers.Main) {
// Sanitize mention for notifications: hide '#hash' from @username#hash if not needed
val contentForNotification = if (isMention) {
var sanitized = content
try {
val myGeoIdentity = NostrIdentityBridge.deriveIdentity(
forGeohash = geohash,
context = application
)
val myDisplay = displayNameForNostrPubkeyUI(myGeoIdentity.publicKeyHex)
val needsDisambiguation = myDisplay.contains("#")
if (!needsDisambiguation) {
val pattern = ("@" + java.util.regex.Pattern.quote(currentNickname) + "#[a-fA-F0-9]{4}\\b").toRegex()
sanitized = sanitized.replace(pattern, "@" + currentNickname)
}
} catch (_: Exception) { }
sanitized
} else content
val locationName = getLocationNameForGeohash(geohash)
notificationManager.showGeohashNotification(
geohash = geohash,
senderNickname = senderName,
messageContent = content,
messageContent = contentForNotification,
isMention = isMention,
isFirstMessage = isFirstMessage,
locationName = locationName
@@ -5,6 +5,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.sp
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.mesh.BluetoothMeshService
@@ -257,6 +258,31 @@ private fun appendIOSFormattedContent(
for (match in mentionMatches) {
allMatches.add(match.range to "mention")
}
// Add standalone geohash matches (e.g., "#9q") that are not part of another word
// We use MessageSpecialParser to find exact ranges; then merge with existing ranges avoiding overlaps
val geoMatches = MessageSpecialParser.findStandaloneGeohashes(content)
for (gm in geoMatches) {
val range = gm.start until gm.endExclusive
if (!overlapsMention(range)) {
allMatches.add(range to "geohash")
}
}
// Remove generic hashtag matches that overlap with detected geohash ranges to avoid duplicate rendering
fun rangesOverlap(a: IntRange, b: IntRange): Boolean {
return a.first < b.last && a.last > b.first
}
val geoRanges = allMatches.filter { it.second == "geohash" }.map { it.first }
if (geoRanges.isNotEmpty()) {
val iterator = allMatches.listIterator()
while (iterator.hasNext()) {
val (range, type) = iterator.next()
if (type == "hashtag" && geoRanges.any { rangesOverlap(range, it) }) {
iterator.remove()
}
}
}
allMatches.sortBy { it.first.first }
@@ -327,7 +353,7 @@ private fun appendIOSFormattedContent(
}
}
"hashtag" -> {
// iOS-style: render hashtags like normal content (no special styling)
// Render general hashtags like normal content
builder.pushStyle(SpanStyle(
color = baseColor,
fontSize = BASE_FONT_SIZE.sp,
@@ -342,6 +368,37 @@ private fun appendIOSFormattedContent(
}
builder.pop()
}
else -> {
if (type == "geohash") {
// Style geohash in blue, underlined, and add click annotation
builder.pushStyle(SpanStyle(
color = Color(0xFF007AFF),
fontSize = BASE_FONT_SIZE.sp,
fontWeight = if (isSelf) FontWeight.Bold else FontWeight.SemiBold,
textDecoration = TextDecoration.Underline
))
val start = builder.length
builder.append(matchText)
val end = builder.length
val geohash = matchText.removePrefix("#").lowercase()
builder.addStringAnnotation(
tag = "geohash_click",
annotation = geohash,
start = start,
end = end
)
builder.pop()
} else {
// Fallback: treat as normal text
builder.pushStyle(SpanStyle(
color = baseColor,
fontSize = BASE_FONT_SIZE.sp,
fontWeight = if (isSelf) FontWeight.Bold else FontWeight.Normal
))
builder.append(matchText)
builder.pop()
}
}
}
lastEnd = range.last + 1
@@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import com.bitchat.android.model.BitchatMessage
@@ -199,6 +200,7 @@ private fun MessageTextWithClickableNicknames(
if (!isSelf && (onNicknameClick != null || onMessageLongPress != null)) {
val haptic = LocalHapticFeedback.current
val context = LocalContext.current
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
Text(
text = annotatedText,
@@ -216,6 +218,33 @@ private fun MessageTextWithClickableNicknames(
val nickname = nicknameAnnotations.first().item
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
onNicknameClick?.invoke(nickname)
return@detectTapGestures
}
// Handle geohash click to teleport
val geohashAnnotations = annotatedText.getStringAnnotations(
tag = "geohash_click",
start = offset,
end = offset
)
if (geohashAnnotations.isNotEmpty()) {
val geohash = geohashAnnotations.first().item
try {
val locationManager = com.bitchat.android.geohash.LocationChannelManager.getInstance(
context
)
// Select appropriate precision based on length
val level = when (geohash.length) {
in 0..2 -> com.bitchat.android.geohash.GeohashChannelLevel.REGION
in 3..4 -> com.bitchat.android.geohash.GeohashChannelLevel.PROVINCE
5 -> com.bitchat.android.geohash.GeohashChannelLevel.CITY
6 -> com.bitchat.android.geohash.GeohashChannelLevel.NEIGHBORHOOD
else -> com.bitchat.android.geohash.GeohashChannelLevel.BLOCK
}
val channel = com.bitchat.android.geohash.GeohashChannel(level, geohash.lowercase())
locationManager.setTeleported(true)
locationManager.select(com.bitchat.android.geohash.ChannelID.Location(channel))
} catch (_: Exception) { }
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
}
},
onLongPress = {