diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index 59b312ff190..43c58ef3210 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -1009,134 +1009,6 @@ "surface": "android", "id": "native.android.73e6ab1a168fd381" }, - { - "kind": "conditional-branch", - "line": 392, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Last gateway error", - "surface": "android", - "id": "native.android.f1c67abfbe3ec6e0" - }, - { - "kind": "conditional-branch", - "line": 392, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Pairing required", - "surface": "android", - "id": "native.android.6c4cd299250df476" - }, - { - "kind": "ui-call", - "line": 403, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "OpenClaw Android ${openClawAndroidVersionLabel()}", - "surface": "android", - "id": "native.android.d4cf912850bd01fa" - }, - { - "kind": "ui-call", - "line": 444, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Setup code, endpoint, TLS, token, password, onboarding.", - "surface": "android", - "id": "native.android.cbff50e93346f3c5" - }, - { - "kind": "conditional-branch", - "line": 448, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Collapse advanced controls", - "surface": "android", - "id": "native.android.207b536e12b5360e" - }, - { - "kind": "conditional-branch", - "line": 448, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Expand advanced controls", - "surface": "android", - "id": "native.android.03c5bbc5a9f0b75e" - }, - { - "kind": "ui-call", - "line": 479, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Run these on the gateway host:", - "surface": "android", - "id": "native.android.bcc45347b4e15232" - }, - { - "kind": "ui-call", - "line": 482, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "For Tailscale or public hosts, use wss:// or Tailscale Serve. Private LAN ws:// remains supported.", - "surface": "android", - "id": "native.android.8b607ce7ba3ac4d3" - }, - { - "kind": "ui-named-argument", - "line": 511, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Android Emulator", - "surface": "android", - "id": "native.android.e23c1c9a2c8391f9" - }, - { - "kind": "ui-named-argument", - "line": 520, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Localhost", - "surface": "android", - "id": "native.android.b28ff3f4d4bd3830" - }, - { - "kind": "conditional-branch", - "line": 547, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Port", - "surface": "android", - "id": "native.android.8eb9efc461827277" - }, - { - "kind": "conditional-branch", - "line": 547, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Port (optional, defaults to 443)", - "surface": "android", - "id": "native.android.e1ad7cf446f7d370" - }, - { - "kind": "ui-call", - "line": 573, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.", - "surface": "android", - "id": "native.android.bdb650918c75aa9b" - }, - { - "kind": "ui-call", - "line": 599, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Leave blank to keep saved token", - "surface": "android", - "id": "native.android.d56dcd9606f37263" - }, - { - "kind": "ui-call", - "line": 608, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "Password (optional)", - "surface": "android", - "id": "native.android.8123f2cfbe415437" - }, - { - "kind": "ui-call", - "line": 710, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt", - "source": "CF-Access-Client-Id", - "surface": "android", - "id": "native.android.4ddaa88cd4939df2" - }, { "kind": "ui-named-argument", "line": 52, @@ -5569,142 +5441,6 @@ "surface": "android", "id": "native.android.b10a51fe9d4b8d2d" }, - { - "kind": "ui-call", - "line": 178, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Tap mic or Talk", - "surface": "android", - "id": "native.android.aadc50cd77652b2d" - }, - { - "kind": "ui-call", - "line": 183, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Mic sends turns; Talk keeps the conversation open.", - "surface": "android", - "id": "native.android.63dcad83fd6c28dd" - }, - { - "kind": "conditional-branch", - "line": 243, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Mute speaker", - "surface": "android", - "id": "native.android.b6bbe61ec94523ae" - }, - { - "kind": "conditional-branch", - "line": 243, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Unmute speaker", - "surface": "android", - "id": "native.android.e62290e2a1e6f464" - }, - { - "kind": "conditional-branch", - "line": 249, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Muted", - "surface": "android", - "id": "native.android.428e2bd904f67872" - }, - { - "kind": "conditional-branch", - "line": 249, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Speaker", - "surface": "android", - "id": "native.android.723510d6f6b85cea" - }, - { - "kind": "conditional-branch", - "line": 306, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Turn microphone off", - "surface": "android", - "id": "native.android.a018a5ecea3cda3a" - }, - { - "kind": "conditional-branch", - "line": 306, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Turn microphone on", - "surface": "android", - "id": "native.android.5512f9eb8f18274f" - }, - { - "kind": "conditional-branch", - "line": 334, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Turn Talk Mode off", - "surface": "android", - "id": "native.android.4e7af9bb2202c677" - }, - { - "kind": "conditional-branch", - "line": 334, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Turn Talk Mode on", - "surface": "android", - "id": "native.android.195abf15cebfbf9a" - }, - { - "kind": "conditional-branch", - "line": 341, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Talk", - "surface": "android", - "id": "native.android.774e74150a1d9bae" - }, - { - "kind": "conditional-branch", - "line": 341, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Talk on", - "surface": "android", - "id": "native.android.d6cd8b5669156542" - }, - { - "kind": "ui-call", - "line": 371, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "$gatewayStatus · $stateText", - "surface": "android", - "id": "native.android.959507fd4a6ba3a4" - }, - { - "kind": "ui-call", - "line": 401, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "Open settings", - "surface": "android", - "id": "native.android.b0cd6db035f9049f" - }, - { - "kind": "conditional-branch", - "line": 437, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "OpenClaw", - "surface": "android", - "id": "native.android.dd372d2915032feb" - }, - { - "kind": "conditional-branch", - "line": 437, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "You", - "surface": "android", - "id": "native.android.5ec494944bda138e" - }, - { - "kind": "ui-call", - "line": 469, - "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt", - "source": "OpenClaw is thinking…", - "surface": "android", - "id": "native.android.09137c880dd8890e" - }, { "kind": "ui-named-argument", "line": 176, @@ -6749,37 +6485,13 @@ "kind": "resource-string", "line": 3, "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Gateway Connection", - "surface": "android", - "id": "native.android.55da01a287502809" - }, - { - "kind": "resource-string", - "line": 4, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Connect Gateway", - "surface": "android", - "id": "native.android.edb177f35510f636" - }, - { - "kind": "resource-string", - "line": 5, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Disconnect", - "surface": "android", - "id": "native.android.ed2b7c53127062b1" - }, - { - "kind": "resource-string", - "line": 6, - "path": "apps/android/app/src/main/res/values/strings.xml", "source": "Trust this gateway?", "surface": "android", "id": "native.android.cd32b06480d9bb69" }, { "kind": "resource-string", - "line": 7, + "line": 4, "path": "apps/android/app/src/main/res/values/strings.xml", "source": "Trust and continue", "surface": "android", @@ -6787,7 +6499,7 @@ }, { "kind": "resource-string", - "line": 8, + "line": 5, "path": "apps/android/app/src/main/res/values/strings.xml", "source": "Cancel", "surface": "android", @@ -6795,7 +6507,7 @@ }, { "kind": "resource-string", - "line": 9, + "line": 6, "path": "apps/android/app/src/main/res/values/strings.xml", "source": "New chat in worktree", "surface": "android", @@ -6803,183 +6515,7 @@ }, { "kind": "resource-string", - "line": 10, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Endpoint", - "surface": "android", - "id": "native.android.c12edfa8fda16801" - }, - { - "kind": "resource-string", - "line": 11, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Status", - "surface": "android", - "id": "native.android.75efde06ba05f289" - }, - { - "kind": "resource-string", - "line": 12, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Your gateway is active and ready.", - "surface": "android", - "id": "native.android.3fa18ce9f5965142" - }, - { - "kind": "resource-string", - "line": 13, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Connect to your gateway to get started.", - "surface": "android", - "id": "native.android.df305a8c797e5600" - }, - { - "kind": "resource-string", - "line": 14, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Copy Report for Claw", - "surface": "android", - "id": "native.android.6b6a08e8d4eff2d2" - }, - { - "kind": "resource-string", - "line": 15, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Advanced controls", - "surface": "android", - "id": "native.android.8f1072da28d67483" - }, - { - "kind": "resource-string", - "line": 16, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Connection method", - "surface": "android", - "id": "native.android.a6739759bcab830c" - }, - { - "kind": "resource-string", - "line": 17, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Setup Code", - "surface": "android", - "id": "native.android.139b14a5e9980de6" - }, - { - "kind": "resource-string", - "line": 18, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Manual", - "surface": "android", - "id": "native.android.be082a5c0a39d06b" - }, - { - "kind": "resource-string", - "line": 19, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Paste setup code", - "surface": "android", - "id": "native.android.9bab58464c4fc9b0" - }, - { - "kind": "resource-string", - "line": 20, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Host", - "surface": "android", - "id": "native.android.f23d9864f1b619f7" - }, - { - "kind": "resource-string", - "line": 21, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Use TLS", - "surface": "android", - "id": "native.android.5677de0e2a1c0faf" - }, - { - "kind": "resource-string", - "line": 22, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Token (optional)", - "surface": "android", - "id": "native.android.9f17b363626f2516" - }, - { - "kind": "resource-string", - "line": 23, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Password", - "surface": "android", - "id": "native.android.005e23b33d333f6a" - }, - { - "kind": "resource-string", - "line": 24, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Custom headers (optional)", - "surface": "android", - "id": "native.android.43900769f2cf0458" - }, - { - "kind": "resource-string", - "line": 25, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Sent with every connection to this gateway, for example Cloudflare Access service token headers. Stored encrypted and applied on the next reconnect.", - "surface": "android", - "id": "native.android.6d42cb48a4a32331" - }, - { - "kind": "resource-string", - "line": 26, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Header value", - "surface": "android", - "id": "native.android.c4d4f0299965fe55" - }, - { - "kind": "resource-string", - "line": 27, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "This header is managed by the connection and cannot be overridden.", - "surface": "android", - "id": "native.android.123e497273b8586e" - }, - { - "kind": "resource-string", - "line": 28, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Add header", - "surface": "android", - "id": "native.android.94f0633a1fb099bc" - }, - { - "kind": "resource-string", - "line": 29, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Remove %1$s", - "surface": "android", - "id": "native.android.ff4c8c3b56827d8e" - }, - { - "kind": "resource-string", - "line": 30, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Run onboarding again", - "surface": "android", - "id": "native.android.a43f43c1338a6960" - }, - { - "kind": "resource-string", - "line": 31, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Resolved endpoint", - "surface": "android", - "id": "native.android.8a14b91132d358be" - }, - { - "kind": "resource-string", - "line": 32, + "line": 7, "path": "apps/android/app/src/main/res/values/strings.xml", "source": "Verify the certificate fingerprint before trusting this gateway.\n\n%1$s", "surface": "android", @@ -6987,36 +6523,12 @@ }, { "kind": "resource-string", - "line": 33, + "line": 8, "path": "apps/android/app/src/main/res/values/strings.xml", "source": "The gateway certificate changed. Continue only if you expected this.\n\nOld SHA-256:\n%1$s\n\nNew SHA-256:\n%2$s", "surface": "android", "id": "native.android.6ad2d3f084142bed" }, - { - "kind": "resource-string", - "line": 34, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Switch gateway", - "surface": "android", - "id": "native.android.7c49ec7fe94a9063" - }, - { - "kind": "resource-string", - "line": 35, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Select gateway", - "surface": "android", - "id": "native.android.9e0a677fd629a15f" - }, - { - "kind": "resource-string", - "line": 36, - "path": "apps/android/app/src/main/res/values/strings.xml", - "source": "Active gateway", - "surface": "android", - "id": "native.android.b0034ac5bbc6b25f" - }, { "kind": "plist-string", "line": 8, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/GatewayEventHandler.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/GatewayEventHandler.kt deleted file mode 100644 index 5f0af044bbb..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/GatewayEventHandler.kt +++ /dev/null @@ -1,78 +0,0 @@ -package ai.openclaw.app.node - -import ai.openclaw.app.SecurePrefs -import ai.openclaw.app.gateway.GatewaySession -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray - -/** - * Handles gateway-originated events that need to update local Android preferences. - */ -class GatewayEventHandler( - private val scope: CoroutineScope, - private val prefs: SecurePrefs, - private val json: Json, - private val operatorSession: GatewaySession, - private val isConnected: () -> Boolean, -) { - private var suppressWakeWordsSync = false - private var wakeWordsSyncJob: Job? = null - - /** Applies gateway wake words locally without echoing the same change back to the gateway. */ - fun applyWakeWordsFromGateway(words: List) { - suppressWakeWordsSync = true - prefs.setWakeWords(words) - suppressWakeWordsSync = false - } - - /** Debounces local wake-word edits before sending voicewake.set to the operator session. */ - fun scheduleWakeWordsSyncIfNeeded() { - if (suppressWakeWordsSync) return - if (!isConnected()) return - - val snapshot = prefs.wakeWords.value - wakeWordsSyncJob?.cancel() - wakeWordsSyncJob = - scope.launch { - delay(650) - val jsonList = snapshot.joinToString(separator = ",") { it.toJsonString() } - val params = """{"triggers":[$jsonList]}""" - try { - operatorSession.request("voicewake.set", params) - } catch (_: Throwable) { - // ignore - } - } - } - - /** Loads gateway wake words on connect so Android settings show server truth. */ - suspend fun refreshWakeWordsFromGateway() { - if (!isConnected()) return - try { - val res = operatorSession.request("voicewake.get", "{}") - val payload = json.parseToJsonElement(res).asObjectOrNull() ?: return - val array = payload["triggers"] as? JsonArray ?: return - val triggers = array.mapNotNull { it.asStringOrNull() } - applyWakeWordsFromGateway(triggers) - } catch (_: Throwable) { - // ignore - } - } - - /** Applies voicewake.changed event payloads emitted by the gateway. */ - fun handleVoiceWakeChangedEvent(payloadJson: String?) { - if (payloadJson.isNullOrBlank()) return - try { - val payload = json.parseToJsonElement(payloadJson).asObjectOrNull() ?: return - val array = payload["triggers"] as? JsonArray ?: return - val triggers = array.mapNotNull { it.asStringOrNull() } - applyWakeWordsFromGateway(triggers) - } catch (_: Throwable) { - // ignore - } - } -} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ChatSheet.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ChatSheet.kt deleted file mode 100644 index 7cf7ff3cb20..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ChatSheet.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ai.openclaw.app.ui - -import ai.openclaw.app.MainViewModel -import ai.openclaw.app.ui.chat.ChatSheetContent -import androidx.compose.runtime.Composable - -/** Keeps the public shell entry point stable while chat internals live under ui.chat. */ -@Composable -fun ChatSheet(viewModel: MainViewModel) { - ChatSheetContent(viewModel = viewModel) -} diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt deleted file mode 100644 index 7acc825d5a4..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt +++ /dev/null @@ -1,848 +0,0 @@ -package ai.openclaw.app.ui - -import ai.openclaw.app.GatewayConnectionProblem -import ai.openclaw.app.MainViewModel -import ai.openclaw.app.R -import ai.openclaw.app.gateway.GatewayCustomHeaders -import ai.openclaw.app.gateway.GatewayEndpoint -import ai.openclaw.app.ui.mobileCardSurface -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Cloud -import androidx.compose.material.icons.filled.ContentCopy -import androidx.compose.material.icons.filled.ExpandLess -import androidx.compose.material.icons.filled.ExpandMore -import androidx.compose.material.icons.filled.Link -import androidx.compose.material.icons.filled.PowerSettingsNew -import androidx.compose.material.icons.filled.SwapHoriz -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.unit.dp - -private enum class ConnectInputMode { - SetupCode, - Manual, -} - -/** Gateway connection screen for setup-code and manual endpoint pairing. */ -@Composable -fun ConnectTabScreen(viewModel: MainViewModel) { - val context = LocalContext.current - val gatewayConnectionDisplay by viewModel.gatewayConnectionDisplay.collectAsState() - val statusText = gatewayConnectionDisplay.statusText - val gatewayConnectionProblem = gatewayConnectionDisplay.problem - val isConnected = gatewayConnectionDisplay.isConnected - val remoteAddress by viewModel.remoteAddress.collectAsState() - val manualHost by viewModel.manualHost.collectAsState() - val manualPort by viewModel.manualPort.collectAsState() - val manualTls by viewModel.manualTls.collectAsState() - val manualEnabled by viewModel.manualEnabled.collectAsState() - val pendingTrust by viewModel.pendingGatewayTrust.collectAsState() - val pairedGateways by viewModel.pairedGateways.collectAsState() - val activeGatewayStableId by viewModel.activeGatewayStableId.collectAsState() - - var advancedOpen by rememberSaveable { mutableStateOf(false) } - var gatewaySwitcherOpen by remember { mutableStateOf(false) } - var inputMode by - remember(manualEnabled, manualHost) { - mutableStateOf( - if (manualEnabled || manualHost.isNotBlank()) { - ConnectInputMode.Manual - } else { - ConnectInputMode.SetupCode - }, - ) - } - var setupCode by rememberSaveable { mutableStateOf("") } - var manualHostInput by rememberSaveable { mutableStateOf(manualHost.ifBlank { "10.0.2.2" }) } - var manualPortInput by rememberSaveable { mutableStateOf(manualPort.toString()) } - var manualTlsInput by rememberSaveable { mutableStateOf(manualTls) } - var tokenInput by remember { mutableStateOf("") } - var passwordInput by rememberSaveable { mutableStateOf("") } - var validationText by rememberSaveable { mutableStateOf(null) } - - if (pendingTrust != null) { - val prompt = pendingTrust!! - AlertDialog( - onDismissRequest = { viewModel.declineGatewayTrustPrompt() }, - containerColor = mobileCardSurface, - title = { Text(stringResource(R.string.trust_this_gateway), style = mobileHeadline, color = mobileText) }, - text = { - val message = - if (prompt.previousFingerprintSha256.isNullOrBlank()) { - stringResource(R.string.gateway_trust_first_seen, prompt.fingerprintSha256) - } else { - stringResource( - R.string.gateway_trust_changed, - prompt.previousFingerprintSha256, - prompt.fingerprintSha256, - ) - } - Text( - message, - style = mobileCallout, - color = mobileText, - ) - }, - confirmButton = { - TextButton( - onClick = { viewModel.acceptGatewayTrustPrompt() }, - colors = ButtonDefaults.textButtonColors(contentColor = mobileAccent), - ) { - Text(stringResource(R.string.trust_and_continue)) - } - }, - dismissButton = { - TextButton( - onClick = { viewModel.declineGatewayTrustPrompt() }, - colors = ButtonDefaults.textButtonColors(contentColor = mobileTextSecondary), - ) { - Text(stringResource(R.string.cancel)) - } - }, - ) - } - - val setupResolvedEndpoint = remember(setupCode) { decodeGatewaySetupCode(setupCode)?.url?.let { parseGatewayEndpoint(it)?.displayUrl } } - val manualEndpointConfig = - remember(manualHostInput, manualPortInput, manualTlsInput) { - composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput)?.let(::parseGatewayEndpoint) - } - val manualResolvedEndpoint = manualEndpointConfig?.displayUrl - - val activeEndpoint = - remember(isConnected, remoteAddress, setupResolvedEndpoint, manualResolvedEndpoint, inputMode) { - when { - isConnected && !remoteAddress.isNullOrBlank() -> remoteAddress!! - inputMode == ConnectInputMode.SetupCode -> setupResolvedEndpoint ?: "Not set" - else -> manualResolvedEndpoint ?: "Not set" - } - } - - val showDiagnostics = !isConnected && (gatewayConnectionProblem != null || gatewayStatusHasDiagnostics(statusText)) - val pairingRequired = !isConnected && (gatewayConnectionProblem?.isPairingRequired == true || gatewayStatusLooksLikePairing(statusText)) - val pairingInstruction = gatewayPairingInstruction(gatewayConnectionProblem) - val statusLabel = gatewayStatusForDisplay(gatewayConnectionProblem?.message ?: statusText) - - Column( - modifier = Modifier.verticalScroll(rememberScrollState()).padding(horizontal = 20.dp, vertical = 16.dp), - verticalArrangement = Arrangement.spacedBy(14.dp), - ) { - Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { - Text(stringResource(R.string.gateway_connection), style = mobileTitle1, color = mobileText) - Text( - if (isConnected) { - stringResource(R.string.connected_gateway_ready) - } else { - stringResource(R.string.connect_gateway_get_started) - }, - style = mobileCallout, - color = mobileTextSecondary, - ) - } - - // Status cards in a unified card group - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileCardSurface, - border = BorderStroke(1.dp, mobileBorder), - ) { - Column { - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Surface( - shape = RoundedCornerShape(10.dp), - color = mobileAccentSoft, - ) { - Icon( - imageVector = Icons.Default.Link, - contentDescription = null, - modifier = Modifier.padding(8.dp).size(18.dp), - tint = mobileAccent, - ) - } - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text(stringResource(R.string.endpoint), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText) - } - } - HorizontalDivider(color = mobileBorder) - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Surface( - shape = RoundedCornerShape(10.dp), - color = if (isConnected) mobileSuccessSoft else mobileSurface, - ) { - Icon( - imageVector = Icons.Default.Cloud, - contentDescription = null, - modifier = Modifier.padding(8.dp).size(18.dp), - tint = if (isConnected) mobileSuccess else mobileTextTertiary, - ) - } - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text(stringResource(R.string.status), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - Text(statusText, style = mobileBody, color = if (isConnected) mobileSuccess else mobileText) - } - } - } - } - - if (pairedGateways.size > 1) { - Box(modifier = Modifier.fillMaxWidth()) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileCardSurface, - border = BorderStroke(1.dp, mobileBorder), - onClick = { gatewaySwitcherOpen = true }, - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp), - ) { - Icon(Icons.Default.SwapHoriz, contentDescription = null, tint = mobileAccent) - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.switch_gateway), style = mobileCaption1, color = mobileTextSecondary) - Text( - pairedGateways.firstOrNull { it.stableId == activeGatewayStableId }?.name - ?: stringResource(R.string.select_gateway), - style = mobileHeadline, - color = mobileText, - ) - } - Icon(Icons.Default.ExpandMore, contentDescription = null, tint = mobileTextSecondary) - } - } - DropdownMenu( - expanded = gatewaySwitcherOpen, - onDismissRequest = { gatewaySwitcherOpen = false }, - ) { - pairedGateways.forEach { entry -> - DropdownMenuItem( - text = { Text(entry.name) }, - leadingIcon = { - if (entry.stableId == activeGatewayStableId) { - Icon(Icons.Default.Check, contentDescription = stringResource(R.string.active_gateway)) - } - }, - onClick = { - gatewaySwitcherOpen = false - if (entry.stableId != activeGatewayStableId) { - viewModel.switchToGateway(entry.stableId) - } - }, - ) - } - } - } - } - - if (isConnected) { - // Outlined secondary button when connected — don't scream "danger" - Button( - onClick = { - viewModel.disconnect() - validationText = null - }, - modifier = Modifier.fillMaxWidth().height(48.dp), - shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = mobileCardSurface, - contentColor = mobileDanger, - ), - border = BorderStroke(1.dp, mobileDanger.copy(alpha = 0.4f)), - ) { - Icon(Icons.Default.PowerSettingsNew, contentDescription = null, modifier = Modifier.size(18.dp)) - Spacer(modifier = Modifier.width(8.dp)) - Text(stringResource(R.string.disconnect), style = mobileHeadline.copy(fontWeight = FontWeight.SemiBold)) - } - } else { - Button( - onClick = { - if (statusText.contains("operator offline", ignoreCase = true)) { - validationText = null - viewModel.refreshGatewayConnection() - return@Button - } - - val plan = - resolveGatewayConnectPlan( - useSetupCode = inputMode == ConnectInputMode.SetupCode, - setupCode = setupCode, - savedManualHost = manualHost, - savedManualPort = manualPort.toString(), - savedManualTls = manualTls, - manualHostInput = manualHostInput, - manualPortInput = manualPortInput, - manualTlsInput = manualTlsInput, - bootstrapTokenInput = "", - tokenInput = tokenInput, - passwordInput = passwordInput, - ) - - if (plan == null) { - validationText = - if (inputMode == ConnectInputMode.SetupCode) { - val parsedSetup = decodeGatewaySetupCode(setupCode) - if (parsedSetup == null) { - "Paste a valid setup code to connect." - } else { - val parsedGateway = parseGatewayEndpointResult(parsedSetup.url) - gatewayEndpointValidationMessage( - parsedGateway.error ?: GatewayEndpointValidationError.INVALID_URL, - GatewayEndpointInputSource.SETUP_CODE, - ) - } - } else { - val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput) - val parsedGateway = manualUrl?.let(::parseGatewayEndpointResult) - gatewayEndpointValidationMessage( - parsedGateway?.error ?: GatewayEndpointValidationError.INVALID_URL, - GatewayEndpointInputSource.MANUAL, - ) - } - return@Button - } - - validationText = null - viewModel.saveGatewayConfigAndConnect(plan) - tokenInput = "" - }, - modifier = Modifier.fillMaxWidth().height(52.dp), - shape = RoundedCornerShape(14.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = mobileAccent, - contentColor = Color.White, - ), - ) { - Text(stringResource(R.string.connect_gateway), style = mobileHeadline.copy(fontWeight = FontWeight.Bold)) - } - } - - if (showDiagnostics) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileWarningSoft, - border = BorderStroke(1.dp, mobileWarning.copy(alpha = 0.25f)), - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 14.dp), - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - Text(if (pairingRequired) "Pairing required" else "Last gateway error", style = mobileHeadline, color = mobileWarning) - Text(statusLabel, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText) - if (pairingRequired) { - Text( - pairingInstruction, - style = mobileCallout, - color = mobileTextSecondary, - ) - CommandBlock("openclaw devices list") - CommandBlock("openclaw devices approve ") - } - Text("OpenClaw Android ${openClawAndroidVersionLabel()}", style = mobileCaption1, color = mobileTextSecondary) - Button( - onClick = { - copyGatewayDiagnosticsReport( - context = context, - screen = "connect tab", - gatewayAddress = activeEndpoint, - statusText = statusLabel, - ) - }, - modifier = Modifier.fillMaxWidth().height(46.dp), - shape = RoundedCornerShape(12.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = mobileCardSurface, - contentColor = mobileWarning, - ), - border = BorderStroke(1.dp, mobileWarning.copy(alpha = 0.3f)), - ) { - Icon(Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(18.dp)) - Spacer(modifier = Modifier.width(8.dp)) - Text(stringResource(R.string.copy_report_for_claw), style = mobileCallout.copy(fontWeight = FontWeight.Bold)) - } - } - } - } - - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileSurface, - border = BorderStroke(1.dp, mobileBorder), - onClick = { advancedOpen = !advancedOpen }, - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text(stringResource(R.string.advanced_controls), style = mobileHeadline, color = mobileText) - Text("Setup code, endpoint, TLS, token, password, onboarding.", style = mobileCaption1, color = mobileTextSecondary) - } - Icon( - imageVector = if (advancedOpen) Icons.Default.ExpandLess else Icons.Default.ExpandMore, - contentDescription = if (advancedOpen) "Collapse advanced controls" else "Expand advanced controls", - tint = mobileTextSecondary, - ) - } - } - - AnimatedVisibility(visible = advancedOpen) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileCardSurface, - border = BorderStroke(1.dp, mobileBorder), - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 14.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text(stringResource(R.string.connection_method), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - MethodChip( - label = stringResource(R.string.setup_code), - active = inputMode == ConnectInputMode.SetupCode, - onClick = { inputMode = ConnectInputMode.SetupCode }, - ) - MethodChip( - label = stringResource(R.string.manual), - active = inputMode == ConnectInputMode.Manual, - onClick = { inputMode = ConnectInputMode.Manual }, - ) - } - - Text("Run these on the gateway host:", style = mobileCallout, color = mobileTextSecondary) - CommandBlock("openclaw qr --setup-code-only") - CommandBlock("openclaw qr --json") - Text( - "For Tailscale or public hosts, use wss:// or Tailscale Serve. Private LAN ws:// remains supported.", - style = mobileCaption1, - color = mobileTextSecondary, - ) - - if (inputMode == ConnectInputMode.SetupCode) { - Text(stringResource(R.string.setup_code), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - OutlinedTextField( - value = setupCode, - onValueChange = { - setupCode = it - validationText = null - }, - placeholder = { Text(stringResource(R.string.paste_setup_code), style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - minLines = 3, - maxLines = 5, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - textStyle = mobileBody.copy(fontFamily = FontFamily.Monospace, color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - if (!setupResolvedEndpoint.isNullOrBlank()) { - EndpointPreview(endpoint = setupResolvedEndpoint) - } - } else { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - QuickFillChip( - label = "Android Emulator", - onClick = { - manualHostInput = "10.0.2.2" - manualPortInput = "18789" - manualTlsInput = false - validationText = null - }, - ) - QuickFillChip( - label = "Localhost", - onClick = { - manualHostInput = "127.0.0.1" - manualPortInput = "18789" - manualTlsInput = false - validationText = null - }, - ) - } - - Text(stringResource(R.string.host), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - OutlinedTextField( - value = manualHostInput, - onValueChange = { - manualHostInput = it - validationText = null - }, - placeholder = { Text("10.0.2.2", style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri), - textStyle = mobileBody.copy(color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - - Text( - if (manualTlsInput) "Port (optional, defaults to 443)" else "Port", - style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), - color = mobileTextSecondary, - ) - OutlinedTextField( - value = manualPortInput, - onValueChange = { - manualPortInput = it - validationText = null - }, - placeholder = { Text(if (manualTlsInput) "443" else "18789", style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - textStyle = mobileBody.copy(fontFamily = FontFamily.Monospace, color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { - Text(stringResource(R.string.use_tls), style = mobileHeadline, color = mobileText) - Text( - "Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.", - style = mobileCallout, - color = mobileTextSecondary, - ) - } - Switch( - checked = manualTlsInput, - onCheckedChange = { - manualTlsInput = it - validationText = null - }, - colors = - SwitchDefaults.colors( - checkedTrackColor = mobileAccent, - uncheckedTrackColor = mobileBorderStrong, - checkedThumbColor = Color.White, - uncheckedThumbColor = Color.White, - ), - ) - } - - Text(stringResource(R.string.token_optional), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary) - OutlinedTextField( - value = tokenInput, - onValueChange = { tokenInput = it }, - placeholder = { Text("Leave blank to keep saved token", style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - textStyle = mobileBody.copy(color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - - Text( - "Password (optional)", - style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), - color = mobileTextSecondary, - ) - OutlinedTextField( - value = passwordInput, - onValueChange = { passwordInput = it }, - placeholder = { Text(stringResource(R.string.password), style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - textStyle = mobileBody.copy(color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - - if (!manualResolvedEndpoint.isNullOrBlank()) { - EndpointPreview(endpoint = manualResolvedEndpoint) - } - - // Proxy credentials are unavailable on cleartext ws:// routes, including pasted URLs - // whose explicit scheme overrides the TLS toggle. The transport enforces this too. - manualEndpointConfig?.takeIf { it.tls }?.let { endpoint -> - ManualGatewayCustomHeadersEditor( - viewModel = viewModel, - stableId = GatewayEndpoint.manual(host = endpoint.host, port = endpoint.port).stableId, - ) - } - } - - HorizontalDivider(color = mobileBorder) - - TextButton(onClick = { viewModel.setOnboardingCompleted(false) }) { - Text(stringResource(R.string.run_onboarding_again), style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent) - } - } - } - } - - if (!validationText.isNullOrBlank()) { - Text(validationText!!, style = mobileCaption1, color = mobileWarning) - } - } -} - -/** - * Per-gateway proxy credential headers for the manual endpoint (Cloudflare Access-style - * service tokens). Values are secrets: masked while typing, hidden once saved, stored - * encrypted, and applied on the next reconnect. - */ -@Composable -private fun ManualGatewayCustomHeadersEditor( - viewModel: MainViewModel, - stableId: String, -) { - var headers by remember(stableId) { mutableStateOf(viewModel.gatewayCustomHeaders(stableId)) } - var nameInput by rememberSaveable(stableId) { mutableStateOf("") } - // Not rememberSaveable: header values are credentials and must not land in saved instance state. - var valueInput by remember(stableId) { mutableStateOf("") } - - val trimmedName = nameInput.trim() - val nameIsReserved = GatewayCustomHeaders.isReservedName(trimmedName) - val nameIsDuplicate = headers.keys.any { it.equals(trimmedName, ignoreCase = true) } - - fun persist(updated: Map) { - viewModel.setGatewayCustomHeaders(stableId, updated) - headers = viewModel.gatewayCustomHeaders(stableId) - } - - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text( - stringResource(R.string.custom_headers_optional), - style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), - color = mobileTextSecondary, - ) - Text( - stringResource(R.string.custom_headers_explainer), - style = mobileCaption1, - color = mobileTextSecondary, - ) - - for (name in headers.keys.sorted()) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Text(name, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText) - IconButton(onClick = { persist(headers - name) }) { - Icon( - Icons.Default.Close, - contentDescription = stringResource(R.string.remove_custom_header, name), - tint = mobileTextSecondary, - ) - } - } - } - - OutlinedTextField( - value = nameInput, - onValueChange = { nameInput = it }, - placeholder = { Text("CF-Access-Client-Id", style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii), - textStyle = mobileBody.copy(fontFamily = FontFamily.Monospace, color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - OutlinedTextField( - value = valueInput, - onValueChange = { valueInput = it }, - placeholder = { Text(stringResource(R.string.custom_header_value), style = mobileBody, color = mobileTextTertiary) }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - visualTransformation = PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), - textStyle = mobileBody.copy(color = mobileText), - shape = RoundedCornerShape(14.dp), - colors = outlinedColors(), - ) - if (nameIsReserved) { - Text(stringResource(R.string.custom_header_reserved), style = mobileCaption1, color = mobileWarning) - } - Button( - onClick = { - persist(headers + (trimmedName to valueInput)) - nameInput = "" - valueInput = "" - }, - enabled = trimmedName.isNotEmpty() && !nameIsReserved && !nameIsDuplicate, - modifier = Modifier.height(40.dp), - shape = RoundedCornerShape(12.dp), - contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = mobileSurface, - contentColor = mobileText, - ), - border = BorderStroke(1.dp, mobileBorderStrong), - ) { - Text(stringResource(R.string.add_custom_header), style = mobileCaption1.copy(fontWeight = FontWeight.Bold)) - } - } -} - -private fun gatewayPairingInstruction(problem: GatewayConnectionProblem?): String = - if (problem?.canAutoRetry == true) { - "Approve this phone on the gateway. OpenClaw will reconnect automatically." - } else { - "Approve this phone on the gateway, then retry the connection." - } - -@Composable -private fun MethodChip( - label: String, - active: Boolean, - onClick: () -> Unit, -) { - Button( - onClick = onClick, - modifier = Modifier.height(40.dp), - shape = RoundedCornerShape(12.dp), - contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = if (active) mobileAccent else mobileSurface, - contentColor = if (active) Color.White else mobileText, - ), - border = BorderStroke(1.dp, if (active) mobileAccentBorderStrong else mobileBorderStrong), - ) { - Text(label, style = mobileCaption1.copy(fontWeight = FontWeight.Bold)) - } -} - -@Composable -private fun QuickFillChip( - label: String, - onClick: () -> Unit, -) { - Button( - onClick = onClick, - shape = RoundedCornerShape(999.dp), - contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = mobileAccentSoft, - contentColor = mobileAccent, - ), - elevation = null, - ) { - Text(label, style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold)) - } -} - -@Composable -private fun CommandBlock(command: String) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - color = mobileCodeBg, - border = BorderStroke(1.dp, mobileCodeBorder), - ) { - Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { - Box(modifier = Modifier.width(3.dp).height(42.dp).background(mobileCodeAccent)) - Text( - text = command, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), - style = mobileCallout.copy(fontFamily = FontFamily.Monospace), - color = mobileCodeText, - ) - } - } -} - -@Composable -private fun EndpointPreview(endpoint: String) { - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - HorizontalDivider(color = mobileBorder) - Text( - stringResource(R.string.resolved_endpoint), - style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), - color = mobileTextSecondary, - ) - Text(endpoint, style = mobileCallout.copy(fontFamily = FontFamily.Monospace), color = mobileText) - HorizontalDivider(color = mobileBorder) - } -} - -@Composable -private fun outlinedColors() = - OutlinedTextFieldDefaults.colors( - focusedContainerColor = mobileSurface, - unfocusedContainerColor = mobileSurface, - focusedBorderColor = mobileAccent, - unfocusedBorderColor = mobileBorder, - focusedTextColor = mobileText, - unfocusedTextColor = mobileText, - cursorColor = mobileAccent, - ) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt index 388577ec0f0..4258cf3a6f2 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt @@ -165,15 +165,6 @@ internal val mobileFontFamily = Font(resId = R.font.manrope_700_bold, weight = FontWeight.Bold), ) -internal val mobileDisplay = - TextStyle( - fontFamily = mobileFontFamily, - fontWeight = FontWeight.Bold, - fontSize = 34.sp, - lineHeight = 40.sp, - letterSpacing = (-0.8).sp, - ) - internal val mobileTitle1 = TextStyle( fontFamily = mobileFontFamily, @@ -183,15 +174,6 @@ internal val mobileTitle1 = letterSpacing = (-0.5).sp, ) -internal val mobileTitle2 = - TextStyle( - fontFamily = mobileFontFamily, - fontWeight = FontWeight.SemiBold, - fontSize = 20.sp, - lineHeight = 26.sp, - letterSpacing = (-0.3).sp, - ) - internal val mobileHeadline = TextStyle( fontFamily = mobileFontFamily, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt deleted file mode 100644 index c06760beb18..00000000000 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/VoiceTabScreen.kt +++ /dev/null @@ -1,528 +0,0 @@ -package ai.openclaw.app.ui - -import ai.openclaw.app.MainViewModel -import ai.openclaw.app.VoiceCaptureMode -import ai.openclaw.app.voice.VoiceConversationEntry -import ai.openclaw.app.voice.VoiceConversationRole -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.provider.Settings -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.VolumeOff -import androidx.compose.material.icons.automirrored.filled.VolumeUp -import androidx.compose.material.icons.filled.Mic -import androidx.compose.material.icons.filled.MicOff -import androidx.compose.material.icons.filled.RecordVoiceOver -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.compose.LocalLifecycleOwner -import kotlin.math.max - -/** - * Voice tab that switches between push-to-send mic capture and continuous Talk Mode. - */ -@Composable -fun VoiceTabScreen(viewModel: MainViewModel) { - val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - val activity = remember(context) { context.findActivity() } - val listState = rememberLazyListState() - - val gatewayStatus by viewModel.statusText.collectAsState() - val voiceCaptureMode by viewModel.voiceCaptureMode.collectAsState() - val micEnabled by viewModel.micEnabled.collectAsState() - val micCooldown by viewModel.micCooldown.collectAsState() - val speakerEnabled by viewModel.speakerEnabled.collectAsState() - val micStatusText by viewModel.micStatusText.collectAsState() - val micLiveTranscript by viewModel.micLiveTranscript.collectAsState() - val micQueuedMessages by viewModel.micQueuedMessages.collectAsState() - val micConversation by viewModel.micConversation.collectAsState() - val micInputLevel by viewModel.micInputLevel.collectAsState() - val micIsSending by viewModel.micIsSending.collectAsState() - val talkModeEnabled by viewModel.talkModeEnabled.collectAsState() - val talkModeListening by viewModel.talkModeListening.collectAsState() - val talkModeSpeaking by viewModel.talkModeSpeaking.collectAsState() - val talkModeConversation by viewModel.talkModeConversation.collectAsState() - - val activeConversation = if (voiceCaptureMode == VoiceCaptureMode.TalkMode) talkModeConversation else micConversation - val hasStreamingAssistant = activeConversation.any { it.role == VoiceConversationRole.Assistant && it.isStreaming } - val showThinkingBubble = micIsSending && !hasStreamingAssistant - - var hasMicPermission by remember { mutableStateOf(context.hasRecordAudioPermission()) } - var pendingVoicePermissionAction by remember { mutableStateOf(null) } - - DisposableEffect(lifecycleOwner, context) { - val observer = - LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_RESUME) { - hasMicPermission = context.hasRecordAudioPermission() - } - } - lifecycleOwner.lifecycle.addObserver(observer) - onDispose { - lifecycleOwner.lifecycle.removeObserver(observer) - // Manual mic is tab-scoped; Talk Mode is user-enabled and can continue elsewhere. - viewModel.setVoiceScreenActive(false) - } - } - - val requestMicPermission = - rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - hasMicPermission = granted - if (granted) { - when (pendingVoicePermissionAction) { - PendingVoicePermissionAction.ManualMic -> viewModel.setMicEnabled(true) - PendingVoicePermissionAction.TalkMode -> viewModel.setTalkModeEnabled(true) - null -> Unit - } - } - pendingVoicePermissionAction = null - } - - LaunchedEffect(voiceCaptureMode, activeConversation.size, showThinkingBubble) { - val total = activeConversation.size + if (showThinkingBubble) 1 else 0 - if (total > 0) { - listState.animateScrollToItem(total - 1) - } - } - - Column( - modifier = - Modifier - .fillMaxSize() - .background(mobileBackgroundGradient) - .imePadding() - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)) - .padding(horizontal = 20.dp, vertical = 14.dp), - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - LazyColumn( - state = listState, - modifier = Modifier.fillMaxWidth().weight(1f), - contentPadding = PaddingValues(vertical = 4.dp), - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - if (activeConversation.isEmpty() && !showThinkingBubble) { - item { - Box( - modifier = Modifier.fillParentMaxHeight().fillMaxWidth(), - contentAlignment = Alignment.Center, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - Icon( - imageVector = Icons.Default.Mic, - contentDescription = null, - modifier = Modifier.size(48.dp), - tint = mobileTextTertiary, - ) - Text( - "Tap mic or Talk", - style = mobileHeadline, - color = mobileTextSecondary, - ) - Text( - "Mic sends turns; Talk keeps the conversation open.", - style = mobileCallout, - color = mobileTextTertiary, - ) - } - } - } - } - - items(items = activeConversation, key = { it.id }) { entry -> - VoiceTurnBubble(entry = entry) - } - - if (showThinkingBubble) { - item { - VoiceThinkingBubble() - } - } - } - - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(6.dp), - ) { - if (!micLiveTranscript.isNullOrBlank()) { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(14.dp), - color = mobileAccentSoft, - border = BorderStroke(1.dp, mobileAccent.copy(alpha = 0.2f)), - ) { - Text( - micLiveTranscript!!.trim(), - modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), - style = mobileCallout, - color = mobileText, - ) - } - } - - // Mic button with input-reactive ring + speaker toggle - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - // Speaker toggle - Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) { - IconButton( - onClick = { viewModel.setSpeakerEnabled(!speakerEnabled) }, - modifier = Modifier.size(48.dp), - colors = - IconButtonDefaults.iconButtonColors( - containerColor = if (speakerEnabled) mobileSurface else mobileDangerSoft, - ), - ) { - Icon( - imageVector = if (speakerEnabled) Icons.AutoMirrored.Filled.VolumeUp else Icons.AutoMirrored.Filled.VolumeOff, - contentDescription = if (speakerEnabled) "Mute speaker" else "Unmute speaker", - modifier = Modifier.size(22.dp), - tint = if (speakerEnabled) mobileTextSecondary else mobileDanger, - ) - } - Text( - if (speakerEnabled) "Speaker" else "Muted", - style = mobileCaption2, - color = if (speakerEnabled) mobileTextTertiary else mobileDanger, - ) - } - - // Ring size = 68dp base + up to 22dp driven by mic input level. - // The outer Box is fixed at 90dp (max ring size) so the ring never shifts the button. - Box( - modifier = Modifier.padding(horizontal = 16.dp).size(90.dp), - contentAlignment = Alignment.Center, - ) { - if (micEnabled) { - val ringLevel = micInputLevel.coerceIn(0f, 1f) - val ringSize = 68.dp + (22.dp * max(ringLevel, 0.05f)) - Box( - modifier = - Modifier - .size(ringSize) - .background(mobileAccent.copy(alpha = 0.12f + 0.14f * ringLevel), CircleShape), - ) - } - Button( - onClick = { - if (micCooldown) return@Button - if (micEnabled) { - viewModel.setMicEnabled(false) - return@Button - } - if (hasMicPermission) { - viewModel.setMicEnabled(true) - } else { - pendingVoicePermissionAction = PendingVoicePermissionAction.ManualMic - requestMicPermission.launch(Manifest.permission.RECORD_AUDIO) - } - }, - enabled = !micCooldown, - shape = CircleShape, - contentPadding = PaddingValues(0.dp), - modifier = Modifier.size(60.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = - if (micCooldown) { - mobileTextSecondary - } else if (micEnabled) { - mobileDanger - } else { - mobileAccent - }, - contentColor = Color.White, - disabledContainerColor = mobileTextSecondary, - disabledContentColor = Color.White.copy(alpha = 0.5f), - ), - ) { - Icon( - imageVector = if (micEnabled) Icons.Default.MicOff else Icons.Default.Mic, - contentDescription = if (micEnabled) "Turn microphone off" else "Turn microphone on", - modifier = Modifier.size(24.dp), - ) - } - } - - Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) { - IconButton( - onClick = { - if (talkModeEnabled) { - viewModel.setTalkModeEnabled(false) - return@IconButton - } - if (hasMicPermission) { - viewModel.setTalkModeEnabled(true) - } else { - pendingVoicePermissionAction = PendingVoicePermissionAction.TalkMode - requestMicPermission.launch(Manifest.permission.RECORD_AUDIO) - } - }, - modifier = Modifier.size(48.dp), - colors = - IconButtonDefaults.iconButtonColors( - containerColor = if (talkModeEnabled) mobileSuccessSoft else mobileSurface, - ), - ) { - Icon( - imageVector = Icons.Default.RecordVoiceOver, - contentDescription = if (talkModeEnabled) "Turn Talk Mode off" else "Turn Talk Mode on", - modifier = Modifier.size(22.dp), - tint = if (talkModeEnabled) mobileSuccess else mobileTextSecondary, - ) - } - Spacer(modifier = Modifier.height(4.dp)) - Text( - if (talkModeEnabled) "Talk on" else "Talk", - style = mobileCaption2, - color = if (talkModeEnabled) mobileSuccess else mobileTextTertiary, - ) - } - } - - // Status + labels - val queueCount = micQueuedMessages.size - val stateText = - when { - voiceCaptureMode == VoiceCaptureMode.TalkMode && talkModeSpeaking -> "Talk speaking" - voiceCaptureMode == VoiceCaptureMode.TalkMode && talkModeListening -> "Talk listening" - voiceCaptureMode == VoiceCaptureMode.TalkMode -> "Talk on" - micEnabled || micIsSending || micCooldown -> micStatusText - queueCount > 0 -> "$queueCount queued" - else -> "Mic off" - } - val stateColor = - when { - voiceCaptureMode == VoiceCaptureMode.TalkMode -> mobileSuccess - micEnabled -> mobileSuccess - micIsSending -> mobileAccent - else -> mobileTextSecondary - } - Surface( - shape = RoundedCornerShape(999.dp), - color = if (micEnabled || talkModeEnabled) mobileSuccessSoft else mobileSurface, - border = BorderStroke(1.dp, if (micEnabled || talkModeEnabled) mobileSuccess.copy(alpha = 0.3f) else mobileBorder), - ) { - Text( - "$gatewayStatus · $stateText", - style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), - color = stateColor, - modifier = Modifier.padding(horizontal = 14.dp, vertical = 6.dp), - ) - } - - if (!hasMicPermission) { - val showRationale = - if (activity == null) { - false - } else { - ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.RECORD_AUDIO) - } - Text( - if (showRationale) { - "Microphone permission is required for voice mode." - } else { - "Microphone blocked. Open app settings to enable it." - }, - style = mobileCaption1, - color = mobileWarning, - textAlign = TextAlign.Center, - ) - Button( - onClick = { openAppSettings(context) }, - shape = RoundedCornerShape(12.dp), - colors = ButtonDefaults.buttonColors(containerColor = mobileSurfaceStrong, contentColor = mobileText), - ) { - Text("Open settings", style = mobileCallout.copy(fontWeight = FontWeight.SemiBold)) - } - } - } - } -} - -/** - * Permission continuation captured before Android's runtime permission dialog suspends the action. - */ -private enum class PendingVoicePermissionAction { - ManualMic, - TalkMode, -} - -/** - * Renders one transcript turn, preserving side and color by speaker role. - */ -@Composable -private fun VoiceTurnBubble(entry: VoiceConversationEntry) { - val isUser = entry.role == VoiceConversationRole.User - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = if (isUser) Arrangement.End else Arrangement.Start, - ) { - Surface( - modifier = Modifier.fillMaxWidth(0.90f), - shape = RoundedCornerShape(12.dp), - color = if (isUser) mobileAccentSoft else mobileCardSurface, - border = BorderStroke(1.dp, if (isUser) mobileAccent else mobileBorderStrong), - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(horizontal = 11.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(3.dp), - ) { - Text( - if (isUser) "You" else "OpenClaw", - style = mobileCaption2.copy(fontWeight = FontWeight.SemiBold, letterSpacing = 0.6.sp), - color = if (isUser) mobileAccent else mobileTextSecondary, - ) - Text( - if (entry.isStreaming && entry.text.isBlank()) "Listening response…" else entry.text, - style = mobileCallout, - color = mobileText, - ) - } - } - } -} - -/** - * Placeholder assistant turn shown while a manual mic request is queued but not streaming yet. - */ -@Composable -private fun VoiceThinkingBubble() { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start) { - Surface( - modifier = Modifier.fillMaxWidth(0.68f), - shape = RoundedCornerShape(12.dp), - color = mobileCardSurface, - border = BorderStroke(1.dp, mobileBorderStrong), - ) { - Row( - modifier = Modifier.padding(horizontal = 11.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - ThinkingDots(color = mobileTextSecondary) - Text("OpenClaw is thinking…", style = mobileCallout, color = mobileTextSecondary) - } - } - } -} - -/** - * Static dot cluster used by VoiceThinkingBubble to avoid starting another animation loop. - */ -@Composable -private fun ThinkingDots(color: Color) { - Row(horizontalArrangement = Arrangement.spacedBy(5.dp), verticalAlignment = Alignment.CenterVertically) { - ThinkingDot(alpha = 0.38f, color = color) - ThinkingDot(alpha = 0.62f, color = color) - ThinkingDot(alpha = 0.90f, color = color) - } -} - -@Composable -private fun ThinkingDot( - alpha: Float, - color: Color, -) { - Surface( - modifier = Modifier.size(6.dp).alpha(alpha), - shape = CircleShape, - color = color, - ) {} -} - -/** - * Checks RECORD_AUDIO using ContextCompat so wrapped activity contexts behave the same. - */ -private fun Context.hasRecordAudioPermission(): Boolean = - ( - ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED - ) - -/** - * Walks ContextWrappers until an Activity is found for permission rationale checks. - */ -private fun Context.findActivity(): Activity? = - when (this) { - is Activity -> this - is ContextWrapper -> baseContext.findActivity() - else -> null - } - -/** - * Opens this app's settings page after Android reports the mic permission as blocked. - */ -private fun openAppSettings(context: Context) { - val intent = - Intent( - Settings.ACTION_APPLICATION_DETAILS_SETTINGS, - Uri.fromParts("package", context.packageName, null), - ) - context.startActivity(intent) -} diff --git a/apps/android/app/src/main/res/values-ar/strings.xml b/apps/android/app/src/main/res/values-ar/strings.xml index befd7d40c79..8431e637653 100644 --- a/apps/android/app/src/main/res/values-ar/strings.xml +++ b/apps/android/app/src/main/res/values-ar/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - اتصال البوابة - توصيل البوابة - قطع الاتصال هل تثق بهذه البوابة؟ الثقة والمتابعة إلغاء دردشة جديدة في worktree - نقطة النهاية - الحالة - بوابتك نشطة وجاهزة. - اتصل ببوابتك للبدء. - نسخ التقرير لـ Claw - عناصر التحكم المتقدمة - طريقة الاتصال - رمز الإعداد - يدوي - الصق رمز الإعداد - المضيف - استخدام TLS - الرمز المميز (اختياري) - كلمة المرور - رؤوس مخصصة (اختياري) - تُرسل مع كل اتصال بهذا الـ gateway، على سبيل المثال رؤوس رمز خدمة Cloudflare Access. تُخزَّن مشفّرة وتُطبَّق عند إعادة الاتصال التالية. - قيمة الرأس - تتم إدارة هذا الرأس بواسطة الاتصال ولا يمكن تجاوزه. - إضافة رأس - إزالة %1$s - تشغيل الإعداد الأولي مرة أخرى - نقطة النهاية التي تم حلها تحقق من بصمة الشهادة قبل الوثوق بهذه البوابة.\n\n%1$s تم تغيير شهادة البوابة. تابع فقط إذا كنت تتوقع هذا التغيير.\n\nSHA-256 القديم:\n%1$s\n\nSHA-256 الجديد:\n%2$s - تبديل Gateway - تحديد Gateway - Gateway النشط diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index 4efa891b575..0e79f40a95b 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Gateway-Verbindung - Gateway verbinden - Trennen Diesem Gateway vertrauen? Vertrauen und fortfahren Abbrechen Neuer Chat im Worktree - Endpunkt - Status - Ihr Gateway ist aktiv und bereit. - Verbinden Sie sich mit Ihrem Gateway, um loszulegen. - Bericht für Claw kopieren - Erweiterte Steuerungen - Verbindungsmethode - Einrichtungscode - Manuell - Einrichtungscode einfügen - Host - TLS verwenden - Token (optional) - Passwort - Benutzerdefinierte Header (optional) - Wird bei jeder Verbindung zu diesem Gateway gesendet, zum Beispiel Cloudflare Access-Diensttoken-Header. Wird verschlüsselt gespeichert und beim nächsten erneuten Verbindungsaufbau angewendet. - Header-Wert - Dieser Header wird von der Verbindung verwaltet und kann nicht überschrieben werden. - Header hinzufügen - %1$s entfernen - Onboarding erneut ausführen - Aufgelöster Endpunkt Überprüfen Sie den Zertifikatfingerabdruck, bevor Sie diesem Gateway vertrauen.\n\n%1$s Das Gateway-Zertifikat wurde geändert. Fahren Sie nur fort, wenn Sie diese Änderung erwartet haben.\n\nAlter SHA-256-Wert:\n%1$s\n\nNeuer SHA-256-Wert:\n%2$s - Gateway wechseln - Gateway auswählen - Aktives Gateway diff --git a/apps/android/app/src/main/res/values-es/strings.xml b/apps/android/app/src/main/res/values-es/strings.xml index a01b77f9366..539bde90d6d 100644 --- a/apps/android/app/src/main/res/values-es/strings.xml +++ b/apps/android/app/src/main/res/values-es/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Conexión de Gateway - Conectar Gateway - Desconectar ¿Confiar en este gateway? Confiar y continuar Cancelar Nuevo chat en worktree - Endpoint - Estado - Tu gateway está activo y listo. - Conéctate a tu gateway para empezar. - Copiar informe para Claw - Controles avanzados - Método de conexión - Código de configuración - Manual - Pegar código de configuración - Host - Usar TLS - Token (opcional) - Contraseña - Encabezados personalizados (opcional) - Se envía con cada conexión a este Gateway, por ejemplo, encabezados de token de servicio de Cloudflare Access. Se almacena cifrado y se aplica en la próxima reconexión. - Valor del encabezado - Este encabezado lo gestiona la conexión y no se puede sobrescribir. - Añadir encabezado - Eliminar %1$s - Ejecutar la incorporación de nuevo - Endpoint resuelto Verifica la huella digital del certificado antes de confiar en este gateway.\n\n%1$s El certificado del gateway cambió. Continúa solo si esperabas este cambio.\n\nSHA-256 anterior:\n%1$s\n\nSHA-256 nuevo:\n%2$s - Cambiar de gateway - Seleccionar gateway - Gateway activo diff --git a/apps/android/app/src/main/res/values-fa/strings.xml b/apps/android/app/src/main/res/values-fa/strings.xml index 0a13e11b08f..907937ebf13 100644 --- a/apps/android/app/src/main/res/values-fa/strings.xml +++ b/apps/android/app/src/main/res/values-fa/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - اتصال دروازه - اتصال به دروازه - قطع اتصال به این دروازه اعتماد دارید؟ اعتماد و ادامه لغو چت جدید در worktree - نقطه پایانی - وضعیت - دروازه شما فعال و آماده است. - برای شروع، به دروازه خود متصل شوید. - کپی گزارش برای Claw - کنترل‌های پیشرفته - روش اتصال - کد راه‌اندازی - دستی - کد راه‌اندازی را جای‌گذاری کنید - میزبان - استفاده از TLS - توکن (اختیاری) - رمز عبور - هدرهای سفارشی (اختیاری) - با هر اتصال به این gateway ارسال می‌شود، برای مثال هدرهای توکن سرویس Cloudflare Access. به‌صورت رمزگذاری‌شده ذخیره می‌شود و در اتصال مجدد بعدی اعمال می‌شود. - مقدار هدر - این هدر توسط اتصال مدیریت می‌شود و قابل بازنویسی نیست. - افزودن هدر - حذف %1$s - اجرای دوباره فرایند شروع به کار - نقطه پایانی حل‌شده پیش از اعتماد به این دروازه، اثر انگشت گواهی را تأیید کنید.\n\n%1$s گواهی دروازه تغییر کرده است. فقط در صورتی ادامه دهید که انتظار این تغییر را داشتید.\n\nSHA-256 قدیمی:\n%1$s\n\nSHA-256 جدید:\n%2$s - تغییر Gateway - انتخاب Gateway - Gateway فعال diff --git a/apps/android/app/src/main/res/values-fr/strings.xml b/apps/android/app/src/main/res/values-fr/strings.xml index e3976c4f81e..3d967c76b5e 100644 --- a/apps/android/app/src/main/res/values-fr/strings.xml +++ b/apps/android/app/src/main/res/values-fr/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Connexion à la passerelle - Connecter la passerelle - Déconnecter Faire confiance à cette passerelle ? Faire confiance et continuer Annuler Nouveau chat dans le worktree - Point de terminaison - État - Votre passerelle est active et prête. - Connectez-vous à votre passerelle pour commencer. - Copier le rapport pour Claw - Contrôles avancés - Méthode de connexion - Code de configuration - Manuel - Coller le code de configuration - Hôte - Utiliser TLS - Jeton (facultatif) - Mot de passe - En-têtes personnalisés (facultatif) - Envoyés à chaque connexion à cette Gateway, par exemple les en-têtes de jeton de service Cloudflare Access. Stockés de manière chiffrée et appliqués lors de la prochaine reconnexion. - Valeur de l’en-tête - Cet en-tête est géré par la connexion et ne peut pas être remplacé. - Ajouter un en-tête - Supprimer %1$s - Relancer l’intégration - Point de terminaison résolu Vérifiez l’empreinte du certificat avant d’accorder votre confiance à cette passerelle.\n\n%1$s Le certificat de la passerelle a changé. Continuez uniquement si vous vous attendiez à ce changement.\n\nAncien SHA-256 :\n%1$s\n\nNouveau SHA-256 :\n%2$s - Changer de Gateway - Sélectionner un Gateway - Gateway actif diff --git a/apps/android/app/src/main/res/values-hi/strings.xml b/apps/android/app/src/main/res/values-hi/strings.xml index a539e8be72c..5dcc02d8e59 100644 --- a/apps/android/app/src/main/res/values-hi/strings.xml +++ b/apps/android/app/src/main/res/values-hi/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - गेटवे कनेक्शन - गेटवे कनेक्ट करें - डिस्कनेक्ट करें इस गेटवे पर भरोसा करें? भरोसा करें और जारी रखें रद्द करें worktree में नया चैट - एंडपॉइंट - स्थिति - आपका गेटवे सक्रिय और तैयार है। - शुरू करने के लिए अपने गेटवे से कनेक्ट करें। - Claw के लिए रिपोर्ट कॉपी करें - उन्नत नियंत्रण - कनेक्शन विधि - सेटअप कोड - मैन्युअल - सेटअप कोड पेस्ट करें - होस्ट - TLS का उपयोग करें - टोकन (वैकल्पिक) - पासवर्ड - कस्टम हेडर (वैकल्पिक) - इस gateway से हर कनेक्शन के साथ भेजा जाता है, उदाहरण के लिए Cloudflare Access service token headers. एन्क्रिप्ट करके संग्रहीत किया जाता है और अगले reconnect पर लागू किया जाता है। - हेडर मान - यह हेडर कनेक्शन द्वारा प्रबंधित है और इसे ओवरराइड नहीं किया जा सकता। - हेडर जोड़ें - %1$s हटाएँ - ऑनबोर्डिंग फिर से चलाएँ - रिज़ॉल्व किया गया एंडपॉइंट इस गेटवे पर भरोसा करने से पहले प्रमाणपत्र फ़िंगरप्रिंट सत्यापित करें।\n\n%1$s गेटवे प्रमाणपत्र बदल गया है। केवल तभी जारी रखें जब आपको इस बदलाव की अपेक्षा थी।\n\nपुराना SHA-256:\n%1$s\n\nनया SHA-256:\n%2$s - Gateway बदलें - Gateway चुनें - सक्रिय gateway diff --git a/apps/android/app/src/main/res/values-in/strings.xml b/apps/android/app/src/main/res/values-in/strings.xml index cef9d7b4456..773990f4fe7 100644 --- a/apps/android/app/src/main/res/values-in/strings.xml +++ b/apps/android/app/src/main/res/values-in/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Koneksi Gateway - Hubungkan Gateway - Putuskan koneksi Percayai gateway ini? Percayai dan lanjutkan Batal Chat baru di worktree - Endpoint - Status - Gateway Anda aktif dan siap. - Hubungkan ke gateway Anda untuk memulai. - Salin Laporan untuk Claw - Kontrol lanjutan - Metode koneksi - Kode Penyiapan - Manual - Tempel kode penyiapan - Host - Gunakan TLS - Token (opsional) - Kata sandi - Header kustom (opsional) - Dikirim dengan setiap koneksi ke gateway ini, misalnya header token layanan Cloudflare Access. Disimpan secara terenkripsi dan diterapkan pada koneksi ulang berikutnya. - Nilai header - Header ini dikelola oleh koneksi dan tidak dapat ditimpa. - Tambahkan header - Hapus %1$s - Jalankan onboarding lagi - Endpoint yang diselesaikan Verifikasi sidik jari sertifikat sebelum memercayai gateway ini.\n\n%1$s Sertifikat gateway berubah. Lanjutkan hanya jika Anda mengharapkan perubahan ini.\n\nSHA-256 lama:\n%1$s\n\nSHA-256 baru:\n%2$s - Ganti gateway - Pilih gateway - Gateway aktif diff --git a/apps/android/app/src/main/res/values-it/strings.xml b/apps/android/app/src/main/res/values-it/strings.xml index af4e30da743..2e815058b62 100644 --- a/apps/android/app/src/main/res/values-it/strings.xml +++ b/apps/android/app/src/main/res/values-it/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Connessione al gateway - Connetti gateway - Disconnetti Considerare attendibile questo gateway? Considera attendibile e continua Annulla Nuova chat nel worktree - Endpoint - Stato - Il tuo gateway è attivo e pronto. - Connettiti al tuo gateway per iniziare. - Copia report per Claw - Controlli avanzati - Metodo di connessione - Codice di configurazione - Manuale - Incolla codice di configurazione - Host - Usa TLS - Token (opzionale) - Password - Intestazioni personalizzate (facoltativo) - Inviate con ogni connessione a questo gateway, ad esempio le intestazioni dei token del servizio Cloudflare Access. Archiviate crittografate e applicate alla prossima riconnessione. - Valore dell\'intestazione - Questa intestazione è gestita dalla connessione e non può essere sovrascritta. - Aggiungi intestazione - Rimuovi %1$s - Esegui di nuovo l’onboarding - Endpoint risolto Verifica l’impronta digitale del certificato prima di considerare attendibile questo gateway.\n\n%1$s Il certificato del gateway è cambiato. Continua solo se ti aspettavi questa modifica.\n\nSHA-256 precedente:\n%1$s\n\nNuovo SHA-256:\n%2$s - Cambia gateway - Seleziona gateway - Gateway attivo diff --git a/apps/android/app/src/main/res/values-ja/strings.xml b/apps/android/app/src/main/res/values-ja/strings.xml index 08f1c6adc4e..c8b8fc117e3 100644 --- a/apps/android/app/src/main/res/values-ja/strings.xml +++ b/apps/android/app/src/main/res/values-ja/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - ゲートウェイ接続 - ゲートウェイに接続 - 切断 このゲートウェイを信頼しますか? 信頼して続行 キャンセル worktree で新規チャット - エンドポイント - ステータス - ゲートウェイはアクティブで準備完了です。 - 開始するにはゲートウェイに接続してください。 - Claw 用レポートをコピー - 詳細コントロール - 接続方法 - セットアップコード - 手動 - セットアップコードを貼り付け - ホスト - TLS を使用 - トークン(任意) - パスワード - カスタムヘッダー(任意) - このゲートウェイへのすべての接続で送信されます。たとえば、Cloudflare Access サービス トークン ヘッダーなどです。暗号化して保存され、次回の再接続時に適用されます。 - ヘッダー値 - このヘッダーは接続によって管理されており、上書きできません。 - ヘッダーを追加 - %1$s を削除 - オンボーディングを再実行 - 解決済みエンドポイント このゲートウェイを信頼する前に、証明書のフィンガープリントを確認してください。\n\n%1$s ゲートウェイの証明書が変更されました。想定した変更である場合のみ続行してください。\n\n以前の SHA-256:\n%1$s\n\n新しい SHA-256:\n%2$s - Gatewayを切り替え - Gatewayを選択 - アクティブなGateway diff --git a/apps/android/app/src/main/res/values-ko/strings.xml b/apps/android/app/src/main/res/values-ko/strings.xml index b5f8b455d19..48dc88d658a 100644 --- a/apps/android/app/src/main/res/values-ko/strings.xml +++ b/apps/android/app/src/main/res/values-ko/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - 게이트웨이 연결 - 게이트웨이 연결 - 연결 해제 이 게이트웨이를 신뢰하시겠습니까? 신뢰하고 계속 취소 worktree에서 새 채팅 - 엔드포인트 - 상태 - 게이트웨이가 활성화되어 준비되었습니다. - 시작하려면 게이트웨이에 연결하세요. - Claw용 보고서 복사 - 고급 제어 - 연결 방법 - 설정 코드 - 수동 - 설정 코드 붙여넣기 - 호스트 - TLS 사용 - 토큰(선택 사항) - 비밀번호 - 사용자 지정 헤더(선택 사항) - 이 Gateway에 연결할 때마다 전송됩니다. 예: Cloudflare Access 서비스 토큰 헤더. 암호화되어 저장되며 다음 재연결 시 적용됩니다. - 헤더 값 - 이 헤더는 연결에서 관리되며 재정의할 수 없습니다. - 헤더 추가 - %1$s 제거 - 온보딩 다시 실행 - 확인된 엔드포인트 이 게이트웨이를 신뢰하기 전에 인증서 지문을 확인하세요.\n\n%1$s 게이트웨이 인증서가 변경되었습니다. 예상한 변경인 경우에만 계속하세요.\n\n이전 SHA-256:\n%1$s\n\n새 SHA-256:\n%2$s - Gateway 전환 - Gateway 선택 - 활성 Gateway diff --git a/apps/android/app/src/main/res/values-nl/strings.xml b/apps/android/app/src/main/res/values-nl/strings.xml index 454061ddca6..868a2866a08 100644 --- a/apps/android/app/src/main/res/values-nl/strings.xml +++ b/apps/android/app/src/main/res/values-nl/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Gatewayverbinding - Gateway verbinden - Verbinding verbreken Deze gateway vertrouwen? Vertrouwen en doorgaan Annuleren Nieuwe chat in worktree - Endpoint - Status - Je gateway is actief en klaar voor gebruik. - Verbind met je gateway om te beginnen. - Rapport voor Claw kopiëren - Geavanceerde bediening - Verbindingsmethode - Setupcode - Handmatig - Setupcode plakken - Host - TLS gebruiken - Token (optioneel) - Wachtwoord - Aangepaste headers (optioneel) - Meegestuurd met elke verbinding met deze Gateway, bijvoorbeeld headers voor Cloudflare Access-servicetokens. Versleuteld opgeslagen en toegepast bij de volgende herverbinding. - Headerwaarde - Deze header wordt beheerd door de verbinding en kan niet worden overschreven. - Header toevoegen - %1$s verwijderen - Onboarding opnieuw uitvoeren - Opgelost endpoint Controleer de certificaatvingerafdruk voordat je deze gateway vertrouwt.\n\n%1$s Het gatewaycertificaat is gewijzigd. Ga alleen door als je deze wijziging verwachtte.\n\nOude SHA-256:\n%1$s\n\nNieuwe SHA-256:\n%2$s - Van gateway wisselen - Gateway selecteren - Actieve gateway diff --git a/apps/android/app/src/main/res/values-pl/strings.xml b/apps/android/app/src/main/res/values-pl/strings.xml index 2edb5e43a06..c8408860e22 100644 --- a/apps/android/app/src/main/res/values-pl/strings.xml +++ b/apps/android/app/src/main/res/values-pl/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Połączenie z bramą - Połącz z bramą - Rozłącz Ufać tej bramie? Zaufaj i kontynuuj Anuluj Nowy czat w worktree - Punkt końcowy - Status - Twoja brama jest aktywna i gotowa. - Połącz się ze swoją bramą, aby rozpocząć. - Kopiuj raport dla Claw - Zaawansowane ustawienia - Metoda połączenia - Kod konfiguracji - Ręcznie - Wklej kod konfiguracji - Host - Użyj TLS - Token (opcjonalnie) - Hasło - Niestandardowe nagłówki (opcjonalnie) - Wysyłane przy każdym połączeniu z tym gateway, na przykład nagłówki tokena usługi Cloudflare Access. Przechowywane w postaci zaszyfrowanej i stosowane przy następnym ponownym połączeniu. - Wartość nagłówka - Ten nagłówek jest zarządzany przez połączenie i nie można go zastąpić. - Dodaj nagłówek - Usuń %1$s - Uruchom ponownie wdrażanie - Rozpoznany punkt końcowy Sprawdź odcisk certyfikatu, zanim zaufasz tej bramie.\n\n%1$s Certyfikat bramy uległ zmianie. Kontynuuj tylko wtedy, gdy oczekujesz tej zmiany.\n\nStary SHA-256:\n%1$s\n\nNowy SHA-256:\n%2$s - Przełącz Gateway - Wybierz Gateway - Aktywne Gateway diff --git a/apps/android/app/src/main/res/values-pt-rBR/strings.xml b/apps/android/app/src/main/res/values-pt-rBR/strings.xml index c908476845b..4e0567e9161 100644 --- a/apps/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/apps/android/app/src/main/res/values-pt-rBR/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Conexão do Gateway - Conectar Gateway - Desconectar Confiar neste gateway? Confiar e continuar Cancelar Novo chat no worktree - Endpoint - Status - Seu gateway está ativo e pronto. - Conecte-se ao seu gateway para começar. - Copiar relatório para o Claw - Controles avançados - Método de conexão - Código de configuração - Manual - Colar código de configuração - Host - Usar TLS - Token (opcional) - Senha - Cabeçalhos personalizados (opcional) - Enviados com todas as conexões a este gateway, por exemplo, cabeçalhos de token de serviço do Cloudflare Access. Armazenados criptografados e aplicados na próxima reconexão. - Valor do cabeçalho - Este cabeçalho é gerenciado pela conexão e não pode ser substituído. - Adicionar cabeçalho - Remover %1$s - Executar integração novamente - Endpoint resolvido Verifique a impressão digital do certificado antes de confiar neste gateway.\n\n%1$s O certificado do gateway foi alterado. Continue somente se você esperava essa alteração.\n\nSHA-256 antigo:\n%1$s\n\nSHA-256 novo:\n%2$s - Trocar Gateway - Selecionar Gateway - Gateway ativo diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 66a113aa722..94b5b2c3bde 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Подключение к шлюзу - Подключить шлюз - Отключить Доверять этому шлюзу? Доверять и продолжить Отмена Новый чат в worktree - Конечная точка - Статус - Ваш шлюз активен и готов. - Подключитесь к своему шлюзу, чтобы начать. - Скопировать отчет для Claw - Расширенные настройки - Способ подключения - Код настройки - Вручную - Вставьте код настройки - Хост - Использовать TLS - Токен (необязательно) - Пароль - Пользовательские заголовки (необязательно) - Отправляются при каждом подключении к этому gateway, например заголовки токена службы Cloudflare Access. Хранятся в зашифрованном виде и применяются при следующем переподключении. - Значение заголовка - Этот заголовок управляется подключением и не может быть переопределен. - Добавить заголовок - Удалить %1$s - Запустить настройку заново - Разрешенная конечная точка Проверьте отпечаток сертификата, прежде чем доверять этому шлюзу.\n\n%1$s Сертификат шлюза изменился. Продолжайте, только если вы ожидали это изменение.\n\nСтарый SHA-256:\n%1$s\n\nНовый SHA-256:\n%2$s - Переключить gateway - Выбрать gateway - Активный gateway diff --git a/apps/android/app/src/main/res/values-sv/strings.xml b/apps/android/app/src/main/res/values-sv/strings.xml index c0ddf836ed1..267cdfa2b51 100644 --- a/apps/android/app/src/main/res/values-sv/strings.xml +++ b/apps/android/app/src/main/res/values-sv/strings.xml @@ -1,37 +1,9 @@ OpenClaw-nod - Gatewayanslutning - Anslut gateway - Koppla från Lita på denna gateway? Lita på och fortsätt Avbryt Ny chatt i worktree - Slutpunkt - Status - Din gateway är aktiv och redo. - Anslut till din gateway för att komma igång. - Kopiera rapport för Claw - Avancerade kontroller - Anslutningsmetod - Konfigurationskod - Manuell - Klistra in konfigurationskod - Värd - Använd TLS - Token (valfritt) - Lösenord - Anpassade headers (valfritt) - Skickas med varje anslutning till denna gateway, till exempel Cloudflare Access service token headers. Lagras krypterat och tillämpas vid nästa återanslutning. - Headervärde - Denna header hanteras av anslutningen och kan inte åsidosättas. - Lägg till header - Ta bort %1$s - Kör introduktionen igen - Löst slutpunkt Verifiera certifikatets fingeravtryck innan du litar på denna gateway.\n\n%1$s Gateway-certifikatet har ändrats. Fortsätt bara om du förväntade dig denna ändring.\n\nTidigare SHA-256:\n%1$s\n\nNy SHA-256:\n%2$s - Byt Gateway - Välj Gateway - Aktiv Gateway diff --git a/apps/android/app/src/main/res/values-th/strings.xml b/apps/android/app/src/main/res/values-th/strings.xml index cfdd73799db..f10686f06f9 100644 --- a/apps/android/app/src/main/res/values-th/strings.xml +++ b/apps/android/app/src/main/res/values-th/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - การเชื่อมต่อเกตเวย์ - เชื่อมต่อเกตเวย์ - ตัดการเชื่อมต่อ เชื่อถือเกตเวย์นี้หรือไม่? เชื่อถือและดำเนินการต่อ ยกเลิก แชทใหม่ใน worktree - เอนด์พอยต์ - สถานะ - เกตเวย์ของคุณเปิดใช้งานและพร้อมใช้งานแล้ว - เชื่อมต่อกับเกตเวย์ของคุณเพื่อเริ่มต้นใช้งาน - คัดลอกรายงานสำหรับ Claw - การควบคุมขั้นสูง - วิธีการเชื่อมต่อ - รหัสตั้งค่า - ด้วยตนเอง - วางรหัสตั้งค่า - โฮสต์ - ใช้ TLS - โทเค็น (ไม่บังคับ) - รหัสผ่าน - ส่วนหัวที่กำหนดเอง (ไม่บังคับ) - ส่งไปพร้อมกับทุกการเชื่อมต่อไปยัง gateway นี้ เช่น ส่วนหัวโทเค็นบริการ Cloudflare Access จัดเก็บแบบเข้ารหัสและจะนำไปใช้ในการเชื่อมต่อใหม่ครั้งถัดไป - ค่าส่วนหัว - ส่วนหัวนี้ถูกจัดการโดยการเชื่อมต่อและไม่สามารถเขียนทับได้ - เพิ่มส่วนหัว - ลบ %1$s - เรียกใช้การเริ่มต้นใช้งานอีกครั้ง - เอนด์พอยต์ที่แก้ไขแล้ว ตรวจสอบลายนิ้วมือของใบรับรองก่อนเชื่อถือเกตเวย์นี้\n\n%1$s ใบรับรองของเกตเวย์มีการเปลี่ยนแปลง ดำเนินการต่อเมื่อคุณคาดว่าจะมีการเปลี่ยนแปลงนี้เท่านั้น\n\nSHA-256 เดิม:\n%1$s\n\nSHA-256 ใหม่:\n%2$s - สลับ Gateway - เลือก Gateway - Gateway ที่ใช้งานอยู่ diff --git a/apps/android/app/src/main/res/values-tr/strings.xml b/apps/android/app/src/main/res/values-tr/strings.xml index 868b6bb8422..b0d9edc3886 100644 --- a/apps/android/app/src/main/res/values-tr/strings.xml +++ b/apps/android/app/src/main/res/values-tr/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Ağ Geçidi Bağlantısı - Ağ Geçidine Bağlan - Bağlantıyı Kes Bu ağ geçidine güvenilsin mi? Güven ve devam et İptal Worktree\'de yeni sohbet - Uç nokta - Durum - Ağ geçidiniz etkin ve hazır. - Başlamak için ağ geçidinize bağlanın. - Claw için Raporu Kopyala - Gelişmiş kontroller - Bağlantı yöntemi - Kurulum Kodu - Manuel - Kurulum kodunu yapıştır - Ana makine - TLS kullan - Token (isteğe bağlı) - Parola - Özel üst bilgiler (isteğe bağlı) - Bu gateway\'e yapılan her bağlantıyla birlikte gönderilir; örneğin Cloudflare Access hizmet belirteci üst bilgileri. Şifrelenmiş olarak saklanır ve bir sonraki yeniden bağlantıda uygulanır. - Üst bilgi değeri - Bu üst bilgi bağlantı tarafından yönetilir ve geçersiz kılınamaz. - Üst bilgi ekle - %1$s kaldır - Başlangıç sürecini tekrar çalıştır - Çözümlenen uç nokta Bu ağ geçidine güvenmeden önce sertifika parmak izini doğrulayın.\n\n%1$s Ağ geçidi sertifikası değişti. Yalnızca bu değişikliği bekliyorsanız devam edin.\n\nEski SHA-256:\n%1$s\n\nYeni SHA-256:\n%2$s - Gateway değiştir - Gateway seç - Etkin Gateway diff --git a/apps/android/app/src/main/res/values-uk/strings.xml b/apps/android/app/src/main/res/values-uk/strings.xml index 149c3d8bcd9..889f00bc3ee 100644 --- a/apps/android/app/src/main/res/values-uk/strings.xml +++ b/apps/android/app/src/main/res/values-uk/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Підключення до шлюзу - Підключити шлюз - Відключити Довіряти цьому шлюзу? Довіряти й продовжити Скасувати Новий чат у worktree - Кінцева точка - Стан - Ваш шлюз активний і готовий. - Підключіться до свого шлюзу, щоб почати. - Скопіювати звіт для Claw - Розширені елементи керування - Спосіб підключення - Код налаштування - Вручну - Вставте код налаштування - Хост - Використовувати TLS - Токен (необов’язково) - Пароль - Користувацькі заголовки (необов’язково) - Надсилається з кожним підключенням до цього gateway, наприклад заголовки токена сервісу Cloudflare Access. Зберігається в зашифрованому вигляді та застосовується під час наступного повторного підключення. - Значення заголовка - Цей заголовок керується підключенням і не може бути перевизначений. - Додати заголовок - Видалити %1$s - Запустити адаптацію знову - Визначена кінцева точка Перевірте відбиток сертифіката, перш ніж довіряти цьому шлюзу.\n\n%1$s Сертифікат шлюзу змінився. Продовжуйте, лише якщо ви очікували цю зміну.\n\nСтарий SHA-256:\n%1$s\n\nНовий SHA-256:\n%2$s - Перемкнути gateway - Виберіть gateway - Активний gateway diff --git a/apps/android/app/src/main/res/values-vi/strings.xml b/apps/android/app/src/main/res/values-vi/strings.xml index 3e069a05b7c..8c680514eaa 100644 --- a/apps/android/app/src/main/res/values-vi/strings.xml +++ b/apps/android/app/src/main/res/values-vi/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Kết nối cổng - Kết nối cổng - Ngắt kết nối Tin cậy cổng này? Tin cậy và tiếp tục Hủy Trò chuyện mới trong worktree - Điểm cuối - Trạng thái - Cổng của bạn đang hoạt động và sẵn sàng. - Kết nối với cổng của bạn để bắt đầu. - Sao chép báo cáo cho Claw - Điều khiển nâng cao - Phương thức kết nối - Mã thiết lập - Thủ công - Dán mã thiết lập - Máy chủ - Sử dụng TLS - Token (tùy chọn) - Mật khẩu - Tiêu đề tùy chỉnh (không bắt buộc) - Được gửi cùng mọi kết nối đến gateway này, ví dụ như các tiêu đề mã thông báo dịch vụ Cloudflare Access. Được lưu trữ ở dạng mã hóa và áp dụng trong lần kết nối lại tiếp theo. - Giá trị tiêu đề - Tiêu đề này do kết nối quản lý và không thể bị ghi đè. - Thêm tiêu đề - Xóa %1$s - Chạy hướng dẫn thiết lập lại - Điểm cuối đã phân giải Xác minh dấu vân tay chứng chỉ trước khi tin cậy cổng này.\n\n%1$s Chứng chỉ cổng đã thay đổi. Chỉ tiếp tục nếu bạn mong đợi thay đổi này.\n\nSHA-256 cũ:\n%1$s\n\nSHA-256 mới:\n%2$s - Chuyển Gateway - Chọn Gateway - Gateway đang hoạt động diff --git a/apps/android/app/src/main/res/values-zh-rCN/strings.xml b/apps/android/app/src/main/res/values-zh-rCN/strings.xml index 1eae9253b9e..52614149a93 100644 --- a/apps/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/apps/android/app/src/main/res/values-zh-rCN/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - 网关连接 - 连接网关 - 断开连接 信任此网关? 信任并继续 取消 在 worktree 中新建聊天 - 端点 - 状态 - 你的网关已激活并准备就绪。 - 连接到你的网关以开始使用。 - 复制 Claw 报告 - 高级控制 - 连接方式 - 设置代码 - 手动 - 粘贴设置代码 - 主机 - 使用 TLS - 令牌(可选) - 密码 - 自定义标头(可选) - 随每次连接到此 Gateway 一起发送,例如 Cloudflare Access 服务令牌标头。将加密存储,并在下次重新连接时应用。 - 标头值 - 此标头由连接管理,无法覆盖。 - 添加标头 - 移除 %1$s - 再次运行引导流程 - 已解析的端点 验证证书指纹后再信任此网关。\n\n%1$s 网关证书已更改。仅当这是您预期的更改时才继续。\n\n旧 SHA-256:\n%1$s\n\n新 SHA-256:\n%2$s - 切换 Gateway - 选择 Gateway - 当前 Gateway diff --git a/apps/android/app/src/main/res/values-zh-rTW/strings.xml b/apps/android/app/src/main/res/values-zh-rTW/strings.xml index f2d134abeed..cdaa1745b5a 100644 --- a/apps/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/apps/android/app/src/main/res/values-zh-rTW/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - 閘道連線 - 連接閘道 - 中斷連線 信任此閘道? 信任並繼續 取消 在 worktree 中新增聊天 - 端點 - 狀態 - 您的閘道已啟用並準備就緒。 - 連接到您的閘道以開始使用。 - 複製 Claw 報告 - 進階控制項 - 連線方式 - 設定碼 - 手動 - 貼上設定碼 - 主機 - 使用 TLS - 權杖(選填) - 密碼 - 自訂標頭(選填) - 會隨每次連線傳送至此 gateway,例如 Cloudflare Access 服務權杖標頭。已加密儲存,並會在下次重新連線時套用。 - 標頭值 - 此標頭由連線管理,無法覆寫。 - 新增標頭 - 移除 %1$s - 再次執行新手導覽 - 已解析的端點 請先驗證憑證指紋,再信任此閘道。\n\n%1$s 閘道憑證已變更。僅在這是您預期的變更時繼續。\n\n舊 SHA-256:\n%1$s\n\n新 SHA-256:\n%2$s - 切換 gateway - 選取 gateway - 使用中的 gateway diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 48baef6b5b1..3cc756921d9 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -1,37 +1,9 @@ OpenClaw Node - Gateway Connection - Connect Gateway - Disconnect Trust this gateway? Trust and continue Cancel New chat in worktree - Endpoint - Status - Your gateway is active and ready. - Connect to your gateway to get started. - Copy Report for Claw - Advanced controls - Connection method - Setup Code - Manual - Paste setup code - Host - Use TLS - Token (optional) - Password - Custom headers (optional) - Sent with every connection to this gateway, for example Cloudflare Access service token headers. Stored encrypted and applied on the next reconnect. - Header value - This header is managed by the connection and cannot be overridden. - Add header - Remove %1$s - Run onboarding again - Resolved endpoint Verify the certificate fingerprint before trusting this gateway.\n\n%1$s The gateway certificate changed. Continue only if you expected this.\n\nOld SHA-256:\n%1$s\n\nNew SHA-256:\n%2$s - Switch gateway - Select gateway - Active gateway