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

This commit is contained in:
callebtc 2026-07-29 02:04:09 +02:00
parent 86a04bc8a2
commit c1e6204686
3 changed files with 102 additions and 11 deletions

View File

@ -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)) }

View File

@ -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)
}
}
}

View File

@ -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,