diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt index 64ee244611..9517a3b62b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt @@ -58,6 +58,34 @@ class GeohashChatIdentityState( @Volatile private var cachedDeviceSeed: ByteArray? = null + @Volatile private var cachedNickname: String? = null + + /** + * The user's display handle for location chats: a single global nickname, persisted per account. + * Bitchat carries this as the per-message `["n", …]` tag rather than a kind-0 profile, and kind-20000 + * messages are ephemeral (relays needn't store them), so the only durable home for it is the device. + * Kept in this account's encrypted storage, so it survives restarts and switches with the account. + * Empty string means "no nickname set". Reads touch disk on first call — invoke off the main thread. + */ + fun nickname(): String { + cachedNickname?.let { return it } + synchronized(lock) { + cachedNickname?.let { return it } + val value = Amethyst.instance.encryptedStorage(signer.pubKey).getString(PREF_NICKNAME, "") ?: "" + cachedNickname = value + return value + } + } + + /** Persists the global location-chat nickname (trimmed) for this account. */ + fun setNickname(value: String) { + val trimmed = value.trim() + synchronized(lock) { + cachedNickname = trimmed + Amethyst.instance.encryptedStorage(signer.pubKey).edit { putString(PREF_NICKNAME, trimmed) } + } + } + /** The Nostr key pair to use inside [geohash]. Derivation is cheap but cached; call off the main thread. */ fun keyPair(geohash: String): KeyPair = synchronized(lock) { @@ -90,5 +118,6 @@ class GeohashChatIdentityState( companion object { private const val PREF_KEY = "geohash_chat_device_seed" + private const val PREF_NICKNAME = "geohash_chat_nickname" } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt index 91f14fd526..54f7c60898 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt @@ -87,6 +87,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send.E import com.vitorpamplona.quartz.experimental.bitchat.geohash.GeohashChatEvent import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon /** @@ -143,6 +145,13 @@ private fun GeohashChatRoom( newMessageModel.init(accountViewModel) newMessageModel.load(channel) + // The nickname lives on-device (the throwaway key has no kind-0 profile, and kind-20000 messages + // are ephemeral), so restore the account's saved global handle into the composer on open. + LaunchedEffect(newMessageModel) { + val saved = withContext(Dispatchers.IO) { accountViewModel.account.geohashIdentity.nickname() } + if (saved.isNotBlank()) newMessageModel.geohashNickname = saved + } + // Teleport is a fact, not a per-message choice: the app decides whether the sender is physically // in this cell. When the device's location is known we compare it to the channel cell (objective); // otherwise we fall back to how the user arrived — the map picker passes teleported=true, "near me" @@ -277,7 +286,7 @@ private fun GeohashIdentityAvatar( ) { var showNickname by remember { mutableStateOf(false) } if (showNickname) { - GeohashNicknameDialog(model) { showNickname = false } + GeohashNicknameDialog(model, accountViewModel) { showNickname = false } } val postAsSelf = model.geohashPostAsSelf @@ -342,6 +351,7 @@ private fun GeohashIdentityAvatar( @Composable private fun GeohashNicknameDialog( model: ChannelNewMessageViewModel, + accountViewModel: AccountViewModel, onDismiss: () -> Unit, ) { var text by remember { mutableStateOf(model.geohashNickname) } @@ -349,7 +359,10 @@ private fun GeohashNicknameDialog( onDismissRequest = onDismiss, confirmButton = { TextButton(onClick = { - model.geohashNickname = text.trim() + val trimmed = text.trim() + model.geohashNickname = trimmed + // Persist the global handle so it survives app restarts (see GeohashChatIdentityState). + accountViewModel.account.geohashIdentity.setNickname(trimmed) onDismiss() }) { Text("Save") } },