@@ -17,15 +17,21 @@ import androidx.compose.material.icons.Icons
1717import androidx.compose.material.icons.filled.Lock
1818import androidx.compose.material.icons.filled.Person
1919import androidx.compose.material.icons.filled.Terminal
20+import androidx.compose.material.icons.filled.Visibility
21+import androidx.compose.material.icons.filled.VisibilityOff
2022import androidx.compose.material3.Button
2123import androidx.compose.material3.CircularProgressIndicator
2224import androidx.compose.material3.Icon
25+import androidx.compose.material3.IconButton
2326import androidx.compose.material3.MaterialTheme
2427import androidx.compose.material3.OutlinedTextField
2528import androidx.compose.material3.Text
2629import androidx.compose.runtime.Composable
2730import androidx.compose.runtime.LaunchedEffect
31+import androidx.compose.runtime.getValue
32+import androidx.compose.runtime.mutableStateOf
2833import androidx.compose.runtime.remember
34+import androidx.compose.runtime.setValue
2935import androidx.compose.ui.Alignment
3036import androidx.compose.ui.Modifier
3137import androidx.compose.ui.focus.FocusRequester
@@ -33,6 +39,7 @@ import androidx.compose.ui.focus.focusRequester
3339import androidx.compose.ui.text.input.ImeAction
3440import androidx.compose.ui.text.input.KeyboardType
3541import androidx.compose.ui.text.input.PasswordVisualTransformation
42+import androidx.compose.ui.text.input.VisualTransformation
3643import androidx.compose.ui.text.style.TextAlign
3744import androidx.compose.ui.unit.dp
3845import soy.iko.opencode.core.Event
@@ -41,6 +48,7 @@ import soy.iko.opencode.core.UiState
4148@Composable
4249fun ConnectScreen(state: UiState, dispatch: (Event) -> Unit) {
4350 val focusRequester = remember { FocusRequester() }
51+ var passwordVisible by remember { mutableStateOf(false) }
4452
4553 // Focus the server URL field when the screen opens, but only when it's empty
4654 // so we don't steal focus (and pop the keyboard) on a return visit.
@@ -111,8 +119,16 @@ fun ConnectScreen(state: UiState, dispatch: (Event) -> Unit) {
111119 onValueChange = { dispatch(Event.PasswordChanged(it)) },
112120 label = { Text("Password") },
113121 singleLine = true,
114− visualTransformation = PasswordVisualTransformation(),
122+ visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
115123 leadingIcon = { Icon(Icons.Filled.Lock, contentDescription = null) },
124+ trailingIcon = {
125+ IconButton(onClick = { passwordVisible = !passwordVisible }) {
126+ Icon(
127+ imageVector = if (passwordVisible) Icons.Filled.VisibilityOff else Icons.Filled.Visibility,
128+ contentDescription = if (passwordVisible) "Hide password" else "Show password",
129+ )
130+ }
131+ },
116132 modifier = Modifier.fillMaxWidth(),
117133 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Go),
118134 keyboardActions = KeyboardActions(onGo = { dispatch(Event.Connect) }),