From c1e62046868859c24f324648bcc80a0e94d21f90 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 Jul 2026 02:04:09 +0200 Subject: [PATCH] wear: rename from People screen - 'you' row first opens nickname editor; nickname field auto-focuses with cursor at end, IME Done only closes keyboard for review, confirm button is the single commit path, trim while typing --- .../java/com/bitchat/watch/MainActivity.kt | 19 ++++++- .../bitchat/watch/ui/NicknameSetupScreen.kt | 52 +++++++++++++++---- .../java/com/bitchat/watch/ui/PeopleScreen.kt | 42 ++++++++++++++- 3 files changed, 102 insertions(+), 11 deletions(-) diff --git a/wear/src/main/java/com/bitchat/watch/MainActivity.kt b/wear/src/main/java/com/bitchat/watch/MainActivity.kt index 342ad19e..4e82d260 100644 --- a/wear/src/main/java/com/bitchat/watch/MainActivity.kt +++ b/wear/src/main/java/com/bitchat/watch/MainActivity.kt @@ -54,6 +54,7 @@ import com.bitchat.watch.ui.theme.BitchatWearTheme sealed interface WearScreen { data object Chat : WearScreen data object People : WearScreen + data object Nickname : WearScreen data class Dm(val peerID: String) : WearScreen data class TextInput(val peerID: String?) : WearScreen } @@ -209,7 +210,23 @@ fun WearNavHost(openDmPeer: String?, onOpenDmHandled: () -> Unit) { onOpenPeople = { navigate(WearScreen.People) }, onOpenTextInput = { navigate(WearScreen.TextInput(null)) } ) - is WearScreen.People -> PeopleScreen(onOpenDm = { navigate(WearScreen.Dm(it)) }) + is WearScreen.People -> PeopleScreen( + onOpenDm = { navigate(WearScreen.Dm(it)) }, + onEditNickname = { navigate(WearScreen.Nickname) } + ) + is WearScreen.Nickname -> { + val mesh = WearMeshService.peek() + NicknameSetupScreen( + initialNickname = mesh?.nickname ?: "", + title = "you", + subtitle = "this is how nearby peers see you", + confirmLabel = "save", + onConfirm = { name -> + mesh?.setNickname(name) + goBack() + } + ) + } is WearScreen.Dm -> DmScreen( peerID = current.peerID, onOpenTextInput = { navigate(WearScreen.TextInput(current.peerID)) } diff --git a/wear/src/main/java/com/bitchat/watch/ui/NicknameSetupScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/NicknameSetupScreen.kt index e6493f96..0a5190e8 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/NicknameSetupScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/NicknameSetupScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -19,9 +20,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.wear.compose.material3.Button @@ -30,13 +36,33 @@ import androidx.wear.compose.material3.Text import com.bitchat.watch.ui.theme.ChatVisualTokens import com.bitchat.watch.ui.theme.LocalBitchatPalette +/** + * Nickname entry, used both for first-run onboarding and for renaming later. The IME's + * Done action only closes the keyboard so the user can review the name; the confirm + * button is the single commit path. + */ @Composable fun NicknameSetupScreen( initialNickname: String, + title: String = "bitchat", + subtitle: String = "pick a nickname", + confirmLabel: String = "join the mesh", onConfirm: (String) -> Unit ) { val palette = LocalBitchatPalette.current - var name by remember { mutableStateOf(initialNickname) } + // Pre-fill with the cursor at the end of the existing name, not the start. + var name by remember { + mutableStateOf( + TextFieldValue( + text = initialNickname, + selection = TextRange(initialNickname.length) + ) + ) + } + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + + LaunchedEffect(Unit) { focusRequester.requestFocus() } Column( modifier = Modifier @@ -46,13 +72,13 @@ fun NicknameSetupScreen( verticalArrangement = Arrangement.Center ) { Text( - text = "bitchat", + text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary ) Text( - text = "pick a nickname", + text = subtitle, style = MaterialTheme.typography.bodySmall, color = palette.textTertiary, textAlign = TextAlign.Center, @@ -60,7 +86,14 @@ fun NicknameSetupScreen( ) BasicTextField( value = name, - onValueChange = { name = it.take(24) }, + onValueChange = { newValue -> + val trimmed = newValue.text.trim().take(24) + name = if (trimmed == newValue.text) { + newValue + } else { + newValue.copy(text = trimmed, selection = TextRange(trimmed.length)) + } + }, singleLine = true, textStyle = ChatVisualTokens.MessageBodyStyle.copy( color = MaterialTheme.colorScheme.onSurface, @@ -69,16 +102,17 @@ fun NicknameSetupScreen( cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { - if (name.isNotBlank()) onConfirm(name.trim()) + keyboardController?.hide() }), modifier = Modifier .fillMaxWidth() + .focusRequester(focusRequester) .clip(RoundedCornerShape(18.dp)) .background(palette.inputSurface) .padding(horizontal = 12.dp, vertical = 8.dp), decorationBox = { innerTextField -> Box(contentAlignment = Alignment.Center) { - if (name.isEmpty()) { + if (name.text.isEmpty()) { Text( text = "nickname", style = ChatVisualTokens.MessageBodyStyle, @@ -90,11 +124,11 @@ fun NicknameSetupScreen( } ) Button( - onClick = { if (name.isNotBlank()) onConfirm(name.trim()) }, - enabled = name.isNotBlank(), + onClick = { if (name.text.isNotBlank()) onConfirm(name.text.trim()) }, + enabled = name.text.isNotBlank(), modifier = Modifier.padding(top = 10.dp) ) { - Text("join the mesh") + Text(confirmLabel) } } } diff --git a/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt b/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt index 9a332a02..20462231 100644 --- a/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt +++ b/wear/src/main/java/com/bitchat/watch/ui/PeopleScreen.kt @@ -34,7 +34,7 @@ import com.bitchat.watch.ui.theme.LocalBitchatPalette import com.bitchat.watch.ui.theme.colorForPeer @Composable -fun PeopleScreen(onOpenDm: (String) -> Unit) { +fun PeopleScreen(onOpenDm: (String) -> Unit, onEditNickname: () -> Unit) { val peers by AppStateStore.peers.collectAsState() val unread by WearChatState.unreadDms.collectAsState() val mesh = WearMeshService.peek() @@ -77,6 +77,12 @@ fun PeopleScreen(onOpenDm: (String) -> Unit) { ) } } + item(key = "self") { + SelfRow( + nickname = mesh?.nickname ?: "me", + onClick = onEditNickname + ) + } items(sortedPeers, key = { it }) { peerID -> val nick = nicknames[peerID] ?: peerID.take(8) PersonRow( @@ -91,6 +97,40 @@ fun PeopleScreen(onOpenDm: (String) -> Unit) { } } +@Composable +private fun SelfRow(nickname: String, onClick: () -> Unit) { + val palette = LocalBitchatPalette.current + Card( + onClick = onClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 10.dp, vertical = 2.dp) + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = nickname, + style = ChatVisualTokens.SenderStyle, + color = palette.accentOrange, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false) + ) + Text( + text = " (you)", + style = ChatVisualTokens.SenderStyle, + color = palette.textTertiary + ) + } + Text( + text = "tap to rename", + style = ChatVisualTokens.SystemActionStyle, + color = palette.textTertiary + ) + } + } +} + @Composable private fun PersonRow( nickname: String,