From 817f86b4407ced8096e052bbdf2eb2927f607284 Mon Sep 17 00:00:00 2001 From: Aleks Pliekhov Date: Thu, 19 Mar 2026 18:38:07 -0400 Subject: [PATCH] added NIP-09 event deletion request for location note --- .../android/nostr/LocationNotesManager.kt | 86 +++- .../com/bitchat/android/nostr/NostrEvent.kt | 1 + .../bitchat/android/nostr/NostrProtocol.kt | 18 + .../bitchat/android/ui/LocationNotesSheet.kt | 443 ++++++++++++------ .../android/ui/LocationNotesSheetPresenter.kt | 86 ++-- app/src/main/res/values-ar/strings.xml | 2 + app/src/main/res/values-bn/strings.xml | 2 + app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values-es/strings.xml | 2 + app/src/main/res/values-fa/strings.xml | 2 + app/src/main/res/values-fil/strings.xml | 2 + app/src/main/res/values-fr/strings.xml | 2 + app/src/main/res/values-he/strings.xml | 2 + app/src/main/res/values-hi/strings.xml | 2 + app/src/main/res/values-id/strings.xml | 2 + app/src/main/res/values-it/strings.xml | 2 + app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values-ka/strings.xml | 2 + app/src/main/res/values-ko/strings.xml | 2 + app/src/main/res/values-mg/strings.xml | 2 + app/src/main/res/values-ms/strings.xml | 2 + app/src/main/res/values-ne/strings.xml | 2 + app/src/main/res/values-nl/strings.xml | 2 + app/src/main/res/values-pa-rPK/strings.xml | 2 + app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values-pt-rBR/strings.xml | 2 + app/src/main/res/values-pt/strings.xml | 2 + app/src/main/res/values-ru/strings.xml | 2 + app/src/main/res/values-sv/strings.xml | 2 + app/src/main/res/values-ta/strings.xml | 2 + app/src/main/res/values-th/strings.xml | 2 + app/src/main/res/values-tr/strings.xml | 2 + app/src/main/res/values-uk/strings.xml | 2 + app/src/main/res/values-ur/strings.xml | 2 + app/src/main/res/values-vi/strings.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values-zh/strings.xml | 2 + app/src/main/res/values/strings.xml | 8 +- 39 files changed, 533 insertions(+), 175 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/nostr/LocationNotesManager.kt b/app/src/main/java/com/bitchat/android/nostr/LocationNotesManager.kt index dc1a8e85..860bcadd 100644 --- a/app/src/main/java/com/bitchat/android/nostr/LocationNotesManager.kt +++ b/app/src/main/java/com/bitchat/android/nostr/LocationNotesManager.kt @@ -67,6 +67,9 @@ class LocationNotesManager private constructor() { // Published state (StateFlow for Android) private val _notes = MutableStateFlow>(emptyList()) val notes: StateFlow> = _notes.asStateFlow() + + private val _localPubkey = MutableStateFlow(null) + val localPubkey: StateFlow = _localPubkey.asStateFlow() private val _geohash = MutableStateFlow(null) val geohash: StateFlow = _geohash.asStateFlow() @@ -145,6 +148,14 @@ class LocationNotesManager private constructor() { noteIDs.clear() _geohash.value = normalized + // Derive and cache local pubkey for ownership checks + scope.launch { + runCatching { + val identity = withContext(Dispatchers.IO) { deriveIdentityFunc?.invoke(normalized) } + if (identity != null) _localPubkey.value = identity.publicKeyHex + } + } + // Compute target geohashes: center + neighbors (±1) val neighbors = try { com.bitchat.android.geohash.Geohash.neighborsSamePrecision(normalized) @@ -285,6 +296,54 @@ class LocationNotesManager private constructor() { } } + /** + * Delete a location note by sending a NIP-09 kind:5 deletion event to relays. + * Removes the note locally immediately for optimistic UI, then broadcasts the + * deletion event. Only notes authored by the local user can be deleted. + */ + fun deleteNote(noteId: String) { + val currentGeohash = _geohash.value ?: run { + Log.w(TAG, "Cannot delete note - no geohash set") + return + } + val deriveIdentity = deriveIdentityFunc ?: run { + Log.e(TAG, "Cannot delete note - deriveIdentity not initialized") + return + } + val relays = try { + com.bitchat.android.nostr.RelayDirectory.closestRelaysForGeohash(currentGeohash, 5) + } catch (e: Exception) { + Log.e(TAG, "Failed to lookup relays for geohash $currentGeohash: ${e.message}") + emptyList() + } + + scope.launch { + try { + val identity = withContext(Dispatchers.IO) { deriveIdentity(currentGeohash) } + + val deletionEvent = withContext(Dispatchers.IO) { + NostrProtocol.createDeletionEvent( + targetEventId = noteId, + senderIdentity = identity + ) + } + + // Optimistic local removal + _notes.value = _notes.value.filter { it.id != noteId } + noteIDs.remove(noteId) + + // Broadcast to geo relays + withContext(Dispatchers.IO) { + sendEventFunc?.invoke(deletionEvent, relays.ifEmpty { null }) + } + + Log.d(TAG, "✅ Note deleted: ${noteId.take(16)}...") + } catch (e: Exception) { + Log.e(TAG, "Failed to delete note: ${e.message}") + } + } + } + /** * Subscribe to location notes for current geohash */ @@ -326,9 +385,11 @@ class LocationNotesManager private constructor() { // Subscribe for each geohash in the ±1 set subscribedGeohashes.forEach { gh -> - val filter = NostrFilter.geohashNotes( - geohash = gh, - since = null, + // Include kind:5 (NIP-09 deletion) alongside kind:1 notes so remote + // deletions are reflected in real time for all subscribers + val filter = NostrFilter( + kinds = listOf(NostrKind.TEXT_NOTE, NostrKind.DELETION), + tagFilters = mapOf("g" to listOf(gh)), limit = 200 ) val subId = "location-notes-$gh" @@ -356,7 +417,24 @@ class LocationNotesManager private constructor() { * Handle incoming event from subscription */ private fun handleEvent(event: NostrEvent) { - // Validate event + // Handle NIP-09 deletion events: remove notes authored by the sender + if (event.kind == NostrKind.DELETION) { + val targetIds = event.tags + .filter { it.size >= 2 && it[0] == "e" } + .map { it[1] }.toSet() + if (targetIds.isNotEmpty()) { + val before = _notes.value + val removed = before.filter { it.id in targetIds && it.pubkey == event.pubkey } + if (removed.isNotEmpty()) { + _notes.value = before.filter { it.id !in targetIds || it.pubkey != event.pubkey } + removed.forEach { noteIDs.remove(it.id) } + Log.d(TAG, "🗑️ Removed ${removed.size} note(s) via kind:5 from ${event.pubkey.take(8)}") + } + } + return + } + + // Validate event — only TEXT_NOTE beyond this point if (event.kind != NostrKind.TEXT_NOTE) { Log.v(TAG, "Ignoring non-text-note event: kind=${event.kind}") return diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt b/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt index 92752b17..399930b7 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrEvent.kt @@ -210,6 +210,7 @@ data class NostrEvent( object NostrKind { const val METADATA = 0 const val TEXT_NOTE = 1 + const val DELETION = 5 // NIP-09 event deletion request const val DIRECT_MESSAGE = 14 // NIP-17 direct message (unsigned) const val FILE_MESSAGE = 15 // NIP-17 file message (unsigned) const val SEAL = 13 // NIP-17 sealed event diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt b/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt index 501cf60c..fb4a0cba 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt @@ -128,6 +128,24 @@ object NostrProtocol { return@withContext senderIdentity.signEvent(event) } + /** + * Create a NIP-09 deletion event (kind 5) for a given note + * Signals to relays and other clients that the note should be removed + */ + suspend fun createDeletionEvent( + targetEventId: String, + senderIdentity: NostrIdentity + ): NostrEvent = withContext(Dispatchers.Default) { + val event = NostrEvent( + pubkey = senderIdentity.publicKeyHex, + createdAt = (System.currentTimeMillis() / 1000).toInt(), + kind = NostrKind.DELETION, + tags = listOf(listOf("e", targetEventId)), + content = "" + ) + return@withContext senderIdentity.signEvent(event) + } + /** * Create a geohash-scoped presence event (kind 20001) * Has no content and no nickname, used for participant counting diff --git a/app/src/main/java/com/bitchat/android/ui/LocationNotesSheet.kt b/app/src/main/java/com/bitchat/android/ui/LocationNotesSheet.kt index 29ead6a6..1df6c7a4 100644 --- a/app/src/main/java/com/bitchat/android/ui/LocationNotesSheet.kt +++ b/app/src/main/java/com/bitchat/android/ui/LocationNotesSheet.kt @@ -3,6 +3,7 @@ package com.bitchat.android.ui import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -17,21 +18,25 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.pluralStringResource -import com.bitchat.android.R +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.bitchat.android.R import com.bitchat.android.core.ui.component.sheet.BitchatBottomSheet -import com.bitchat.android.core.ui.component.sheet.BitchatSheetTopBar import com.bitchat.android.core.ui.component.sheet.BitchatSheetTitle +import com.bitchat.android.core.ui.component.sheet.BitchatSheetTopBar import com.bitchat.android.geohash.GeohashChannelLevel import com.bitchat.android.geohash.LocationChannelManager import com.bitchat.android.nostr.LocationNotesManager +import com.bitchat.android.ui.theme.BASE_FONT_SIZE import java.text.SimpleDateFormat import java.util.* import java.util.Calendar @@ -47,36 +52,40 @@ fun LocationNotesSheet( locationName: String?, nickname: String?, onDismiss: () -> Unit, - modifier: Modifier = Modifier + onNoteLongClick: (LocationNotesManager.Note) -> Unit = {}, + modifier: Modifier = Modifier, ) { val context = LocalContext.current val isDark = isSystemInDarkTheme() - + // iOS color scheme - val accentGreen = if (isDark) Color.Green else Color(0xFF008000) // dark: green, light: dark green (0, 0.5, 0) - + val accentGreen = + if (isDark) Color.Green else Color(0xFF008000) // dark: green, light: dark green (0, 0.5, 0) + // Managers val notesManager = remember { LocationNotesManager.getInstance() } val locationManager = remember { LocationChannelManager.getInstance(context) } - + // State val notes by notesManager.notes.collectAsStateWithLifecycle() val state by notesManager.state.collectAsStateWithLifecycle(LocationNotesManager.State.IDLE) val errorMessage by notesManager.errorMessage.collectAsStateWithLifecycle() val initialLoadComplete by notesManager.initialLoadComplete.collectAsStateWithLifecycle(false) - + // SIMPLIFIED: Get count directly from notes list (no separate counter needed) val count = notes.size - + // Get location name (building or block) - matches iOS locationNames lookup val locationNames by locationManager.locationNames.collectAsStateWithLifecycle() - val displayLocationName = locationNames[GeohashChannelLevel.BUILDING]?.takeIf { it.isNotEmpty() } - ?: locationNames[GeohashChannelLevel.BLOCK]?.takeIf { it.isNotEmpty() } - + val displayLocationName = + locationNames[GeohashChannelLevel.BUILDING]?.takeIf { it.isNotEmpty() } + ?: locationNames[GeohashChannelLevel.BLOCK]?.takeIf { it.isNotEmpty() } + // Input field state var draft by remember { mutableStateOf("") } - val sendButtonEnabled = draft.trim().isNotEmpty() && state != LocationNotesManager.State.NO_RELAYS - + val sendButtonEnabled = + draft.trim().isNotEmpty() && state != LocationNotesManager.State.NO_RELAYS + // Scroll state val listState = rememberLazyListState() val isScrolled by remember { @@ -86,7 +95,7 @@ fun LocationNotesSheet( } val topBarAlpha by animateFloatAsState( targetValue = if (isScrolled) 0.95f else 0f, - label = "topBarAlpha" + label = "topBarAlpha", ) // Refresh location when sheet opens @@ -98,7 +107,7 @@ fun LocationNotesSheet( LaunchedEffect(geohash) { notesManager.setGeohash(geohash) } - + // Cleanup when sheet closes DisposableEffect(Unit) { onDispose { @@ -113,8 +122,11 @@ fun LocationNotesSheet( Box(modifier = Modifier.fillMaxWidth()) { LazyColumn( state = listState, - modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp), - contentPadding = PaddingValues(top = 64.dp, bottom = 20.dp) + modifier = + Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + contentPadding = PaddingValues(top = 64.dp, bottom = 20.dp), ) { item(key = "notes_header") { LocationNotesHeader( @@ -129,23 +141,29 @@ fun LocationNotesSheet( state == LocationNotesManager.State.NO_RELAYS -> { item { NoRelaysRow( - onRetry = { notesManager.refresh() } + onRetry = { notesManager.refresh() }, ) } } + state == LocationNotesManager.State.LOADING && !initialLoadComplete -> { item { LoadingRow() } } + notes.isEmpty() -> { item { EmptyRow() } } + else -> { items(notes, key = { it.id }) { note -> - NoteRow(note = note) + NoteRow( + note = note, + onLongClick = onNoteLongClick, + ) Spacer(modifier = Modifier.height(24.dp)) } item { @@ -160,7 +178,7 @@ fun LocationNotesSheet( item { ErrorRow( message = error, - onDismiss = { notesManager.clearError() } + onDismiss = { notesManager.clearError() }, ) } } @@ -173,27 +191,29 @@ fun LocationNotesSheet( modifier = Modifier.align(Alignment.TopCenter), title = { BitchatSheetTitle( - text = pluralStringResource( - id = R.plurals.location_notes_title, - count = count, - geohash, - count - ) + text = + pluralStringResource( + id = R.plurals.location_notes_title, + count = count, + geohash, + count, + ), ) - } + }, ) Box( - modifier = Modifier - .align(Alignment.BottomCenter) - .fillMaxWidth() - ){ + modifier = + Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(), + ) { Column { // Divider before input (matches iOS overlay) HorizontalDivider( modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f), - thickness = 1.dp + thickness = 1.dp, ) // Input section (matches iOS inputSection) @@ -209,7 +229,7 @@ fun LocationNotesSheet( notesManager.send(content, nickname) draft = "" } - } + }, ) } } @@ -228,10 +248,11 @@ private fun LocationNotesHeader( accentGreen: Color, ) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(bottom = 12.dp) + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 12.dp), ) { // Location name in green (building or block) locationName?.let { name -> @@ -240,20 +261,20 @@ private fun LocationNotesHeader( text = name, fontFamily = FontFamily.Monospace, fontSize = 12.sp, - color = accentGreen + color = accentGreen, ) Spacer(modifier = Modifier.height(8.dp)) } } - + // Description Text( text = stringResource(R.string.location_notes_description), fontFamily = FontFamily.Monospace, fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) - + // Relays paused message if no relays if (state == LocationNotesManager.State.NO_RELAYS) { Spacer(modifier = Modifier.height(4.dp)) @@ -261,7 +282,7 @@ private fun LocationNotesHeader( text = stringResource(R.string.location_notes_relays_unavailable), fontFamily = FontFamily.Monospace, fontSize = 11.sp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) } } @@ -272,28 +293,40 @@ private fun LocationNotesHeader( * Shows @basename then timestamp, then content below */ @Composable -private fun NoteRow(note: LocationNotesManager.Note) { +private fun NoteRow( + note: LocationNotesManager.Note, + onLongClick: (LocationNotesManager.Note) -> Unit, +) { // Extract baseName (before #suffix like iOS) val baseName = note.displayName.split("#", limit = 2).firstOrNull() ?: note.displayName val ts = timestampText(note.createdAt) - + val haptic = LocalHapticFeedback.current + Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) + modifier = + Modifier + .fillMaxWidth() + .pointerInput(note.id) { + detectTapGestures( + onLongPress = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onLongClick(note) + }, + ) + }.padding(vertical = 4.dp), ) { // First row: @nickname and timestamp Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Text( text = "@$baseName", fontFamily = FontFamily.Monospace, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) if (ts.isNotEmpty()) { Spacer(modifier = Modifier.width(6.dp)) @@ -301,19 +334,19 @@ private fun NoteRow(note: LocationNotesManager.Note) { text = ts, fontFamily = FontFamily.Monospace, fontSize = 11.sp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) } } - + Spacer(modifier = Modifier.height(2.dp)) - + // Second row: content Text( text = note.content, fontFamily = FontFamily.Monospace, fontSize = 14.sp, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) } } @@ -324,23 +357,24 @@ private fun NoteRow(note: LocationNotesManager.Note) { @Composable private fun NoRelaysRow(onRetry: () -> Unit) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 6.dp) + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), ) { Text( text = stringResource(R.string.location_notes_no_relays_title), fontFamily = FontFamily.Monospace, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) Spacer(modifier = Modifier.height(4.dp)) Text( text = stringResource(R.string.location_notes_no_relays_desc), fontFamily = FontFamily.Monospace, fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) Spacer(modifier = Modifier.height(4.dp)) Text( @@ -348,7 +382,7 @@ private fun NoRelaysRow(onRetry: () -> Unit) { fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = MaterialTheme.colorScheme.primary, - modifier = Modifier.clickable(onClick = onRetry) + modifier = Modifier.clickable(onClick = onRetry), ) } } @@ -359,22 +393,23 @@ private fun NoRelaysRow(onRetry: () -> Unit) { @Composable private fun LoadingRow() { Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { CircularProgressIndicator( modifier = Modifier.size(16.dp), - strokeWidth = 2.dp + strokeWidth = 2.dp, ) Spacer(modifier = Modifier.width(10.dp)) Text( text = stringResource(R.string.loading_location_notes), fontFamily = FontFamily.Monospace, fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) } } @@ -385,23 +420,24 @@ private fun LoadingRow() { @Composable private fun EmptyRow() { Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 6.dp) + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), ) { Text( text = stringResource(R.string.location_notes_empty_title), fontFamily = FontFamily.Monospace, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) Spacer(modifier = Modifier.height(4.dp)) Text( text = stringResource(R.string.location_notes_empty_desc), fontFamily = FontFamily.Monospace, fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) } } @@ -410,26 +446,30 @@ private fun EmptyRow() { * Error row - matches iOS errorRow */ @Composable -private fun ErrorRow(message: String, onDismiss: () -> Unit) { +private fun ErrorRow( + message: String, + onDismiss: () -> Unit, +) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 6.dp) + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), ) { Row( horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, ) { Text( text = "⚠", - fontSize = 12.sp + fontSize = 12.sp, ) Spacer(modifier = Modifier.width(6.dp)) Text( text = message, fontFamily = FontFamily.Monospace, fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) } Spacer(modifier = Modifier.height(4.dp)) @@ -438,7 +478,7 @@ private fun ErrorRow(message: String, onDismiss: () -> Unit) { fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = MaterialTheme.colorScheme.primary, - modifier = Modifier.clickable(onClick = onDismiss) + modifier = Modifier.clickable(onClick = onDismiss), ) } } @@ -486,68 +526,77 @@ private fun LocationNotesInputSection( ) { // Text input with placeholder overlay (matches main chat exactly) Box( - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) { androidx.compose.foundation.text.BasicTextField( value = draft, onValueChange = onDraftChange, - textStyle = MaterialTheme.typography.bodyMedium.copy( - color = colorScheme.primary, - fontFamily = FontFamily.Monospace - ), - cursorBrush = androidx.compose.ui.graphics.SolidColor(colorScheme.primary), - keyboardOptions = androidx.compose.foundation.text.KeyboardOptions( - imeAction = androidx.compose.ui.text.input.ImeAction.Send - ), - keyboardActions = androidx.compose.foundation.text.KeyboardActions( - onSend = { if (sendButtonEnabled) onSend() } - ), - modifier = Modifier.fillMaxWidth() + textStyle = + MaterialTheme.typography.bodyMedium.copy( + color = colorScheme.primary, + fontFamily = FontFamily.Monospace, + ), + cursorBrush = + androidx.compose.ui.graphics + .SolidColor(colorScheme.primary), + keyboardOptions = + androidx.compose.foundation.text.KeyboardOptions( + imeAction = androidx.compose.ui.text.input.ImeAction.Send, + ), + keyboardActions = + androidx.compose.foundation.text.KeyboardActions( + onSend = { if (sendButtonEnabled) onSend() }, + ), + modifier = Modifier.fillMaxWidth(), ) - + // Placeholder when empty (matches main chat) if (draft.isEmpty()) { Text( text = stringResource(R.string.location_notes_input_placeholder), - style = MaterialTheme.typography.bodyMedium.copy( - fontFamily = FontFamily.Monospace - ), + style = + MaterialTheme.typography.bodyMedium.copy( + fontFamily = FontFamily.Monospace, + ), color = colorScheme.onSurface.copy(alpha = 0.5f), - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) } } - + // Send button - circular with icon (matches main chat exactly) IconButton( onClick = { if (sendButtonEnabled) onSend() }, enabled = sendButtonEnabled, - modifier = Modifier.size(32.dp) + modifier = Modifier.size(32.dp), ) { Box( - modifier = Modifier - .size(30.dp) - .background( - color = if (!sendButtonEnabled) { - colorScheme.onSurface.copy(alpha = 0.3f) - } else { - accentGreen.copy(alpha = 0.75f) - }, - shape = CircleShape - ), - contentAlignment = Alignment.Center + modifier = + Modifier + .size(30.dp) + .background( + color = + if (!sendButtonEnabled) { + colorScheme.onSurface.copy(alpha = 0.3f) + } else { + accentGreen.copy(alpha = 0.75f) + }, + shape = CircleShape, + ), + contentAlignment = Alignment.Center, ) { Icon( imageVector = Icons.Filled.ArrowUpward, contentDescription = stringResource(R.string.send_message), modifier = Modifier.size(20.dp), - tint = if (!sendButtonEnabled) { - colorScheme.onSurface.copy(alpha = 0.5f) - } else if (isDark) { - Color.Black // Black arrow on green in dark theme - } else { - Color.White // White arrow on green in light theme - } + tint = + if (!sendButtonEnabled) { + colorScheme.onSurface.copy(alpha = 0.5f) + } else if (isDark) { + Color.Black // Black arrow on green in dark theme + } else { + Color.White // White arrow on green in light theme + }, ) } } @@ -555,6 +604,118 @@ private fun LocationNotesInputSection( } } +/** + * Note Actions Sheet - shown on long-press of a note + * Reuses BitchatBottomSheet pattern from ChatUserSheet + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun NoteActionsSheet( + note: LocationNotesManager.Note, + onDelete: () -> Unit, + onDismiss: () -> Unit, +) { + val colorScheme = MaterialTheme.colorScheme + val isDark = + colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f + val standardRed = Color(0xFFFF3B30) // iOS red + + BitchatBottomSheet( + onDismissRequest = onDismiss, + ) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + // Header: note author + val baseName = note.displayName.split("#", limit = 2).firstOrNull() ?: note.displayName + Text( + text = "@$baseName", + fontSize = 18.sp, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + ) + + // Action list + LazyColumn( + modifier = Modifier.fillMaxWidth(), + ) { + item { + NoteActionRow( + title = stringResource(R.string.action_delete_note_title), + subtitle = stringResource(R.string.action_delete_note_subtitle), + titleColor = standardRed, + onClick = onDelete, + ) + } + } + + // Cancel button + Button( + onClick = onDismiss, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.12f), + contentColor = MaterialTheme.colorScheme.onSurface, + ), + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = stringResource(R.string.cancel_lower), + fontSize = BASE_FONT_SIZE.sp, + fontFamily = FontFamily.Monospace, + ) + } + } + } +} + +/** + * Single action row inside NoteActionsSheet + * Matches UserActionRow pattern from ChatUserSheet + */ +@Composable +private fun NoteActionRow( + title: String, + subtitle: String, + titleColor: Color, + onClick: () -> Unit, +) { + // iOS-style list row (plain button, no card background) + Surface( + onClick = onClick, + color = Color.Transparent, + shape = MaterialTheme.shapes.medium, + modifier = Modifier.fillMaxWidth(), + ) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = title, + fontSize = BASE_FONT_SIZE.sp, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Medium, + color = titleColor, + ) + Text( + text = subtitle, + fontSize = 12.sp, + fontFamily = FontFamily.Monospace, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + } + } +} + /** * Timestamp text - matches iOS timestampText exactly * Shows relative time for < 7 days, absolute date otherwise @@ -562,40 +723,47 @@ private fun LocationNotesInputSection( private fun timestampText(createdAt: Int): String { val date = Date(createdAt * 1000L) val now = Date() - + // Calculate days difference val calendar = Calendar.getInstance() calendar.time = date val dateDay = calendar.get(Calendar.DAY_OF_YEAR) val dateYear = calendar.get(Calendar.YEAR) - + calendar.time = now val nowDay = calendar.get(Calendar.DAY_OF_YEAR) val nowYear = calendar.get(Calendar.YEAR) - - val daysDiff = if (dateYear == nowYear) { - nowDay - dateDay - } else { - // Simplified: just check if less than 7 days by timestamp - val diff = (now.time - date.time) / (1000 * 60 * 60 * 24) - diff.toInt() - } - + + val daysDiff = + if (dateYear == nowYear) { + nowDay - dateDay + } else { + // Simplified: just check if less than 7 days by timestamp + val diff = (now.time - date.time) / (1000 * 60 * 60 * 24) + diff.toInt() + } + return if (daysDiff < 7) { // Relative formatting (abbreviated) val diffMillis = now.time - date.time val diffSeconds = diffMillis / 1000 - + when { - diffSeconds < 60 -> "" // Don't show "just now" in iOS + diffSeconds < 60 -> { + "" + } + + // Don't show "just now" in iOS diffSeconds < 3600 -> { val minutes = (diffSeconds / 60).toInt() "${minutes}m ago" } + diffSeconds < 86400 -> { val hours = (diffSeconds / 3600).toInt() "${hours}h ago" } + else -> { val days = (diffSeconds / 86400).toInt() "${days}d ago" @@ -604,11 +772,12 @@ private fun timestampText(createdAt: Int): String { } else { // Absolute date formatting val sameYear = dateYear == nowYear - val formatter = if (sameYear) { - SimpleDateFormat("MMM d", Locale.getDefault()) - } else { - SimpleDateFormat("MMM d, y", Locale.getDefault()) - } + val formatter = + if (sameYear) { + SimpleDateFormat("MMM d", Locale.getDefault()) + } else { + SimpleDateFormat("MMM d, y", Locale.getDefault()) + } formatter.format(date) } } diff --git a/app/src/main/java/com/bitchat/android/ui/LocationNotesSheetPresenter.kt b/app/src/main/java/com/bitchat/android/ui/LocationNotesSheetPresenter.kt index 9a8cf9e7..cb0476b0 100644 --- a/app/src/main/java/com/bitchat/android/ui/LocationNotesSheetPresenter.kt +++ b/app/src/main/java/com/bitchat/android/ui/LocationNotesSheetPresenter.kt @@ -4,19 +4,22 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.bitchat.android.R import com.bitchat.android.core.ui.component.sheet.BitchatBottomSheet -import com.bitchat.android.core.ui.component.sheet.BitchatSheetTopBar import com.bitchat.android.core.ui.component.sheet.BitchatSheetTitle +import com.bitchat.android.core.ui.component.sheet.BitchatSheetTopBar import com.bitchat.android.geohash.GeohashChannelLevel import com.bitchat.android.geohash.LocationChannelManager -import com.bitchat.android.R +import com.bitchat.android.nostr.LocationNotesManager /** * Presenter component for LocationNotesSheet @@ -27,29 +30,38 @@ import com.bitchat.android.R @Composable fun LocationNotesSheetPresenter( viewModel: ChatViewModel, - onDismiss: () -> Unit + onDismiss: () -> Unit, ) { val context = LocalContext.current val locationManager = remember { LocationChannelManager.getInstance(context) } + val notesManager = remember { LocationNotesManager.getInstance() } val availableChannels by locationManager.availableChannels.collectAsStateWithLifecycle() val permissionState by locationManager.permissionState.collectAsStateWithLifecycle() val isLoadingLocation by locationManager.isLoadingLocation.collectAsStateWithLifecycle() val nickname by viewModel.nickname.collectAsStateWithLifecycle() - + + // Note long-press state — owned here so NoteActionsSheet is a sibling of + // LocationNotesSheet (not nested inside it), which avoids Dialog window conflicts + var selectedNote by remember { mutableStateOf(null) } + val localPubkey by notesManager.localPubkey.collectAsStateWithLifecycle() + // iOS pattern: notesGeohash ?? LocationChannelManager.shared.availableChannels.first(where: { $0.level == .building })?.geohash - val buildingGeohash = availableChannels.firstOrNull { it.level == GeohashChannelLevel.BUILDING }?.geohash - + val buildingGeohash = + availableChannels.firstOrNull { it.level == GeohashChannelLevel.BUILDING }?.geohash + if (buildingGeohash != null) { // Get location name from locationManager val locationNames by locationManager.locationNames.collectAsStateWithLifecycle() - val locationName = locationNames[GeohashChannelLevel.BUILDING] - ?: locationNames[GeohashChannelLevel.BLOCK] - + val locationName = + locationNames[GeohashChannelLevel.BUILDING] + ?: locationNames[GeohashChannelLevel.BLOCK] + LocationNotesSheet( geohash = buildingGeohash, locationName = locationName, nickname = nickname, - onDismiss = onDismiss + onDismiss = onDismiss, + onNoteLongClick = { note -> selectedNote = note }, ) } else if (permissionState == LocationChannelManager.PermissionState.AUTHORIZED && isLoadingLocation) { LocationNotesAcquiringSheet(onDismiss = onDismiss) @@ -57,7 +69,19 @@ fun LocationNotesSheetPresenter( // No building geohash available - show error state (matches iOS) LocationNotesErrorSheet( onDismiss = onDismiss, - locationManager = locationManager + locationManager = locationManager, + ) + } + + // Note actions sheet + selectedNote?.takeIf { it.pubkey == localPubkey }?.let { note -> + NoteActionsSheet( + note = note, + onDelete = { + notesManager.deleteNote(note.id) + selectedNote = null + }, + onDismiss = { selectedNote = null }, ) } } @@ -67,33 +91,32 @@ fun LocationNotesSheetPresenter( */ @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun LocationNotesAcquiringSheet( - onDismiss: () -> Unit -) { +private fun LocationNotesAcquiringSheet(onDismiss: () -> Unit) { BitchatBottomSheet( onDismissRequest = onDismiss, ) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(24.dp), - horizontalAlignment = Alignment.CenterHorizontally + modifier = + Modifier + .fillMaxWidth() + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { Text( text = "Acquiring Location", style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) Spacer(modifier = Modifier.height(24.dp)) CircularProgressIndicator( modifier = Modifier.size(48.dp), - color = MaterialTheme.colorScheme.primary + color = MaterialTheme.colorScheme.primary, ) Spacer(modifier = Modifier.height(24.dp)) Text( text = "Please wait while your location is being determined", style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, ) } } @@ -106,30 +129,31 @@ private fun LocationNotesAcquiringSheet( @Composable private fun LocationNotesErrorSheet( onDismiss: () -> Unit, - locationManager: LocationChannelManager + locationManager: LocationChannelManager, ) { BitchatBottomSheet( onDismissRequest = onDismiss, ) { Box(modifier = Modifier.fillMaxWidth()) { Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - .padding(top = 80.dp, bottom = 24.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(top = 80.dp, bottom = 24.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Text( text = "Location Unavailable", style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurface + color = MaterialTheme.colorScheme.onSurface, ) Spacer(modifier = Modifier.height(16.dp)) Text( text = "Location permission is required for notes", style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, ) Spacer(modifier = Modifier.height(24.dp)) Button(onClick = { @@ -148,9 +172,9 @@ private fun LocationNotesErrorSheet( modifier = Modifier.align(Alignment.TopCenter), title = { BitchatSheetTitle( - text = stringResource(R.string.cd_location_notes).uppercase() + text = stringResource(R.string.cd_location_notes).uppercase(), ) - } + }, ) } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3d72337a..4ad579ed 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -192,6 +192,8 @@ إرسال عناق ودي حظر %1$s حظر جميع رسائل هذا المستخدم + حذف الملاحظة + يرسل طلب حذف nostr إلى المرحلات #قنوات الموقع diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 3cda37a6..0ac2935b 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -192,6 +192,8 @@ একটি বন্ধুত্বপূর্ণ আলিঙ্গন পাঠান %1$s কে ব্লক করুন এই ব্যবহারকারীর সমস্ত বার্তা ব্লক করুন + নোট মুছুন + রিলেতে nostr মুছে ফেলার অনুরোধ পাঠায় #অবস্থান চ্যানেল diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c6cb4aac..f5dd3cfc 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -192,6 +192,8 @@ Eine freundliche Umarmung senden %1$s blockieren Alle Nachrichten dieses Benutzers blockieren + Notiz löschen + sendet eine nostr-Löschanfrage an Relays #Standort‑Kanäle diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b8fed3aa..6d072abd 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -192,6 +192,8 @@ Enviar un abrazo amistoso Bloquear a %1$s Bloquear todos los mensajes de este usuario + eliminar nota + envía una solicitud de eliminación de nostr a los relays #Canales de ubicación diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 8ad97fe9..1fc731fc 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -192,6 +192,8 @@ یک بغل دوستانه ارسال کن %1$s را مسدود کن همهٔ پیام‌های این کاربر را مسدود کن + حذف یادداشت + درخواست حذف nostr را به رله‌ها ارسال می‌کند #کانال‌های مکانی diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 34e1d5f5..ad2b3288 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -185,6 +185,8 @@ magpadala ng magiliw na yakap harangan si %1$s harangan ang lahat ng mensahe mula sa user na ito + tanggalin ang tala + nagpapadala ng kahilingan sa pagtanggal ng nostr sa mga relay #mga channel sa lokasyon diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9828a76b..f80ec2c1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -185,6 +185,8 @@ envoyer une accolade amicale bloquer %1$s bloquer tous les messages de cet utilisateur + supprimer la note + envoie une demande de suppression nostr aux relais #canaux de lieu diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index e18b57b8..b1a4f9f1 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -53,5 +53,7 @@ Verified You verified %1$s verified %1$s + מחק הערה + שולח בקשת מחיקה של nostr לממסרים diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 02d64711..510a11b8 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -192,6 +192,8 @@ दोस्ताना आलिंगन भेजें %1$s को ब्लॉक करें इस उपयोगकर्ता के सभी संदेश ब्लॉक करें + नोट हटाएं + रिले को nostr हटाने का अनुरोध भेजता है #स्थान चैनल diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index fb9830be..84cd33d4 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -192,6 +192,8 @@ Kirim pelukan yang ramah Blokir %1$s Blokir semua pesan dari pengguna ini + hapus catatan + mengirim permintaan penghapusan nostr ke relay #Channel lokasi diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3db4e85e..5b25e9b1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -192,6 +192,8 @@ invia un abbraccio amichevole blocca %1$s blocca tutti i messaggi da questo utente + elimina nota + invia una richiesta di eliminazione nostr ai relay #canali di posizione diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index df489157..2a19d452 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -192,6 +192,8 @@ フレンドリーなハグを送る %1$s をブロック このユーザーからのすべてのメッセージをブロック + ノートを削除 + nostr の削除リクエストをリレーに送信します #ロケーション・チャンネル diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 2d1c6693..8d28f0a0 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -192,6 +192,8 @@ გაგზავნეთ მეგობრული ჩახუტება %1$s-ის დაბლოკვა ამ მომხმარებლის ყველა შეტყობინების დაბლოკვა + ჩანაწერის წაშლა + nostr-ის წაშლის მოთხოვნას გზავნის relay-ებზე #მდებარეობის არხები diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 68d46136..c57d5ae1 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -192,6 +192,8 @@ 친근한 포옹 보내기 %1$s 차단 이 사용자의 모든 메시지 차단 + 노트 삭제 + nostr 삭제 요청을 릴레이에 전송합니다 #위치 채널 diff --git a/app/src/main/res/values-mg/strings.xml b/app/src/main/res/values-mg/strings.xml index 57676dec..4dc9ca31 100644 --- a/app/src/main/res/values-mg/strings.xml +++ b/app/src/main/res/values-mg/strings.xml @@ -192,6 +192,8 @@ handefasa hafatra honofinofy sariaka hanorina %1$s hanorina ny hafatra rehetra avy amin\'ity mpampiasa ity + fafao ny fanamarihana + mandefa fangatahana fafana nostr any amin\'ny relay #fantsona toerana diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index b4b23655..d96d11db 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -40,5 +40,7 @@ Verified You verified %1$s verified %1$s + padam nota + menghantar permintaan pemadaman nostr ke relay diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml index 252f81c4..7876086f 100644 --- a/app/src/main/res/values-ne/strings.xml +++ b/app/src/main/res/values-ne/strings.xml @@ -185,6 +185,8 @@ मैत्रीपूर्ण अँगालो पठाउनुहोस् %1$s लाई ब्लक गर्नुहोस् यस प्रयोगकर्ताबाट सबै सन्देश ब्लक गर्नुहोस् + नोट मेट्नुहोस् + रिलेहरूमा nostr मेटाउने अनुरोध पठाउँछ #स्थान च्यानल diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 59705572..804b82da 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -192,6 +192,8 @@ stuur een vriendelijke knuffel %1$s blokkeren blokkeer alle berichten van deze gebruiker + notitie verwijderen + stuurt een nostr-verwijderverzoek naar relays #locatiekanalen diff --git a/app/src/main/res/values-pa-rPK/strings.xml b/app/src/main/res/values-pa-rPK/strings.xml index 58135f7c..b512396a 100644 --- a/app/src/main/res/values-pa-rPK/strings.xml +++ b/app/src/main/res/values-pa-rPK/strings.xml @@ -192,6 +192,8 @@ دوستانہ گلے ملنا پھجو %1$s نوں بلاک کرو اس یوزر دے سارے پیغام بلاک کرو + ਨੋਟ ਮਿਟਾਓ + ਰਿਲੇਅਾਂ ਨੂੰ nostr ਮਿਟਾਉਣ ਦੀ ਬੇਨਤੀ ਭੇਜਦਾ ਹੈ #لوکیشن چینل diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cdbffdd0..2604d7a2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -53,5 +53,7 @@ Verified You verified %1$s verified %1$s + usuń notatkę + wysyła żądanie usunięcia nostr do przekaźników diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 45f41512..6b931b12 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -192,6 +192,8 @@ Enviar um abraço amigável Bloquear %1$s Bloquear todas as mensagens deste usuário + excluir nota + envia uma solicitação de exclusão nostr para os relays #Canais de localização diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 147b5f37..261a092d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -192,6 +192,8 @@ Enviar um abraço amigável Bloquear %1$s Bloquear todas as mensagens deste utilizador + eliminar nota + envia um pedido de eliminação nostr para os relays #Canais de localização diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index dade7d1d..ac6936dc 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -172,6 +172,8 @@ отправить дружеские объятия заблокировать %1$s заблокировать все сообщения от этого пользователя + удалить заметку + отправляет запрос на удаление nostr в ретрансляторы #каналы локации общайся с людьми рядом через каналы geohash. делится только грубый geohash, никогда точный gps. не делай скриншоты и не делись экраном для защиты приватности. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 9ac7df67..4a9b2d1e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -172,6 +172,8 @@ skicka en vänlig kram blockera %1$s blockera alla meddelanden från den här användaren + ta bort anteckning + skickar en nostr-borttagningsbegäran till reläer #platskanaler chatta med folk nära dig via geohash‑kanaler. endast en grov geohash delas, aldrig exakt gps. ta inte skärmdumpar eller dela skärmen för att skydda din integritet. diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 1e487548..320bf8bb 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -40,5 +40,7 @@ Verified You verified %1$s verified %1$s + குறிப்பை நீக்கு + relay-களுக்கு nostr நீக்கும் கோரிக்கையை அனுப்புகிறது diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 4a4c5021..4e16b232 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -192,6 +192,8 @@ ส่งกอดแบบเป็นมิตร บล็อก %1$s บล็อกข้อความทั้งหมดจากผู้ใช้นี้ + ลบบันทึก + ส่งคำขอลบ nostr ไปยัง relay #ช่องตามตำแหน่ง diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a2df9dfe..1e9e0fec 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -172,6 +172,8 @@ samimi bir sarılma gönder %1$s kişisini engelle bu kullanıcıdan gelen tüm mesajları engelle + notu sil + rölelere bir nostr silme isteği gönderir #konum kanalları yakındaki insanlarla geohash kanallarıyla sohbet et. yalnızca kabaca geohash paylaşılır, kesin gps asla değil. gizliliğin için ekran görüntüsü alma ve paylaşma. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2c631d04..016caa17 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -40,5 +40,7 @@ Verified You verified %1$s verified %1$s + видалити нотатку + надсилає запит на видалення nostr до ретрансляторів diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 291c3e9a..fcd724d5 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -192,6 +192,8 @@ دوستانہ آلنگن بھیجیں %1$s کو بلاک کریں اس صارف کے تمام پیغامات بلاک کریں + نوٹ حذف کریں + ریلے پر nostr حذف کی درخواست بھیجتا ہے #مقام چینلز diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index a1315617..932859d8 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -192,6 +192,8 @@ Gửi một cái ôm thân thiện Chặn %1$s Chặn tất cả tin nhắn từ người dùng này + xóa ghi chú + gửi yêu cầu xóa nostr tới các relay #Kênh vị trí diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 69bff70c..8171ca39 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -53,5 +53,7 @@ 已验证 你已验证 %1$s 已验证 %1$s + 删除笔记 + 向中继发送 nostr 删除请求 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 1dcf6b27..23557b5a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -53,5 +53,7 @@ 已验证 你已验证 %1$s 已验证 %1$s + 刪除筆記 + 向中繼發送 nostr 刪除請求 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 46caf1af..7a169115 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -185,6 +185,8 @@ 发送一个友好的拥抱 屏蔽 %1$s 屏蔽该用户的所有消息 + 删除笔记 + 向中继发送 nostr 删除请求 #地点频道 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27a5c5e5..d0424ed2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,7 +18,7 @@ No one connected Triple tap to clear all data Network - + Battery Optimization Detected Battery Optimization Disabled @@ -37,7 +37,7 @@ Continue Retry Skip - + and %1$d more %1$d messages from %2$d people @@ -201,10 +201,12 @@ hug %1$s send a friendly hug message block %1$s + block all messages from this user message %1$s send a private message - + delete note + sends a nostr deletion request to relays #location channels