v1.2.3: fix keyboard order, empty password, new logo
@@ -65,6 +65,12 @@ fun SshHost(modifier: Modifier = Modifier) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun openSession(config: SshConfig) {
|
||||
// Bug 3: 如果密码为空,弹出连接对话框让用户输入密码
|
||||
if (config.password.isBlank() && config.authMode != "key") {
|
||||
pendingConfig = config
|
||||
showConnectDialog = true
|
||||
return
|
||||
}
|
||||
// 立即创建 tab 显示"连接中"
|
||||
val mgr = SshTerminalManager(sessionId = "${config.host}:${config.port}")
|
||||
activeSessions.add(mgr)
|
||||
@@ -242,12 +248,25 @@ fun SshHost(modifier: Modifier = Modifier) {
|
||||
}
|
||||
|
||||
if (showConnectDialog) {
|
||||
SshConnectDialog(
|
||||
onDismiss = { showConnectDialog = false },
|
||||
onConnectPassword = { config -> showConnectDialog = false; pendingConfig = config },
|
||||
onConnectKey = { config, passphrase -> showConnectDialog = false; pendingKeyConnect = KeyAuthConnect(config, passphrase) },
|
||||
savedConfigs = savedConfigs
|
||||
)
|
||||
// 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(
|
||||
onDismiss = { showConnectDialog = false },
|
||||
onConnectPassword = { config -> showConnectDialog = false; pendingConfig = config },
|
||||
onConnectKey = { config, passphrase -> showConnectDialog = false; pendingKeyConnect = KeyAuthConnect(config, passphrase) },
|
||||
savedConfigs = savedConfigs
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,16 +69,17 @@ fun SshConnectDialog(
|
||||
onConnectPassword: (SshConfig) -> Unit,
|
||||
onConnectKey: (config: SshConfig, passphrase: String?) -> Unit,
|
||||
savedConfigs: List<SshConfig> = emptyList(),
|
||||
onFillConfig: (SshConfig) -> Unit = {}
|
||||
onFillConfig: (SshConfig) -> Unit = {},
|
||||
initialConfig: SshConfig? = null
|
||||
) {
|
||||
var host by remember { mutableStateOf("") }
|
||||
var port by remember { mutableStateOf("22") }
|
||||
var username by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
var displayName by remember { mutableStateOf("") }
|
||||
var authMethod by remember { mutableStateOf("password") }
|
||||
var host by remember { mutableStateOf(initialConfig?.host ?: "") }
|
||||
var port by remember { mutableStateOf((initialConfig?.port ?: 22).toString()) }
|
||||
var username by remember { mutableStateOf(initialConfig?.username ?: "") }
|
||||
var password by remember { mutableStateOf(initialConfig?.password ?: "") }
|
||||
var displayName by remember { mutableStateOf(initialConfig?.displayName ?: "") }
|
||||
var authMethod by remember { mutableStateOf(initialConfig?.authMode ?: "password") }
|
||||
var keyPassphrase by remember { mutableStateOf("") }
|
||||
var selectedKeyPath by remember { mutableStateOf("") }
|
||||
var selectedKeyPath by remember { mutableStateOf(initialConfig?.savedKeyPath ?: "") }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
var keyPassVisible by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -417,8 +418,7 @@ fun SshTerminalScreen(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val state by manager.connectionState.collectAsState()
|
||||
val inputState = remember { mutableStateOf(TextFieldValue("")) }
|
||||
var currentInput by inputState
|
||||
var currentInput by remember { mutableStateOf("") }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val listState = rememberLazyListState()
|
||||
var showFontPicker by remember { mutableStateOf(false) }
|
||||
@@ -437,7 +437,7 @@ fun SshTerminalScreen(
|
||||
}
|
||||
|
||||
// 获取当前输入的纯文本(用于显示)
|
||||
val currentInputText = currentInput.text
|
||||
val currentInputText = currentInput
|
||||
|
||||
// 硬件键盘处理
|
||||
fun handleKeyEvent(event: KeyEvent): Boolean {
|
||||
@@ -452,13 +452,13 @@ fun SshTerminalScreen(
|
||||
manager.sendCommand(cmd)
|
||||
}
|
||||
}
|
||||
currentInput = TextFieldValue("")
|
||||
currentInput = ""
|
||||
true
|
||||
}
|
||||
Key.Backspace -> {
|
||||
val t = currentInputText
|
||||
if (t.isNotEmpty()) {
|
||||
currentInput = TextFieldValue(t.substring(0, t.length - 1))
|
||||
currentInput = t.substring(0, t.length - 1)
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -488,19 +488,17 @@ fun SshTerminalScreen(
|
||||
) {
|
||||
// 透明输入框 — 极小的 1x30 dp,仅用来弹出软键盘
|
||||
BasicTextField(
|
||||
value = currentInput,
|
||||
value = TextFieldValue(currentInput),
|
||||
onValueChange = { newVal ->
|
||||
val t = newVal.text
|
||||
if (t.endsWith('\n')) {
|
||||
val cmd = t.dropLast(1).trim()
|
||||
currentInput = newVal.text
|
||||
if (newVal.text.endsWith('\n')) {
|
||||
val cmd = newVal.text.dropLast(1).trim()
|
||||
if (cmd.isNotEmpty()) {
|
||||
coroutineScope.launch {
|
||||
manager.sendCommand(cmd)
|
||||
}
|
||||
}
|
||||
currentInput = TextFieldValue("")
|
||||
} else {
|
||||
currentInput = newVal
|
||||
currentInput = ""
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 641 B |
|
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 641 B |
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 641 B |
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 158 B After Width: | Height: | Size: 158 B |
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 816 B |
|
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
|
Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 816 B |
|
Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 816 B |
|
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 435 B After Width: | Height: | Size: 435 B |
|
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 593 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |