This commit is contained in:
+2
-2
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "haus.nightmare.a3270"
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 5
|
||||
versionName "1.0.0"
|
||||
versionCode 6
|
||||
versionName "1.0.1"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -460,7 +460,12 @@ fun MainScreen(
|
||||
autoHost.tlsVerifyCert,
|
||||
autoHost.tn3270e,
|
||||
autoHost.graphicsMode,
|
||||
autoHost.codePage
|
||||
autoHost.codePage,
|
||||
autoHost.proxyType,
|
||||
autoHost.proxyHost,
|
||||
autoHost.proxyPort,
|
||||
autoHost.proxyUsername,
|
||||
autoHost.proxyPassword
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -638,9 +643,9 @@ fun MainScreen(
|
||||
if (showConnectDialog) {
|
||||
ConnectDialog(
|
||||
onDismiss = { showConnectDialog = false },
|
||||
onConnect = { host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp ->
|
||||
onConnect = { host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp, pType, pHost, pPort, pUser, pPass ->
|
||||
showConnectDialog = false
|
||||
viewModel.connect(host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp)
|
||||
viewModel.connect(host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp, pType, pHost, pPort, pUser, pPass)
|
||||
terminalInputViewRef?.showSoftKeyboard()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -396,7 +396,12 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
tlsVerifyCert: Boolean = true,
|
||||
tn3270e: Boolean = true,
|
||||
graphicsModeStr: String = "BOTH",
|
||||
codePageStr: String = "037"
|
||||
codePageStr: String = "037",
|
||||
proxyTypeStr: String = "NONE",
|
||||
proxyHost: String = "",
|
||||
proxyPort: Int = 0,
|
||||
proxyUser: String = "",
|
||||
proxyPass: String = ""
|
||||
) {
|
||||
currentHost = host
|
||||
activeHostType = hostType
|
||||
@@ -447,6 +452,16 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
isTn3270eEnabled = effectiveTn3270e
|
||||
graphicsMode = gMode
|
||||
codePage = codePageStr
|
||||
|
||||
val pType = try {
|
||||
ConnectionConfig.ProxyType.valueOf(proxyTypeStr.uppercase())
|
||||
} catch (_: Exception) {
|
||||
ConnectionConfig.ProxyType.NONE
|
||||
}
|
||||
if (pType != ConnectionConfig.ProxyType.NONE && proxyHost.isNotBlank()) {
|
||||
val defaultPort = if (pType == ConnectionConfig.ProxyType.HTTP) 8080 else 1080
|
||||
setProxy(pType, proxyHost.trim(), if (proxyPort > 0) proxyPort else defaultPort, proxyUser.ifBlank { null }, proxyPass.ifBlank { null })
|
||||
}
|
||||
}
|
||||
|
||||
config.certificateVerifier = haus.nightmare.lib3270j.tls.TlsCertificateVerifier { chain, _, exception ->
|
||||
|
||||
@@ -19,7 +19,12 @@ data class SavedHost(
|
||||
val tlsVerifyCert: Boolean = true,
|
||||
val tn3270e: Boolean = true,
|
||||
val graphicsMode: String = "BOTH",
|
||||
val codePage: String = "037"
|
||||
val codePage: String = "037",
|
||||
val proxyType: String = "NONE",
|
||||
val proxyHost: String = "",
|
||||
val proxyPort: Int = 0,
|
||||
val proxyUsername: String = "",
|
||||
val proxyPassword: String = ""
|
||||
) {
|
||||
fun toJson(): JSONObject {
|
||||
return JSONObject().apply {
|
||||
@@ -36,6 +41,11 @@ data class SavedHost(
|
||||
put("tn3270e", tn3270e)
|
||||
put("graphicsMode", graphicsMode)
|
||||
put("codePage", codePage)
|
||||
put("proxyType", proxyType)
|
||||
put("proxyHost", proxyHost)
|
||||
put("proxyPort", proxyPort)
|
||||
put("proxyUsername", proxyUsername)
|
||||
put("proxyPassword", proxyPassword)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +64,12 @@ data class SavedHost(
|
||||
tlsVerifyCert = json.optBoolean("tlsVerifyCert", true),
|
||||
tn3270e = json.optBoolean("tn3270e", true),
|
||||
graphicsMode = json.optString("graphicsMode", "BOTH"),
|
||||
codePage = json.optString("codePage", "037")
|
||||
codePage = json.optString("codePage", "037"),
|
||||
proxyType = json.optString("proxyType", "NONE"),
|
||||
proxyHost = json.optString("proxyHost", ""),
|
||||
proxyPort = json.optInt("proxyPort", 0),
|
||||
proxyUsername = json.optString("proxyUsername", ""),
|
||||
proxyPassword = json.optString("proxyPassword", "")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,12 @@ fun ConnectDialog(
|
||||
tlsVerifyCert: Boolean,
|
||||
tn3270e: Boolean,
|
||||
graphicsMode: String,
|
||||
codePage: String
|
||||
codePage: String,
|
||||
proxyType: String,
|
||||
proxyHost: String,
|
||||
proxyPort: Int,
|
||||
proxyUser: String,
|
||||
proxyPass: String
|
||||
) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -67,6 +72,12 @@ fun ConnectDialog(
|
||||
var tn3270e by remember { mutableStateOf(true) }
|
||||
var graphicsMode by remember { mutableStateOf("BOTH") }
|
||||
var codePage by remember { mutableStateOf(haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)) }
|
||||
var proxyType by remember { mutableStateOf("NONE") }
|
||||
var proxyHost by remember { mutableStateOf("") }
|
||||
var proxyPortStr by remember { mutableStateOf("") }
|
||||
var proxyUsername by remember { mutableStateOf("") }
|
||||
var proxyPassword by remember { mutableStateOf("") }
|
||||
var showProxySettings by remember { mutableStateOf(false) }
|
||||
|
||||
fun loadProfile(saved: SavedHost) {
|
||||
selectedHostId = saved.id
|
||||
@@ -82,6 +93,12 @@ fun ConnectDialog(
|
||||
tn3270e = saved.tn3270e
|
||||
graphicsMode = saved.graphicsMode
|
||||
codePage = saved.codePage
|
||||
proxyType = saved.proxyType
|
||||
proxyHost = saved.proxyHost
|
||||
proxyPortStr = if (saved.proxyPort > 0) saved.proxyPort.toString() else ""
|
||||
proxyUsername = saved.proxyUsername
|
||||
proxyPassword = saved.proxyPassword
|
||||
showProxySettings = saved.proxyType != "NONE"
|
||||
}
|
||||
|
||||
fun clearFields() {
|
||||
@@ -98,6 +115,12 @@ fun ConnectDialog(
|
||||
tn3270e = true
|
||||
graphicsMode = "BOTH"
|
||||
codePage = haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)
|
||||
proxyType = "NONE"
|
||||
proxyHost = ""
|
||||
proxyPortStr = ""
|
||||
proxyUsername = ""
|
||||
proxyPassword = ""
|
||||
showProxySettings = false
|
||||
}
|
||||
|
||||
fun saveCurrentProfile(): SavedHost? {
|
||||
@@ -118,7 +141,12 @@ fun ConnectDialog(
|
||||
tlsVerifyCert = tlsVerifyCert,
|
||||
tn3270e = tn3270e,
|
||||
graphicsMode = graphicsMode,
|
||||
codePage = codePage
|
||||
codePage = codePage,
|
||||
proxyType = proxyType,
|
||||
proxyHost = proxyHost.trim(),
|
||||
proxyPort = proxyPortStr.trim().toIntOrNull() ?: 0,
|
||||
proxyUsername = proxyUsername.trim(),
|
||||
proxyPassword = proxyPassword.trim()
|
||||
)
|
||||
HostStorage.saveHost(context, hostToSave)
|
||||
savedHosts = HostStorage.getSavedHosts(context)
|
||||
@@ -251,7 +279,12 @@ fun ConnectDialog(
|
||||
hostToConn.tlsVerifyCert,
|
||||
hostToConn.tn3270e,
|
||||
hostToConn.graphicsMode,
|
||||
hostToConn.codePage
|
||||
hostToConn.codePage,
|
||||
hostToConn.proxyType,
|
||||
hostToConn.proxyHost,
|
||||
hostToConn.proxyPort,
|
||||
hostToConn.proxyUsername,
|
||||
hostToConn.proxyPassword
|
||||
)
|
||||
},
|
||||
modifier = Modifier.size(32.dp)
|
||||
@@ -611,6 +644,102 @@ fun ConnectDialog(
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Auto-connect on app startup", fontSize = 12.sp, color = Color.White)
|
||||
}
|
||||
|
||||
// Proxy Settings Expandable Section
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showProxySettings = !showProxySettings }
|
||||
) {
|
||||
Checkbox(
|
||||
checked = showProxySettings || proxyType != "NONE",
|
||||
onCheckedChange = { checked ->
|
||||
showProxySettings = checked
|
||||
if (!checked) proxyType = "NONE"
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = if (proxyType != "NONE") "Proxy Active ($proxyType)" else "Configure Network Proxy",
|
||||
fontSize = 12.sp,
|
||||
color = if (proxyType != "NONE") Color(0xFF339AF0) else Color.White
|
||||
)
|
||||
}
|
||||
|
||||
if (showProxySettings || proxyType != "NONE") {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(containerColor = Color(0xFF25262B)),
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 8.dp, end = 4.dp, top = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
Text("Proxy Protocol:", fontSize = 11.sp, color = Color.Gray)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
listOf("NONE", "HTTP", "SOCKS4", "SOCKS5").forEach { pType ->
|
||||
val isSel = proxyType == pType
|
||||
FilterChip(
|
||||
selected = isSel,
|
||||
onClick = { proxyType = pType },
|
||||
label = { Text(pType, fontSize = 10.sp) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (proxyType != "NONE") {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = proxyHost,
|
||||
onValueChange = { proxyHost = it },
|
||||
label = { Text("Proxy Host") },
|
||||
singleLine = true,
|
||||
keyboardOptions = TerminalKeyboardOptions,
|
||||
modifier = Modifier.weight(0.7f)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = proxyPortStr,
|
||||
onValueChange = { proxyPortStr = it },
|
||||
label = { Text("Port") },
|
||||
placeholder = { Text(if (proxyType == "HTTP") "8080" else "1080") },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.weight(0.3f)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = proxyUsername,
|
||||
onValueChange = { proxyUsername = it },
|
||||
label = { Text("User (Opt)") },
|
||||
singleLine = true,
|
||||
keyboardOptions = TerminalKeyboardOptions,
|
||||
modifier = Modifier.weight(0.5f)
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = proxyPassword,
|
||||
onValueChange = { proxyPassword = it },
|
||||
label = { Text("Pass (Opt)") },
|
||||
singleLine = true,
|
||||
keyboardOptions = TerminalKeyboardOptions,
|
||||
modifier = Modifier.weight(0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -645,7 +774,12 @@ fun ConnectDialog(
|
||||
saved.tlsVerifyCert,
|
||||
saved.tn3270e,
|
||||
saved.graphicsMode,
|
||||
saved.codePage
|
||||
saved.codePage,
|
||||
saved.proxyType,
|
||||
saved.proxyHost,
|
||||
saved.proxyPort,
|
||||
saved.proxyUsername,
|
||||
saved.proxyPassword
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -85,7 +85,8 @@ fun ScriptDialog(
|
||||
) {
|
||||
listOf(
|
||||
"[enter]", "[tab]", "[backtab]", "[home]", "[clear]",
|
||||
"[reset]", "[eraseeof]", "[eraseinpt]", "[attn]", "[sysreq]"
|
||||
"[reset]", "[eraseeof]", "[eraseinpt]", "[attn]", "[sysreq]",
|
||||
"[wordleft]", "[wordright]", "[fieldend]", "[deleteword]", "[cursel]", "[lightpen]"
|
||||
).forEach { tok ->
|
||||
SuggestionChip(
|
||||
onClick = { insertToken(tok) },
|
||||
|
||||
@@ -26,13 +26,23 @@ class CodePageAndStorageTest {
|
||||
tlsVerifyCert = false,
|
||||
tn3270e = true,
|
||||
graphicsMode = "BOTH",
|
||||
codePage = "1047"
|
||||
codePage = "1047",
|
||||
proxyType = "SOCKS5",
|
||||
proxyHost = "proxy.example.com",
|
||||
proxyPort = 1080,
|
||||
proxyUsername = "testuser",
|
||||
proxyPassword = "secretpassword"
|
||||
)
|
||||
|
||||
val json = host.toJson()
|
||||
assertEquals("1047", json.getString("codePage"))
|
||||
assertEquals("Test Mainframe", json.getString("name"))
|
||||
assertTrue(json.getBoolean("autoConnect"))
|
||||
assertEquals("SOCKS5", json.getString("proxyType"))
|
||||
assertEquals("proxy.example.com", json.getString("proxyHost"))
|
||||
assertEquals(1080, json.getInt("proxyPort"))
|
||||
assertEquals("testuser", json.getString("proxyUsername"))
|
||||
assertEquals("secretpassword", json.getString("proxyPassword"))
|
||||
|
||||
val deserialized = SavedHost.fromJson(json)
|
||||
assertEquals(host.id, deserialized.id)
|
||||
@@ -42,17 +52,25 @@ class CodePageAndStorageTest {
|
||||
assertEquals(host.model, deserialized.model)
|
||||
assertEquals(host.codePage, deserialized.codePage)
|
||||
assertEquals(host.graphicsMode, deserialized.graphicsMode)
|
||||
assertEquals(host.proxyType, deserialized.proxyType)
|
||||
assertEquals(host.proxyHost, deserialized.proxyHost)
|
||||
assertEquals(host.proxyPort, deserialized.proxyPort)
|
||||
assertEquals(host.proxyUsername, deserialized.proxyUsername)
|
||||
assertEquals(host.proxyPassword, deserialized.proxyPassword)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSavedHostDefaultCodePageFallback() {
|
||||
// Old JSON without codePage field
|
||||
// Old JSON without codePage or proxy fields
|
||||
val json = org.json.JSONObject().apply {
|
||||
put("name", "Legacy")
|
||||
put("host", "mvs.local")
|
||||
}
|
||||
val deserialized = SavedHost.fromJson(json)
|
||||
assertEquals("037", deserialized.codePage)
|
||||
assertEquals("NONE", deserialized.proxyType)
|
||||
assertEquals("", deserialized.proxyHost)
|
||||
assertEquals(0, deserialized.proxyPort)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user