mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 11:45:19 +00:00
works
This commit is contained in:
@@ -1302,11 +1302,29 @@ class NostrGeohashService(
|
|||||||
Log.d(TAG, "🔔 Triggering geohash notification - geohash: $geohash, mention: $isMention, first: $isFirstMessage")
|
Log.d(TAG, "🔔 Triggering geohash notification - geohash: $geohash, mention: $isMention, first: $isFirstMessage")
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
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)
|
val locationName = getLocationNameForGeohash(geohash)
|
||||||
notificationManager.showGeohashNotification(
|
notificationManager.showGeohashNotification(
|
||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
senderNickname = senderName,
|
senderNickname = senderName,
|
||||||
messageContent = content,
|
messageContent = contentForNotification,
|
||||||
isMention = isMention,
|
isMention = isMention,
|
||||||
isFirstMessage = isFirstMessage,
|
isFirstMessage = isFirstMessage,
|
||||||
locationName = locationName
|
locationName = locationName
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.ui.text.AnnotatedString
|
|||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
import com.bitchat.android.mesh.BluetoothMeshService
|
import com.bitchat.android.mesh.BluetoothMeshService
|
||||||
@@ -258,6 +259,31 @@ private fun appendIOSFormattedContent(
|
|||||||
allMatches.add(match.range to "mention")
|
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 }
|
allMatches.sortBy { it.first.first }
|
||||||
|
|
||||||
var lastEnd = 0
|
var lastEnd = 0
|
||||||
@@ -327,7 +353,7 @@ private fun appendIOSFormattedContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"hashtag" -> {
|
"hashtag" -> {
|
||||||
// iOS-style: render hashtags like normal content (no special styling)
|
// Render general hashtags like normal content
|
||||||
builder.pushStyle(SpanStyle(
|
builder.pushStyle(SpanStyle(
|
||||||
color = baseColor,
|
color = baseColor,
|
||||||
fontSize = BASE_FONT_SIZE.sp,
|
fontSize = BASE_FONT_SIZE.sp,
|
||||||
@@ -342,6 +368,37 @@ private fun appendIOSFormattedContent(
|
|||||||
}
|
}
|
||||||
builder.pop()
|
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
|
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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
@@ -199,6 +200,7 @@ private fun MessageTextWithClickableNicknames(
|
|||||||
|
|
||||||
if (!isSelf && (onNicknameClick != null || onMessageLongPress != null)) {
|
if (!isSelf && (onNicknameClick != null || onMessageLongPress != null)) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
val context = LocalContext.current
|
||||||
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
|
var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }
|
||||||
Text(
|
Text(
|
||||||
text = annotatedText,
|
text = annotatedText,
|
||||||
@@ -216,6 +218,33 @@ private fun MessageTextWithClickableNicknames(
|
|||||||
val nickname = nicknameAnnotations.first().item
|
val nickname = nicknameAnnotations.first().item
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||||
onNicknameClick?.invoke(nickname)
|
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 = {
|
onLongPress = {
|
||||||
|
|||||||
Reference in New Issue
Block a user