v1.2.3: fix keyboard order, empty password, new logo
@@ -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
|
||||||
|
|||||||
|
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 |