v1.2.3: fix keyboard order, empty password, new logo

This commit is contained in:
茂之钳
2026-05-24 10:40:11 +00:00
parent b3281928f1
commit 9bf76bfed7
22 changed files with 43 additions and 26 deletions
@@ -65,6 +65,12 @@ fun SshHost(modifier: Modifier = Modifier) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
fun openSession(config: SshConfig) { fun openSession(config: SshConfig) {
// Bug 3: 如果密码为空,弹出连接对话框让用户输入密码
if (config.password.isBlank() && config.authMode != "key") {
pendingConfig = config
showConnectDialog = true
return
}
// 立即创建 tab 显示"连接中" // 立即创建 tab 显示"连接中"
val mgr = SshTerminalManager(sessionId = "${config.host}:${config.port}") val mgr = SshTerminalManager(sessionId = "${config.host}:${config.port}")
activeSessions.add(mgr) activeSessions.add(mgr)
@@ -242,6 +248,18 @@ fun SshHost(modifier: Modifier = Modifier) {
} }
if (showConnectDialog) { if (showConnectDialog) {
// Bug 3: 如果是从空密码连接触发的,预填配置给用户输入密码
val prefillConfig = pendingConfig
if (prefillConfig != null && prefillConfig.password.isBlank()) {
// 预填配置到 dialog 后清除 pending 标记
SshConnectDialog(
onDismiss = { showConnectDialog = false; pendingConfig = null },
onConnectPassword = { config -> showConnectDialog = false; pendingConfig = config },
onConnectKey = { config, passphrase -> showConnectDialog = false; pendingKeyConnect = KeyAuthConnect(config, passphrase) },
savedConfigs = savedConfigs,
initialConfig = prefillConfig
)
} else {
SshConnectDialog( SshConnectDialog(
onDismiss = { showConnectDialog = false }, onDismiss = { showConnectDialog = false },
onConnectPassword = { config -> showConnectDialog = false; pendingConfig = config }, onConnectPassword = { config -> showConnectDialog = false; pendingConfig = config },
@@ -250,6 +268,7 @@ fun SshHost(modifier: Modifier = Modifier) {
) )
} }
} }
}
} }
// ─── 连接列表(分组 + 搜索) ─── // ─── 连接列表(分组 + 搜索) ───
@@ -69,16 +69,17 @@ fun SshConnectDialog(
onConnectPassword: (SshConfig) -> Unit, onConnectPassword: (SshConfig) -> Unit,
onConnectKey: (config: SshConfig, passphrase: String?) -> Unit, onConnectKey: (config: SshConfig, passphrase: String?) -> Unit,
savedConfigs: List<SshConfig> = emptyList(), savedConfigs: List<SshConfig> = emptyList(),
onFillConfig: (SshConfig) -> Unit = {} onFillConfig: (SshConfig) -> Unit = {},
initialConfig: SshConfig? = null
) { ) {
var host by remember { mutableStateOf("") } var host by remember { mutableStateOf(initialConfig?.host ?: "") }
var port by remember { mutableStateOf("22") } var port by remember { mutableStateOf((initialConfig?.port ?: 22).toString()) }
var username by remember { mutableStateOf("") } var username by remember { mutableStateOf(initialConfig?.username ?: "") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf(initialConfig?.password ?: "") }
var displayName by remember { mutableStateOf("") } var displayName by remember { mutableStateOf(initialConfig?.displayName ?: "") }
var authMethod by remember { mutableStateOf("password") } var authMethod by remember { mutableStateOf(initialConfig?.authMode ?: "password") }
var keyPassphrase by remember { mutableStateOf("") } var keyPassphrase by remember { mutableStateOf("") }
var selectedKeyPath by remember { mutableStateOf("") } var selectedKeyPath by remember { mutableStateOf(initialConfig?.savedKeyPath ?: "") }
var passwordVisible by remember { mutableStateOf(false) } var passwordVisible by remember { mutableStateOf(false) }
var keyPassVisible by remember { mutableStateOf(false) } var keyPassVisible by remember { mutableStateOf(false) }
@@ -417,8 +418,7 @@ fun SshTerminalScreen(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val state by manager.connectionState.collectAsState() val state by manager.connectionState.collectAsState()
val inputState = remember { mutableStateOf(TextFieldValue("")) } var currentInput by remember { mutableStateOf("") }
var currentInput by inputState
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
var showFontPicker by remember { mutableStateOf(false) } var showFontPicker by remember { mutableStateOf(false) }
@@ -437,7 +437,7 @@ fun SshTerminalScreen(
} }
// 获取当前输入的纯文本(用于显示) // 获取当前输入的纯文本(用于显示)
val currentInputText = currentInput.text val currentInputText = currentInput
// 硬件键盘处理 // 硬件键盘处理
fun handleKeyEvent(event: KeyEvent): Boolean { fun handleKeyEvent(event: KeyEvent): Boolean {
@@ -452,13 +452,13 @@ fun SshTerminalScreen(
manager.sendCommand(cmd) manager.sendCommand(cmd)
} }
} }
currentInput = TextFieldValue("") currentInput = ""
true true
} }
Key.Backspace -> { Key.Backspace -> {
val t = currentInputText val t = currentInputText
if (t.isNotEmpty()) { if (t.isNotEmpty()) {
currentInput = TextFieldValue(t.substring(0, t.length - 1)) currentInput = t.substring(0, t.length - 1)
} }
true true
} }
@@ -488,19 +488,17 @@ fun SshTerminalScreen(
) { ) {
// 透明输入框 — 极小的 1x30 dp,仅用来弹出软键盘 // 透明输入框 — 极小的 1x30 dp,仅用来弹出软键盘
BasicTextField( BasicTextField(
value = currentInput, value = TextFieldValue(currentInput),
onValueChange = { newVal -> onValueChange = { newVal ->
val t = newVal.text currentInput = newVal.text
if (t.endsWith('\n')) { if (newVal.text.endsWith('\n')) {
val cmd = t.dropLast(1).trim() val cmd = newVal.text.dropLast(1).trim()
if (cmd.isNotEmpty()) { if (cmd.isNotEmpty()) {
coroutineScope.launch { coroutineScope.launch {
manager.sendCommand(cmd) manager.sendCommand(cmd)
} }
} }
currentInput = TextFieldValue("") currentInput = ""
} else {
currentInput = newVal
} }
}, },
modifier = Modifier modifier = Modifier
Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB